在 GitHub 上编辑此页面

主要注释

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,如果最后一个参数是可变参数,则调用 varargGetterargGetter 获取一个可选的 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