Skip to content

Commit c85aea3

Browse files
Rework of main.Name and main.ShortName
- Merge Name and ShortName into Alias - Make Alias take a variable number of arguments - Distinguish between long and short names by string length
1 parent e405302 commit c85aea3

12 files changed

+161
-88
lines changed

compiler/src/dotty/tools/dotc/ast/MainProxies.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ object MainProxies {
134134
* * @param ys all my params y
135135
* */
136136
* @main(80) def f(
137-
* @main.ShortName('x') @main.Name("myX") x: S,
137+
* @main.Alias("myX") x: S,
138138
* ys: T*
139139
* ) = ...
140140
*
@@ -148,7 +148,7 @@ object MainProxies {
148148
* "Lorem ipsum dolor sit amet consectetur adipiscing elit.",
149149
* new scala.annotation.MainAnnotation.ParameterInfos("x", "S")
150150
* .withDocumentation("my param x")
151-
* .withAnnotations(new scala.main.ShortName('x'), new scala.main.Name("myX")),
151+
* .withAnnotations(new scala.main.Alias("myX")),
152152
* new scala.annotation.MainAnnotation.ParameterInfos("ys", "T")
153153
* .withDocumentation("all my params y")
154154
* )

library/src/scala/main.scala

Lines changed: 58 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ import annotation._
2727
* The parameters of a main function may be passed either by position, or by name. Passing an argument positionaly
2828
* means that you give the arguments in the same order as the function's signature. Passing an argument by name means
2929
* that you give the argument right after giving its name. Considering the function
30-
* `@main def foo(i: Int, s: String)`, we may have arguments passed:
30+
* `@main def foo(i: Int, str: String)`, we may have arguments passed:
3131
* - by position: `scala foo 1 abc`,
32-
* - by name: `scala foo --i 1 --s abc` or `scala foo --s abc --i 1`.
32+
* - by name: `scala foo -i 1 --str abc` or `scala foo --str abc -i 1`.
3333
*
34-
* A mixture of both is also possible: `scala foo --s abc 1` is equivalent to all previous examples.
34+
* A mixture of both is also possible: `scala foo --str abc 1` is equivalent to all previous examples.
3535
*
3636
* Note that main function overloading is not currently supported, i.e. you cannot define two main methods that have
3737
* the same name in the same project.
@@ -41,18 +41,15 @@ import annotation._
4141
* associated with the function, more precisely its description and the description of the parameters documented with
4242
* `@param`.
4343
*
44-
*
4544
* Parameters may be given annotations to add functionalities to the main function:
46-
* - `main.ShortName` adds a short name to a parameter. For example, if a parameter `node` has as short name `n`, it
47-
* may be addressed using either `--node` or `-n`,
48-
* - `main.Name` adds another name to a parameter. For example, if a parameter `node` has as alternative name
49-
* `otherNode`, it may be addressed using either `--node` or `--otherNode`.
45+
* - `main.Alias` adds other names to a parameter. For example, if a parameter `node` has as aliases
46+
* `otherNode` and `n`, it may be addressed using `--node`, `--otherNode` or `-n`.
5047
*
5148
* Here is an example of a main function with annotated parameters:
52-
* `@main def foo(@main.ShortName('x') number: Int, @main.Name("explanation") s: String)`. The following commands are
49+
* `@main def foo(@main.Alias("x") number: Int, @main.Alias("explanation") s: String)`. The following commands are
5350
* equivalent:
54-
* - `scala foo --number 1 --s abc`
55-
* - `scala foo -x 1 --s abc`
51+
* - `scala foo --number 1 -s abc`
52+
* - `scala foo -x 1 -s abc`
5653
* - `scala foo --number 1 --explanation abc`
5754
* - `scala foo -x 1 --explanation abc`
5855
*
@@ -125,8 +122,20 @@ final class main(maxLineLength: Int) extends MainAnnotation:
125122
private val nameToParameterInfos: Map[String, ParameterInfos] = parameterInfoss.map(infos => infos.name -> infos).toMap
126123

127124
private val (positionalArgs, byNameArgs, invalidByNameArgs) = {
128-
val namesToCanonicalName: Map[String, String] = parameterInfoss.flatMap(infos => (infos.name +: getAlternativeNames(infos)).map(_ -> infos.name)).toMap
129-
val shortNamesToCanonicalName: Map[Char, String] = parameterInfoss.flatMap(infos => getShortNames(infos).map(_ -> infos.name)).toMap
125+
val namesToCanonicalName: Map[String, String] = parameterInfoss.flatMap(
126+
infos =>
127+
var names = getAlternativeNames(infos)
128+
val canonicalName = infos.name
129+
if nameIsValid(canonicalName) then names = canonicalName +: names
130+
names.map(_ -> canonicalName)
131+
).toMap
132+
val shortNamesToCanonicalName: Map[Char, String] = parameterInfoss.flatMap(
133+
infos =>
134+
var names = getShortNames(infos)
135+
val canonicalName = infos.name
136+
if shortNameIsValid(canonicalName) then names = canonicalName(0) +: names
137+
names.map(_ -> canonicalName)
138+
).toMap
130139

131140
def getCanonicalArgName(arg: String): Option[String] =
132141
if arg.startsWith(argMarker) && arg.length > argMarker.length then
@@ -138,7 +147,7 @@ final class main(maxLineLength: Int) extends MainAnnotation:
138147

139148
def isArgName(arg: String): Boolean =
140149
val isFullName = arg.startsWith(argMarker)
141-
val isShortName = arg.startsWith(shortArgMarker) && arg.length == 2 && shortNameIsValid(arg(1))
150+
val isShortName = arg.startsWith(shortArgMarker) && arg.length == shortArgMarker.length + 1 && shortNameIsValid(arg(shortArgMarker.length))
142151
isFullName || isShortName
143152

144153
def recurse(remainingArgs: Seq[String], pa: mutable.Queue[String], bna: Seq[(String, String)], ia: Seq[String]): (mutable.Queue[String], Seq[(String, String)], Seq[String]) =
@@ -170,12 +179,21 @@ final class main(maxLineLength: Int) extends MainAnnotation:
170179
errors += msg
171180
() => throw new AssertionError("trying to get invalid argument")
172181

173-
private def nameIsValid(name: String): Boolean =
174-
name.length > 0 // TODO add more checks for illegal characters
182+
private inline def nameIsValid(name: String): Boolean =
183+
name.length > 1 // TODO add more checks for illegal characters
184+
185+
private inline def shortNameIsValid(name: String): Boolean =
186+
name.length == 1 && shortNameIsValid(name(0))
175187

176-
private def shortNameIsValid(shortName: Char): Boolean =
188+
private inline def shortNameIsValid(shortName: Char): Boolean =
177189
('A' <= shortName && shortName <= 'Z') || ('a' <= shortName && shortName <= 'z')
178190

191+
private def getNameWithMarker(name: String | Char): String = name match {
192+
case c: Char => shortArgMarker + c
193+
case s: String if shortNameIsValid(s) => shortArgMarker + s
194+
case s => argMarker + s
195+
}
196+
179197
private def convert[T](argName: String, arg: String, p: ArgumentParser[T]): () => T =
180198
p.fromStringOption(arg) match
181199
case Some(t) => () => t
@@ -185,9 +203,9 @@ final class main(maxLineLength: Int) extends MainAnnotation:
185203
def argsUsage: Seq[String] =
186204
for ((infos, kind) <- parameterInfoss.zip(argKinds))
187205
yield {
188-
val canonicalName = argMarker + infos.name
189-
val shortNames = getShortNames(infos).map(shortArgMarker + _)
190-
val alternativeNames = getAlternativeNames(infos).map(argMarker + _)
206+
val canonicalName = getNameWithMarker(infos.name)
207+
val shortNames = getShortNames(infos).map(getNameWithMarker)
208+
val alternativeNames = getAlternativeNames(infos).map(getNameWithMarker)
191209
val namesPrint = (canonicalName +: alternativeNames ++: shortNames).mkString("[", " | ", "]")
192210

193211
kind match {
@@ -241,9 +259,9 @@ final class main(maxLineLength: Int) extends MainAnnotation:
241259

242260
println("Arguments:")
243261
for ((infos, kind) <- parameterInfoss.zip(argKinds))
244-
val canonicalName = argMarker + infos.name
245-
val shortNames = getShortNames(infos).map(shortArgMarker + _)
246-
val alternativeNames = getAlternativeNames(infos).map(argMarker + _)
262+
val canonicalName = getNameWithMarker(infos.name)
263+
val shortNames = getShortNames(infos).map(getNameWithMarker)
264+
val alternativeNames = getAlternativeNames(infos).map(getNameWithMarker)
247265
val otherNames = (alternativeNames ++: shortNames) match {
248266
case Seq() => ""
249267
case names => names.mkString("(", ", ", ") ")
@@ -271,28 +289,30 @@ final class main(maxLineLength: Int) extends MainAnnotation:
271289
}
272290
end explain
273291

292+
private def getAliases(paramInfos: ParameterInfos): Seq[String] =
293+
paramInfos.annotations.collect{ case a: Alias => a }.flatMap(_.aliases)
294+
274295
private def getAlternativeNames(paramInfos: ParameterInfos): Seq[String] =
275-
val (valid, invalid) =
276-
paramInfos.annotations.collect{ case annot: Name => annot.name }.partition(nameIsValid)
277-
if invalid.nonEmpty then
278-
throw IllegalArgumentException(s"invalid names ${invalid.mkString(", ")} for parameter ${paramInfos.name}")
279-
valid
296+
getAliases(paramInfos).filter(nameIsValid(_))
280297

281298
private def getShortNames(paramInfos: ParameterInfos): Seq[Char] =
282-
val (valid, invalid) =
283-
paramInfos.annotations.collect{ case annot: ShortName => annot.shortName }.partition(shortNameIsValid)
284-
if invalid.nonEmpty then
285-
throw IllegalArgumentException(s"invalid short names ${invalid.mkString(", ")} for parameter ${paramInfos.name}")
286-
valid
299+
getAliases(paramInfos).filter(shortNameIsValid(_)).map(_(0))
300+
301+
private def getInvalidNames(paramInfos: ParameterInfos): Seq[String | Char] =
302+
getAliases(paramInfos).filter(name => !nameIsValid(name) && !shortNameIsValid(name))
303+
304+
private def checkAliasesValidity(): Unit =
305+
val problematicNames = nameToParameterInfos.toList.flatMap((_, infos) => getInvalidNames(infos))
306+
if problematicNames.length > 0 then throw IllegalArgumentException(s"The following aliases are invalid: ${problematicNames.mkString(", ")}")
287307

288-
private def checkNamesUnicity(): Unit =
308+
private def checkAliasesUnicity(): Unit =
289309
val nameAndCanonicalName = nameToParameterInfos.toList.flatMap {
290310
case (canonicalName, infos) => (canonicalName +: getAlternativeNames(infos) ++: getShortNames(infos)).map(_ -> canonicalName)
291311
}
292312
val nameToCanonicalNames = nameAndCanonicalName.groupMap(_._1)(_._2)
293313

294314
for (name, canonicalNames) <- nameToCanonicalNames if canonicalNames.length > 1
295-
do throw AssertionError(s"$name is used for multiple parameters: ${canonicalNames.mkString(", ")}")
315+
do throw IllegalArgumentException(s"$name is used for multiple parameters: ${canonicalNames.mkString(", ")}")
296316

297317
override def argGetter[T](name: String, optDefaultGetter: Option[() => T])(using p: ArgumentParser[T]): () => T =
298318
argKinds += (if optDefaultGetter.nonEmpty then ArgumentKind.OptionalArgument else ArgumentKind.SimpleArgument)
@@ -327,9 +347,10 @@ final class main(maxLineLength: Int) extends MainAnnotation:
327347
() => (byNameGetters ++ positionalGetters).map(_())
328348

329349
override def run(f: => MainResultType): Unit =
350+
checkAliasesUnicity()
351+
checkAliasesValidity()
330352
for (remainingArg <- positionalArgs) error(s"unused argument: $remainingArg")
331353
for (invalidArg <- invalidByNameArgs) error(s"unknown argument name: $invalidArg")
332-
checkNamesUnicity()
333354

334355
if args.contains(s"${argMarker}help") then
335356
usage()
@@ -345,6 +366,5 @@ final class main(maxLineLength: Int) extends MainAnnotation:
345366
end main
346367

347368
object main:
348-
final class ShortName(val shortName: Char) extends MainAnnotation.ParameterAnnotation
349-
final class Name(val name: String) extends MainAnnotation.ParameterAnnotation
369+
final class Alias(val aliases: String*) extends MainAnnotation.ParameterAnnotation
350370
end main

project/MiMaFilters.scala

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@ object MiMaFilters {
99
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.main.this"),
1010
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.main.command"),
1111
ProblemFilters.exclude[MissingClassProblem]("scala.main$"),
12-
ProblemFilters.exclude[MissingClassProblem]("scala.main$Name"),
13-
ProblemFilters.exclude[MissingClassProblem]("scala.main$Name$"),
14-
ProblemFilters.exclude[MissingClassProblem]("scala.main$ShortName"),
15-
ProblemFilters.exclude[MissingClassProblem]("scala.main$ShortName$"),
12+
ProblemFilters.exclude[MissingClassProblem]("scala.main$Alias"),
1613
ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation"),
1714
ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation$"),
1815
ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation$Command"),

tests/run/main-annotation-help.check

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,9 @@ Arguments:
150150
my first element
151151
--second - MyGeneric[Int]
152152
my second element
153-
Usage: doc17 [--a] <Int> [--b] <Int> [--c] <String>
153+
Usage: doc17 [-a] <Int> [-b] <Int> [-c] <String>
154154

155155
Arguments:
156-
--a - Int
157-
--b - Int
158-
--c - String
156+
-a - Int
157+
-b - Int
158+
-c - String
Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,40 @@
1-
Usage: doc1 [--a] <Int> [--b] <Int> [--c] <String>
1+
Usage: doc1 [-a] <Int> [-b] <Int> [-c] <String>
22

33
Help is printed normally.
44
Arguments:
5-
--a - Int
5+
-a - Int
66
the first argument
7-
--b - Int
7+
-b - Int
88
the second argument
9-
--c - String
9+
-c - String
1010
the third argument. This one is a String
11-
Usage: doc2 [--a] <Int> [--b] <Int>
12-
[--c] <String>
11+
Usage: doc2 [-a] <Int> [-b] <Int>
12+
[-c] <String>
1313

1414
Help is printed in a slightly narrow
1515
column.
1616
Arguments:
17-
--a - Int
17+
-a - Int
1818
the first argument
19-
--b - Int
19+
-b - Int
2020
the second argument
21-
--c - String
21+
-c - String
2222
the third argument. This one is a
2323
String
24-
Usage: doc3 [--a] <Int>
25-
[--b] <Int>
26-
[--c] <String>
24+
Usage: doc3 [-a] <Int>
25+
[-b] <Int>
26+
[-c] <String>
2727

2828
Help is printed in a
2929
very narrow column!
3030
Arguments:
31-
--a - Int
31+
-a - Int
3232
the first
3333
argument
34-
--b - Int
34+
-b - Int
3535
the second
3636
argument
37-
--c - String
37+
-c - String
3838
the third
3939
argument. This
4040
one is a String

tests/run/main-annotation-param-annot-1.check

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,11 @@
2626
2 + 3 = 5
2727
2 + 3 = 5
2828
2 + 3 = 5
29+
2 + 3 = 5
30+
2 + 3 = 5
31+
2 + 3 = 5
32+
2 + 3 = 5
33+
2 + 3 = 5
34+
2 + 3 = 5
35+
2 + 3 = 5
36+
2 + 3 = 5

tests/run/main-annotation-param-annot-1.scala

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
object myProgram:
22
@main def altName1(
3-
@main.Name("myNum") num: Int,
3+
@main.Alias("myNum") num: Int,
44
inc: Int
55
): Unit =
66
println(s"$num + $inc = ${num + inc}")
77

88
@main def altName2(
9-
@main.Name("myNum") num: Int,
10-
@main.Name("myInc") inc: Int
9+
@main.Alias("myNum") num: Int,
10+
@main.Alias("myInc") inc: Int
1111
): Unit =
1212
println(s"$num + $inc = ${num + inc}")
1313

1414
@main def shortName1(
15-
@main.ShortName('n') num: Int,
15+
@main.Alias("n") num: Int,
1616
inc: Int
1717
): Unit =
1818
println(s"$num + $inc = ${num + inc}")
1919

2020
@main def shortName2(
21-
@main.ShortName('n') num: Int,
22-
@main.ShortName('i') inc: Int
21+
@main.Alias("n") num: Int,
22+
@main.Alias("i") inc: Int
2323
): Unit =
2424
println(s"$num + $inc = ${num + inc}")
2525

2626
@main def mix1(
27-
@main.Name("myNum") @main.ShortName('n') num: Int,
28-
@main.ShortName('i') @main.Name("myInc") inc: Int
27+
@main.Alias("myNum") @main.Alias("n") num: Int,
28+
@main.Alias("i") @main.Alias("myInc") inc: Int
2929
): Unit =
3030
println(s"$num + $inc = ${num + inc}")
3131

@@ -35,14 +35,20 @@ object myProgram:
3535
for i <- 0 until 'n' - 'a'
3636
do
3737
short = (short.toInt + 1).toChar
38-
short
38+
short.toString
3939
}
4040
def myInc = {new Exception("myInc")}.getMessage
41-
def myShortInc = () => 'i'
41+
def myShortInc = () => "i"
4242

4343
@main def mix2(
44-
@main.Name(myNum) @main.ShortName(myShortNum) num: Int,
45-
@main.ShortName(myShortInc()) @main.Name(myInc) inc: Int
44+
@main.Alias(myNum) @main.Alias(myShortNum) num: Int,
45+
@main.Alias(myShortInc()) @main.Alias(myInc) inc: Int
46+
): Unit =
47+
println(s"$num + $inc = ${num + inc}")
48+
49+
@main def multiple(
50+
@main.Alias("myNum", "n") num: Int,
51+
@main.Alias("i", "myInc") inc: Int
4652
): Unit =
4753
println(s"$num + $inc = ${num + inc}")
4854
end myProgram
@@ -87,4 +93,13 @@ object Test:
8793
callMain("mix2", Array("-n", "2", "--myInc", "3"))
8894
callMain("mix2", Array("--myNum", "2", "-i", "3"))
8995
callMain("mix2", Array("-n", "2", "-i", "3"))
96+
97+
callMain("multiple", Array("--num", "2", "--inc", "3"))
98+
callMain("multiple", Array("-n", "2", "--inc", "3"))
99+
callMain("multiple", Array("--num", "2", "-i", "3"))
100+
callMain("multiple", Array("-n", "2", "-i", "3"))
101+
callMain("multiple", Array("--myNum", "2", "--myInc", "3"))
102+
callMain("multiple", Array("-n", "2", "--myInc", "3"))
103+
callMain("multiple", Array("--myNum", "2", "-i", "3"))
104+
callMain("multiple", Array("-n", "2", "-i", "3"))
90105
end Test

0 commit comments

Comments
 (0)