Skip to content

Commit

Permalink
Add MAinAnnotation Parser and Result type parameters
Browse files Browse the repository at this point in the history
And remove type members Parser and Result
  • Loading branch information
nicolasstucki committed Apr 1, 2022
1 parent 1b18a68 commit 9bd6708
Show file tree
Hide file tree
Showing 10 changed files with 74 additions and 101 deletions.
26 changes: 12 additions & 14 deletions docs/_docs/reference/experimental/main-annotation.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ object foo {
),
args = args
)
val args0 = cmd.argGetter[Int](0, None) // using cmd.Parser[Int]
val args1 = cmd.varargGetter[Int] // using cmd.Parser[Int]
val args0 = cmd.argGetter[Int](0, None) // using a parser of Int
val args1 = cmd.varargGetter[Int] // using a parser of Int
cmd.run(() => sum(args0(), args1()*))
}
}
Expand All @@ -47,17 +47,15 @@ Finally, the `run` method is called to run the application. It receives a by-nam
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.

```scala
class myMain extends MainAnnotation:
import MainAnnotation.{ ParameterInfo, Command }

// Parser used to parse command line arguments
type Parser[T] = util.CommandLineParser.FromString[T]
// Parser used to parse command line arguments
import scala.util.CommandLineParser.FromString[T]

// Result type of the annotated method
type Result = Int
// Result type of the annotated method is Int
class myMain extends MainAnnotation[FromString, Int]:
import MainAnnotation.{ ParameterInfo, Command }

/** A new command with arguments from `args` */
def command(info: CommandInfo, args: Array[String]): Command[Parser, Result] =
def command(info: CommandInfo, args: Array[String]): Command[FromString, Int] =
if args.contains("--help") then
println(info.documentation)
// TODO: Print documentation of the parameters
Expand All @@ -75,15 +73,15 @@ class myMain extends MainAnnotation:
new MyCommand(plainArgs, varargs)

@experimental
class MyCommand(plainArgs: Seq[String], varargs: Seq[String]) extends Command[util.CommandLineParser.FromString, Int]:
class MyCommand(plainArgs: Seq[String], varargs: Seq[String]) extends Command[FromString, Int]:

def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using parser: Parser[T]): () => T =
def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using parser: FromString[T]): () => T =
() => parser.fromString(plainArgs(idx))

def varargGetter[T](using parser: Parser[T]): () => Seq[T] =
def varargGetter[T](using parser: FromString[T]): () => Seq[T] =
() => varargs.map(arg => parser.fromString(arg))

def run(program: () => Result): Unit =
def run(program: () => Int): Unit =
println("executing program")
val result = program()
println("result: " + result)
Expand Down
17 changes: 6 additions & 11 deletions library/src/scala/annotation/MainAnnotation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,14 @@ package scala.annotation
* }
* }
* ```
*
* @param Parser The class used for argument string parsing and arguments into a `T`
* @param Result The required result type of the main method.
* If this type is Any or Unit, any type will be accepted.
*/
@experimental
trait MainAnnotation extends StaticAnnotation:
import MainAnnotation.*

/** The class used for argument string parsing and arguments into a `T` */
type Parser[T]

/** The required result type of the main method.
*
* If this type is Any or Unit, any type will be accepted.
*/
type Result
trait MainAnnotation[Parser[_], Result] extends StaticAnnotation:
import MainAnnotation.{Command, CommandInfo}

/** A new command with arguments from `args`
*
Expand Down
19 changes: 7 additions & 12 deletions tests/run/main-annotation-example.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import scala.annotation.*
import collection.mutable
import scala.util.CommandLineParser.FromString

/** Sum all the numbers
*
Expand All @@ -20,17 +21,11 @@ object Test:
end Test

@experimental
class myMain extends MainAnnotation:
class myMain extends MainAnnotation[FromString, Int]:
import MainAnnotation.{ Command, CommandInfo, ParameterInfo }

// Parser used to parse command line arguments
type Parser[T] = util.CommandLineParser.FromString[T]

// Result type of the annotated method
type Result = Int

/** A new command with arguments from `args` */
def command(info: CommandInfo, args: Array[String]): Command[Parser, Result] =
def command(info: CommandInfo, args: Array[String]): Command[FromString, Int] =
if args.contains("--help") then
println(info.documentation)
System.exit(0)
Expand All @@ -47,15 +42,15 @@ class myMain extends MainAnnotation:
new MyCommand(plainArgs, varargs)

@experimental
class MyCommand(plainArgs: Seq[String], varargs: Seq[String]) extends Command[util.CommandLineParser.FromString, Int]:
class MyCommand(plainArgs: Seq[String], varargs: Seq[String]) extends Command[FromString, Int]:

def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using parser: Parser[T]): () => T =
def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using parser: FromString[T]): () => T =
() => parser.fromString(plainArgs(idx))

def varargGetter[T](using parser: Parser[T]): () => Seq[T] =
def varargGetter[T](using parser: FromString[T]): () => Seq[T] =
() => varargs.map(arg => parser.fromString(arg))

def run(program: () => Result): Unit =
def run(program: () => Int): Unit =
println("executing program")
val result = program()
println("result: " + result)
Expand Down
16 changes: 7 additions & 9 deletions tests/run/main-annotation-homemade-annot-1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import scala.annotation.*
import scala.collection.mutable
import ExecutionContext.Implicits.global
import duration._
import util.CommandLineParser.FromString

@mainAwait def get(wait: Int): Future[Int] = Future{
Thread.sleep(1000 * wait)
Expand All @@ -28,20 +29,17 @@ object Test:
end Test

@experimental
class mainAwait(timeout: Int = 2) extends MainAnnotation:
class mainAwait(timeout: Int = 2) extends MainAnnotation[FromString, Future[Any]]:
import MainAnnotation.*

override type Parser[T] = util.CommandLineParser.FromString[T]
override type Result = Future[Any]

// This is a toy example, it only works with positional args
def command(info: CommandInfo, args: Array[String]): Command[Parser, Result] =
new Command[Parser, Result]:
override def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using p: Parser[T]): () => T =
def command(info: CommandInfo, args: Array[String]): Command[FromString, Future[Any]] =
new Command[FromString, Future[Any]]:
override def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using p: FromString[T]): () => T =
() => p.fromString(args(idx))

override def varargGetter[T](using p: Parser[T]): () => Seq[T] =
override def varargGetter[T](using p: FromString[T]): () => Seq[T] =
() => for i <- ((info.parameters.length-1) until args.length) yield p.fromString(args(i))

override def run(f: () => Result): Unit = println(Await.result(f(), Duration(timeout, SECONDS)))
override def run(f: () => Future[Any]): Unit = println(Await.result(f(), Duration(timeout, SECONDS)))
end mainAwait
16 changes: 7 additions & 9 deletions tests/run/main-annotation-homemade-annot-2.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import scala.collection.mutable
import scala.annotation.*
import util.CommandLineParser.FromString

@myMain()("A")
def foo1(): Unit = println("I was run!")
Expand Down Expand Up @@ -28,22 +29,19 @@ end Test

// This is a toy example, it only works with positional args
@experimental
class myMain(runs: Int = 3)(after: String*) extends MainAnnotation:
class myMain(runs: Int = 3)(after: String*) extends MainAnnotation[FromString, Any]:
import MainAnnotation.*

override type Parser[T] = util.CommandLineParser.FromString[T]
override type Result = Any
def command(info: CommandInfo, args: Array[String]): Command[FromString, Any] =
new Command[FromString, Any]:

def command(info: CommandInfo, args: Array[String]): Command[Parser, Result] =
new Command[Parser, Result]:

override def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using p: Parser[T]): () => T =
override def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using p: FromString[T]): () => T =
() => p.fromString(args(idx))

override def varargGetter[T](using p: Parser[T]): () => Seq[T] =
override def varargGetter[T](using p: FromString[T]): () => Seq[T] =
() => for i <- (info.parameters.length until args.length) yield p.fromString(args(i))

override def run(f: () => Result): Unit =
override def run(f: () => Any): Unit =
for (_ <- 1 to runs)
f()
if after.length > 0 then println(after.mkString(", "))
Expand Down
16 changes: 7 additions & 9 deletions tests/run/main-annotation-homemade-annot-3.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import scala.annotation.*
import scala.util.CommandLineParser.FromString

@mainNoArgs def foo() = println("Hello world!")

Expand All @@ -10,17 +11,14 @@ object Test:
end Test

@experimental
class mainNoArgs extends MainAnnotation:
class mainNoArgs extends MainAnnotation[FromString, Any]:
import MainAnnotation.*

override type Parser[T] = util.CommandLineParser.FromString[T]
override type Result = Any
def command(info: CommandInfo, args: Array[String]): Command[FromString, Any] =
new Command[FromString, Any]:
override def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = ???

def command(info: CommandInfo, args: Array[String]): Command[Parser, Result] =
new Command[Parser, Result]:
override def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using p: Parser[T]): () => T = ???
override def varargGetter[T](using p: FromString[T]): () => Seq[T] = ???

override def varargGetter[T](using p: Parser[T]): () => Seq[T] = ???

override def run(program: () => Result): Unit = program()
override def run(program: () => Any): Unit = program()
end command
16 changes: 7 additions & 9 deletions tests/run/main-annotation-homemade-annot-4.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import scala.annotation.*
import scala.util.CommandLineParser.FromString

@mainManyArgs(1, "B", 3) def foo() = println("Hello world!")

Expand All @@ -10,17 +11,14 @@ object Test:
end Test

@experimental
class mainManyArgs(i1: Int, s2: String, i3: Int) extends MainAnnotation:
class mainManyArgs(i1: Int, s2: String, i3: Int) extends MainAnnotation[FromString, Any]:
import MainAnnotation.*

override type Parser[T] = util.CommandLineParser.FromString[T]
override type Result = Any
def command(info: CommandInfo, args: Array[String]): Command[FromString, Any] =
new Command[FromString, Any]:
override def argGetter[T](idx: Int, optDefaultGetter: Option[() => T])(using p: FromString[T]): () => T = ???

def command(info: CommandInfo, args: Array[String]): Command[Parser, Result] =
new Command[Parser, Result]:
override def argGetter[T](idx: Int, optDefaultGetter: Option[() => T])(using p: Parser[T]): () => T = ???
override def varargGetter[T](using p: FromString[T]): () => Seq[T] = ???

override def varargGetter[T](using p: Parser[T]): () => Seq[T] = ???

override def run(program: () => Result): Unit = program()
override def run(program: () => Any): Unit = program()
end command
16 changes: 7 additions & 9 deletions tests/run/main-annotation-homemade-annot-5.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import scala.annotation.*
import scala.util.CommandLineParser.FromString

@mainManyArgs(Some(1)) def foo() = println("Hello world!")
@mainManyArgs(None) def bar() = println("Hello world!")
Expand All @@ -12,17 +13,14 @@ object Test:
end Test

@experimental
class mainManyArgs(o: Option[Int]) extends MainAnnotation:
class mainManyArgs(o: Option[Int]) extends MainAnnotation[FromString, Any]:
import MainAnnotation.*

override type Parser[T] = util.CommandLineParser.FromString[T]
override type Result = Any
def command(info: CommandInfo, args: Array[String]): Command[FromString, Any] =
new Command[FromString, Any]:
override def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = ???

def command(info: CommandInfo, args: Array[String]): Command[Parser, Result] =
new Command[Parser, Result]:
override def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using p: Parser[T]): () => T = ???
override def varargGetter[T](using p: FromString[T]): () => Seq[T] = ???

override def varargGetter[T](using p: Parser[T]): () => Seq[T] = ???

override def run(program: () => Result): Unit = program()
override def run(program: () => Any): Unit = program()
end command
15 changes: 6 additions & 9 deletions tests/run/main-annotation-homemade-annot-6.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,10 @@ object Test:
end Test

@experimental
class myMain extends MainAnnotation:
class myMain extends MainAnnotation[Make, Any]:
import MainAnnotation.*

override type Parser[T] = Make[T]
override type Result = Any

def command(info: CommandInfo, args: Array[String]): Command[Parser, Result] =
def command(info: CommandInfo, args: Array[String]): Command[Make, Any] =
def paramInfoString(paramInfo: ParameterInfo) =
import paramInfo.*
s" ParameterInfo(name=\"$name\", typeName=\"$typeName\", hasDefault=$hasDefault, isVarargs=$isVarargs, documentation=\"$documentation\", annotations=$annotations)"
Expand All @@ -34,16 +31,16 @@ class myMain extends MainAnnotation:
| "${info.documentation}",
| ${info.parameters.map(paramInfoString).mkString("Seq(\n", ",\n", "\n )*")}
|)""".stripMargin)
new Command[Parser, Result]:
override def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using p: Parser[T]): () => T =
new Command[Make, Any]:
override def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using p: Make[T]): () => T =
println(s"argGetter($idx, ${defaultArgument.map(_())})")
() => p.make

override def varargGetter[T](using p: Parser[T]): () => Seq[T] =
override def varargGetter[T](using p: Make[T]): () => Seq[T] =
println("varargGetter()")
() => Seq(p.make, p.make)

override def run(f: () => Result): Unit =
override def run(f: () => Any): Unit =
println("run()")
f()
println()
Expand Down
18 changes: 8 additions & 10 deletions tests/run/main-annotation-newMain.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import scala.annotation.*
import collection.mutable
import scala.util.CommandLineParser.FromString

@newMain def happyBirthday(age: Int, name: String, others: String*) =
val suffix =
Expand Down Expand Up @@ -29,15 +30,12 @@ end Test


@experimental
final class newMain extends MainAnnotation:
final class newMain extends MainAnnotation[FromString, Any]:
import newMain._
import MainAnnotation._

override type Parser[T] = util.CommandLineParser.FromString[T]
override type Result = Any

def command(info: CommandInfo, args: Array[String]): Command[Parser, Result] =
new Command[Parser, Result]:
def command(info: CommandInfo, args: Array[String]): Command[FromString, Any] =
new Command[FromString, Any]:

private inline val argMarker = "--"
private inline val shortArgMarker = "-"
Expand Down Expand Up @@ -133,7 +131,7 @@ final class newMain extends MainAnnotation:
case s => argMarker + s
}

private def convert[T](argName: String, arg: String)(using p: Parser[T]): () => T =
private def convert[T](argName: String, arg: String)(using p: FromString[T]): () => T =
p.fromStringOption(arg) match
case Some(t) => () => t
case None => error(s"invalid argument for $argName: $arg")
Expand Down Expand Up @@ -233,7 +231,7 @@ final class newMain extends MainAnnotation:
private def getInvalidNames(paramInfos: ParameterInfo): Seq[String | Char] =
getAliases(paramInfos).filter(name => !nameIsValid(name) && !shortNameIsValid(name))

override def argGetter[T](idx: Int, optDefaultGetter: Option[() => T])(using p: Parser[T]): () => T =
override def argGetter[T](idx: Int, optDefaultGetter: Option[() => T])(using p: FromString[T]): () => T =
val name = info.parameters(idx).name
val parameterInfo = nameToParameterInfo(name)
// TODO: Decide which string is associated with this arg when constructing the command.
Expand All @@ -259,7 +257,7 @@ final class newMain extends MainAnnotation:
}
end argGetter

override def varargGetter[T](using p: Parser[T]): () => Seq[T] =
override def varargGetter[T](using p: FromString[T]): () => Seq[T] =
val name = info.parameters.last.name
// TODO: Decide which strings are associated with the varargs when constructing the command.
// Here we should only get the strings for this argument, apply them to the parser and handle parsing errors.
Expand All @@ -269,7 +267,7 @@ final class newMain extends MainAnnotation:
// First take arguments passed by name, then those passed by position
() => (byNameGetters ++ positionalGetters).map(_())

override def run(f: () => Result): Unit =
override def run(f: () => Any): Unit =
// Check aliases unicity
val nameAndCanonicalName = nameToParameterInfo.toList.flatMap {
case (canonicalName, infos) => (canonicalName +: getAlternativeNames(infos) ++: getShortNames(infos)).map(_ -> canonicalName)
Expand Down

0 comments on commit 9bd6708

Please sign in to comment.