Skip to content

Commit eb1cb69

Browse files
committed
Add documentation
1 parent 04cb406 commit eb1cb69

File tree

5 files changed

+158
-0
lines changed

5 files changed

+158
-0
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
---
2+
layout: doc-page
3+
title: "MainAnnotation"
4+
---
5+
6+
`MainAnnotation` provides a generic way to define main annotations such as `@main`.
7+
8+
When a users annotates a method with an annotation that extends `MainAnnotation` a class with a `main` method will be generated. The main method will contain the code needed to parse the command line arguments and run the application.
9+
10+
```scala
11+
/** Sum all the numbers
12+
*
13+
* @param first Fist number to sum
14+
* @param rest The rest of the numbers to sum
15+
*/
16+
@myMain def sum(first: Int, rest: Int*): Int = first + rest.sum
17+
```
18+
19+
```scala
20+
object foo {
21+
def main(args: Array[String]): Unit = {
22+
val cmd = new myMain().command(
23+
args = args,
24+
commandName = "sum",
25+
documentation = "Sum all the numbers",
26+
new ParameterInfo("first", "scala.Int", hasDefault=false, isVarargs=false, "Fist number to sum"),
27+
new ParameterInfo("rest", "scala.Int" , hasDefault=false, isVarargs=true, "The rest of the numbers to sum")
28+
)
29+
val args0 = cmd.argGetter[Int](0, None) // using cmd.Parser[Int]
30+
val args1 = cmd.varargGetter[Int] // using cmd.Parser[Int]
31+
cmd.run(sum(args0(), args1()*))
32+
}
33+
}
34+
```
35+
36+
The implementation of the `main` method first instantiates the annotation and then creates a `Command`.
37+
When creating the `Command`, the arguments can be checked and preprocessed.
38+
Then it defines a series of argument getters calling `argGetter` for each parameter and `varargGetter` for the last one if it is a varargs. `argGetter` gets an optional lambda that computes the default argument.
39+
Finally, the `run` method is called to run the application. It receives a by-name argument that contains the call the annotated method with the instantiations arguments (using the lambdas from `argGetter`/`varargGetter`).
40+
41+
42+
Example of implementation of `myMain` that takes all arguments positionally. It used `util.CommandLineParser.FromString` and expects no default arguments. For simplicity, any errors in preprocessing or parsing results in crash.
43+
44+
```scala
45+
class myMain extends MainAnnotation:
46+
import MainAnnotation.{ ParameterInfo, Command }
47+
48+
// Parser used to parse command line arguments
49+
type Parser[T] = util.CommandLineParser.FromString[T]
50+
51+
// Result type of the annotated method
52+
type Result = Int
53+
54+
/** A new command with arguments from `args` */
55+
def command(args: Array[String], commandName: String, documentation: String, parameterInfos: ParameterInfo*): Command[Parser, Result] =
56+
if args.contains("--help") then
57+
println(documentation)
58+
// TODO: Print documentation of the parameters
59+
System.exit(0)
60+
assert(parameterInfos.forall(!_.hasDefault), "Default arguments are not supported")
61+
val (plainArgs, varargs) =
62+
if parameterInfos.last.isVarargs then
63+
val numPlainArgs = parameterInfos.length - 1
64+
assert(numPlainArgs <= args.length, "Not enough arguments")
65+
(args.take(numPlainArgs), args.drop(numPlainArgs))
66+
else
67+
assert(parameterInfos.length <= args.length, "Not enough arguments")
68+
assert(parameterInfos.length >= args.length, "Too many arguments")
69+
(args, Array.empty[String])
70+
new MyCommand(plainArgs, varargs)
71+
72+
@experimental
73+
class MyCommand(plainArgs: Seq[String], varargs: Seq[String]) extends Command[util.CommandLineParser.FromString, Int]:
74+
75+
def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using parser: Parser[T]): () => T =
76+
() => parser.fromString(plainArgs(idx))
77+
78+
def varargGetter[T](using parser: Parser[T]): () => Seq[T] =
79+
() => varargs.map(arg => parser.fromString(arg))
80+
81+
def run(program: => Result): Unit =
82+
println("executing program")
83+
val result = program
84+
println("result: " + result)
85+
println("executed program")
86+
end MyCommand
87+
end myMain
88+
```
89+

docs/sidebar.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ subsection:
147147
- page: reference/experimental/named-typeargs-spec.md
148148
- page: reference/experimental/numeric-literals.md
149149
- page: reference/experimental/explicit-nulls.md
150+
- page: reference/experimental/main-annotation.md
150151
- page: reference/experimental/cc.md
151152
- page: reference/experimental/tupled-function.md
152153
- page: reference/syntax.md

project/resources/referenceReplacements/sidebar.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ subsection:
127127
- page: reference/experimental/named-typeargs-spec.md
128128
- page: reference/experimental/numeric-literals.md
129129
- page: reference/experimental/explicit-nulls.md
130+
- page: reference/experimental/main-annotation.md
130131
- page: reference/experimental/cc.md
131132
- page: reference/syntax.md
132133
- title: Language Versions
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
executing program
2+
result: 28
3+
executed program
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import scala.annotation.*
2+
import collection.mutable
3+
4+
/** Sum all the numbers
5+
*
6+
* @param first Fist number to sum
7+
* @param rest The rest of the numbers to sum
8+
*/
9+
@myMain def sum(first: Int, rest: Int*): Int = first + rest.sum
10+
11+
12+
object Test:
13+
def callMain(args: Array[String]): Unit =
14+
val clazz = Class.forName("sum")
15+
val method = clazz.getMethod("main", classOf[Array[String]])
16+
method.invoke(null, args)
17+
18+
def main(args: Array[String]): Unit =
19+
callMain(Array("23", "2", "3"))
20+
end Test
21+
22+
@experimental
23+
class myMain extends MainAnnotation:
24+
import MainAnnotation.{ ParameterInfo, Command }
25+
26+
// Parser used to parse command line arguments
27+
type Parser[T] = util.CommandLineParser.FromString[T]
28+
29+
// Result type of the annotated method
30+
type Result = Int
31+
32+
/** A new command with arguments from `args` */
33+
def command(args: Array[String], commandName: String, documentation: String, parameterInfos: ParameterInfo*): Command[Parser, Result] =
34+
if args.contains("--help") then
35+
println(documentation)
36+
System.exit(0)
37+
assert(parameterInfos.forall(!_.hasDefault), "Default arguments are not supported")
38+
val (plainArgs, varargs) =
39+
if parameterInfos.last.isVarargs then
40+
val numPlainArgs = parameterInfos.length - 1
41+
assert(numPlainArgs <= args.length, "Not enough arguments")
42+
(args.take(numPlainArgs), args.drop(numPlainArgs))
43+
else
44+
assert(parameterInfos.length <= args.length, "Not enough arguments")
45+
assert(parameterInfos.length >= args.length, "Too many arguments")
46+
(args, Array.empty[String])
47+
new MyCommand(plainArgs, varargs)
48+
49+
@experimental
50+
class MyCommand(plainArgs: Seq[String], varargs: Seq[String]) extends Command[util.CommandLineParser.FromString, Int]:
51+
52+
def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using parser: Parser[T]): () => T =
53+
() => parser.fromString(plainArgs(idx))
54+
55+
def varargGetter[T](using parser: Parser[T]): () => Seq[T] =
56+
() => varargs.map(arg => parser.fromString(arg))
57+
58+
def run(program: => Result): Unit =
59+
println("executing program")
60+
val result = program
61+
println("result: " + result)
62+
println("executed program")
63+
end MyCommand
64+
end myMain

0 commit comments

Comments
 (0)