1
+ import annotation .StaticAnnotation
2
+ import collection .mutable
3
+
4
+ trait MainAnnotation extends StaticAnnotation :
5
+
6
+ type ArgumentParser [T ]
7
+
8
+ // get single argument
9
+ def getArg [T ](argName : String , fromString : ArgumentParser [T ], defaultValue : Option [T ] = None ): () => T
10
+
11
+ // get varargs argument
12
+ def getArgs [T ](argName : String , fromString : ArgumentParser [T ]): () => List [T ]
13
+
14
+ // check that everything is parsed
15
+ def done (): Boolean
16
+
17
+ end MainAnnotation
18
+
19
+ // Sample main class, can be freely implemented:
20
+
21
+ class _main (progName : String , args : Array [String ], docComment : String ) extends MainAnnotation :
22
+
23
+ def this () = this (" " , Array (), " " )
24
+
25
+ type ArgumentParser = util.FromString
26
+
27
+ /** A buffer of demanded argument names, plus
28
+ * "?" if it has a default
29
+ * "*" if it is a vararg
30
+ * "" otherwise
31
+ */
32
+ private var argInfos = new mutable.ListBuffer [(String , String )]
33
+
34
+ /** A buffer for all errors */
35
+ private var errors = new mutable.ListBuffer [String ]
36
+
37
+ /** The next argument index */
38
+ private var n : Int = 0
39
+
40
+ private def error (msg : String ): () => Nothing =
41
+ errors += msg
42
+ () => ???
43
+
44
+ private def argAt (idx : Int ): Option [String ] =
45
+ if idx < args.length then Some (args(idx)) else None
46
+
47
+ private def nextPositionalArg (): Option [String ] =
48
+ while n < args.length && args(n).startsWith(" --" ) do n += 2
49
+ val result = argAt(n)
50
+ n += 1
51
+ result
52
+
53
+ private def convert [T ](argName : String , arg : String , p : ArgumentParser [T ]): () => T =
54
+ p.fromStringOption(arg) match
55
+ case Some (t) => () => t
56
+ case None => error(s " invalid argument for $argName: $arg" )
57
+
58
+ def getArg [T ](argName : String , p : ArgumentParser [T ], defaultValue : Option [T ] = None ): () => T =
59
+ argInfos += ((argName, if defaultValue.isDefined then " ?" else " " ))
60
+ val idx = args.indexOf(s " -- $argName" )
61
+ val argOpt = if idx >= 0 then argAt(idx + 1 ) else nextPositionalArg()
62
+ argOpt match
63
+ case Some (arg) => convert(argName, arg, p)
64
+ case None => defaultValue match
65
+ case Some (t) => () => t
66
+ case None => error(s " missing argument for $argName" )
67
+
68
+ def getArgs [T ](argName : String , fromString : ArgumentParser [T ]): () => List [T ] =
69
+ argInfos += ((argName, " *" ))
70
+ def recur (): List [() => T ] = nextPositionalArg() match
71
+ case Some (arg) => convert(arg, argName, fromString) :: recur()
72
+ case None => Nil
73
+ val fns = recur()
74
+ () => fns.map(_())
75
+
76
+ def usage (): Boolean =
77
+ println(s " Usage: $progName ${argInfos.map(_ + _).mkString(" " )}" )
78
+ if docComment.nonEmpty then
79
+ println(docComment) // todo: process & format doc comment
80
+ false
81
+
82
+ def showUnused (): Unit = nextPositionalArg() match
83
+ case Some (arg) =>
84
+ error(s " unused argument: $arg" )
85
+ showUnused()
86
+ case None =>
87
+ for
88
+ arg <- args
89
+ if arg.startsWith(" --" ) && ! argInfos.map(_._1).contains(arg.drop(2 ))
90
+ do
91
+ error(s " unknown argument name: $arg" )
92
+ end showUnused
93
+
94
+ def done (): Boolean =
95
+ if args.contains(" --help" ) then
96
+ usage()
97
+ else
98
+ showUnused()
99
+ if errors.nonEmpty then
100
+ for msg <- errors do println(s " Error: $msg" )
101
+ usage()
102
+ else
103
+ true
104
+ end done
105
+ end _main
106
+
107
+ // Sample main method
108
+
109
+ object myProgram :
110
+
111
+ /** Adds two numbers */
112
+ @ _main def add (num : Int , inc : Int = 1 ) =
113
+ println(s " $num + $inc = ${num + inc}" )
114
+
115
+ end myProgram
116
+
117
+ // Compiler generated code:
118
+
119
+ object add :
120
+ def main (args : Array [String ]) =
121
+ val cmd = new _main(" add" , args, " Adds two numbers" )
122
+ val arg1 = cmd.getArg[Int ](" num" , summon[cmd.ArgumentParser [Int ]])
123
+ val arg2 = cmd.getArg[Int ](" inc" , summon[cmd.ArgumentParser [Int ]], Some (1 ))
124
+ if cmd.done() then myProgram.add(arg1(), arg2())
125
+ end add
126
+
127
+ /** --- Some scenarios ----------------------------------------
128
+
129
+ > java add 2 3
130
+ 2 + 3 = 5
131
+ > java add 2 3
132
+ 2 + 3 = 5
133
+ > java add 4
134
+ 4 + 1 = 5
135
+ > java add --num 10 --inc -2
136
+ 10 + -2 = 8
137
+ > java add --num 10
138
+ 10 + 1 = 11
139
+ > java add --help
140
+ Usage: add num inc?
141
+ Adds two numbers
142
+ > java add
143
+ error: missing argument for num
144
+ Usage: add num inc?
145
+ Adds two numbers
146
+ > java add 1 2 3
147
+ error: unused argument: 3
148
+ Usage: add num inc?
149
+ Adds two numbers
150
+ > java add --num 1 --incr 2
151
+ error: unknown argument name: --incr
152
+ Usage: add num inc?
153
+ Adds two numbers
154
+ > java add 1 true
155
+ error: invalid argument for inc: true
156
+ Usage: add num inc?
157
+ Adds two numbers
158
+ > java add true false
159
+ error: invalid argument for num: true
160
+ error: invalid argument for inc: false
161
+ Usage: add num inc?
162
+ Adds two numbers
163
+ > java add true false --foo 33
164
+ Error: invalid argument for num: true
165
+ Error: invalid argument for inc: false
166
+ Error: unknown argument name: --foo
167
+ Usage: add num inc?
168
+ Adds two numbers
169
+
170
+ */
0 commit comments