主要注释
MainAnnotation
提供了一种通用方法来定义主注释,例如 @main
。
当用户使用扩展 MainAnnotation
的注释来注释方法时,将生成一个包含 main
方法的类。主方法将包含解析命令行参数并运行应用程序所需的代码。
/** Sum all the numbers
*
* @param first Fist number to sum
* @param rest The rest of the numbers to sum
*/
@myMain def sum(first: Int, second: Int = 0, rest: Int*): Int = first + second + rest.sum
object foo {
def main(args: Array[String]): Unit = {
val mainAnnot = new myMain()
val info = new Info(
name = "foo.main",
documentation = "Sum all the numbers",
parameters = Seq(
new Parameter("first", "scala.Int", hasDefault=false, isVarargs=false, "Fist number to sum", Seq()),
new Parameter("second", "scala.Int", hasDefault=true, isVarargs=false, "", Seq()),
new Parameter("rest", "scala.Int" , hasDefault=false, isVarargs=true, "The rest of the numbers to sum", Seq())
)
)
val mainArgsOpt = mainAnnot.command(info, args)
if mainArgsOpt.isDefined then
val mainArgs = mainArgsOpt.get
val args0 = mainAnnot.argGetter[Int](info.parameters(0), mainArgs(0), None) // using a parser of Int
val args1 = mainAnnot.argGetter[Int](info.parameters(1), mainArgs(1), Some(() => sum$default$1())) // using a parser of Int
val args2 = mainAnnot.varargGetter[Int](info.parameters(2), mainArgs.drop(2)) // using a parser of Int
mainAnnot.run(() => sum(args0(), args1(), args2()*))
}
}
main
方法的实现首先实例化注解,然后调用 command
。在调用 command
时,可以检查和预处理参数。然后,它定义了一系列参数获取器,为每个参数调用 argGetter
,如果最后一个参数是可变参数,则调用 varargGetter
。argGetter
获取一个可选的 lambda 表达式,用于计算默认参数。最后,调用 run
方法来运行应用程序。它接收一个按名称传递的参数,该参数包含对带注解方法的调用,以及实例化参数(使用来自 argGetter
/varargGetter
的 lambda 表达式)。
myMain
实现的示例,它按位置获取所有参数。它使用 util.CommandLineParser.FromString
,并且不期望任何默认参数。为了简单起见,任何预处理或解析错误都会导致崩溃。
// Parser used to parse command line arguments
import scala.util.CommandLineParser.FromString[T]
// Result type of the annotated method is Int and arguments are parsed using FromString
@experimental class myMain extends MainAnnotation[FromString, Int]:
import MainAnnotation.{ Info, Parameter }
def command(info: Info, args: Seq[String]): Option[Seq[String]] =
if args.contains("--help") then
println(info.documentation)
None // do not parse or run the program
else if info.parameters.exists(_.hasDefault) then
println("Default arguments are not supported")
None
else if info.hasVarargs then
val numPlainArgs = info.parameters.length - 1
if numPlainArgs > args.length then
println("Not enough arguments")
None
else
Some(args)
else
if info.parameters.length > args.length then
println("Not enough arguments")
None
else if info.parameters.length < args.length then
println("Too many arguments")
None
else
Some(args)
def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using parser: FromString[T]): () => T =
() => parser.fromString(arg)
def varargGetter[T](param: Parameter, args: Seq[String])(using parser: FromString[T]): () => Seq[T] =
() => args.map(arg => parser.fromString(arg))
def run(program: () => Int): Unit =
println("executing program")
val result = program()
println("result: " + result)
println("executed program")
end myMain