Skip to content

Commit e3b36a8

Browse files
committed
Prototype for proposed main method generation scheme
1 parent 1931bc7 commit e3b36a8

File tree

1 file changed

+170
-0
lines changed

1 file changed

+170
-0
lines changed

tests/pos/main-method-scheme.scala

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
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

Comments
 (0)