Skip to content

Commit 25aa624

Browse files
committed
Refactor CliCommand, Scaladoc arguments parsing and add some tests
1 parent a69b565 commit 25aa624

File tree

20 files changed

+268
-169
lines changed

20 files changed

+268
-169
lines changed

compiler/src/dotty/tools/dotc/Driver.scala

+13-6
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,24 @@ class Driver {
6464

6565
protected def sourcesRequired: Boolean = true
6666

67-
def setup(args: Array[String], rootCtx: Context): (List[AbstractFile], Context) = {
67+
protected def command: CompilerCommand = ScalacCommand
68+
69+
def setup(args: Array[String], rootCtx: Context): (Option[List[AbstractFile]], Context) = {
6870
val ictx = rootCtx.fresh
69-
val summary = CompilerCommand.distill(args, config.ScalaSettings(), ictx.settingsState)
71+
val settings = config.ScalaSettings()
72+
val summary = command.distill(args, settings, settings.defaultState)
7073
ictx.setSettings(summary.sstate)
7174
MacroClassLoader.init(ictx)
7275
Positioned.init(using ictx)
7376

7477
inContext(ictx) {
7578
if !ctx.settings.YdropComments.value || ctx.mode.is(Mode.ReadComments) then
7679
ictx.setProperty(ContextDoc, new ContextDocstrings)
77-
val fileNames = CompilerCommand.checkUsage(summary, sourcesRequired)(using ctx.settings)(using ctx.settingsState)
78-
val files = fileNames.map(ctx.getFile)
79-
(files, fromTastySetup(files))
80+
val fileNamesOrNone = command.checkUsage(summary, sourcesRequired)(using ctx.settings)(using ctx.settingsState)
81+
fileNamesOrNone.fold((None, ictx)) { fileNames =>
82+
val files = fileNames.map(ctx.getFile)
83+
(Some(files), fromTastySetup(files))
84+
}
8085
}
8186
}
8287

@@ -183,7 +188,9 @@ class Driver {
183188
*/
184189
def process(args: Array[String], rootCtx: Context): Reporter = {
185190
val (files, compileCtx) = setup(args, rootCtx)
186-
doCompile(newCompiler(using compileCtx), files)(using compileCtx)
191+
files.fold(compileCtx.reporter) {
192+
doCompile(newCompiler(using compileCtx), _)(using compileCtx)
193+
}
187194
}
188195

189196
def main(args: Array[String]): Unit = {

compiler/src/dotty/tools/dotc/Resident.scala

+15-10
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,21 @@ class Resident extends Driver {
4040

4141
final override def process(args: Array[String], rootCtx: Context): Reporter = {
4242
@tailrec def loop(args: Array[String], prevCtx: Context): Reporter = {
43-
var (files, ctx) = setup(args, prevCtx)
44-
inContext(ctx) { doCompile(residentCompiler, files) }
45-
var nextCtx = ctx
46-
var line = getLine()
47-
while (line == reset) {
48-
nextCtx = rootCtx
49-
line = getLine()
50-
}
51-
if (line.startsWith(quit)) ctx.reporter
52-
else loop(line split "\\s+", nextCtx)
43+
var (possibleFiles, ctx) = setup(args, prevCtx)
44+
if possibleFiles.isDefined then
45+
inContext(ctx) {
46+
doCompile(residentCompiler, possibleFiles.get) // using more complex constructs like fold or map instead of get will make @tailrec complain
47+
}
48+
var nextCtx = ctx
49+
var line = getLine()
50+
while (line == reset) {
51+
nextCtx = rootCtx
52+
line = getLine()
53+
}
54+
if (line.startsWith(quit)) ctx.reporter
55+
else loop(line split "\\s+", nextCtx)
56+
else
57+
ctx.reporter
5358
}
5459
loop(args, rootCtx)
5560
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package dotty.tools.dotc
2+
3+
import config.Properties._
4+
import config.CompilerCommand
5+
6+
object ScalacCommand extends CompilerCommand:
7+
override def cmdName: String = "scalac"
8+
override def versionMsg: String = s"Scala compiler $versionString -- $copyrightString"
9+
override def ifErrorsMsg: String = " scalac -help gives more information"

compiler/src/dotty/tools/dotc/config/CliCommand.scala

+36-30
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,17 @@ trait CliCommand:
1313

1414
type ConcreteSettings <: CommonScalaSettings with Settings.SettingGroup
1515

16+
def versionMsg: String
17+
18+
def ifErrorsMsg: String
19+
1620
/** The name of the command */
1721
def cmdName: String
1822

23+
def isHelpFlag(using settings: ConcreteSettings)(using SettingsState): Boolean
24+
25+
def helpMsg(using settings: ConcreteSettings)(using SettingsState, Context): String
26+
1927
private def explainAdvanced = """
2028
|-- Notes on option parsing --
2129
|Boolean settings are always false unless set.
@@ -31,14 +39,6 @@ trait CliCommand:
3139
| already are in phase X + 1.
3240
"""
3341

34-
def shortUsage: String = s"Usage: $cmdName <options> <source files>"
35-
36-
def versionMsg: String = s"Scala $versionString -- $copyrightString"
37-
38-
def ifErrorsMsg: String = " -help gives more information"
39-
40-
def shouldStopWithInfo(using settings: ConcreteSettings)(using SettingsState): Boolean
41-
4242
/** Distill arguments into summary detailing settings, errors and files to main */
4343
def distill(args: Array[String], sg: Settings.SettingGroup, ss: SettingsState): ArgsSummary =
4444
/**
@@ -64,11 +64,8 @@ trait CliCommand:
6464

6565
sg.processArguments(expandedArguments, ss, processAll = true)
6666

67-
68-
def infoMessage(using settings: ConcreteSettings)(using SettingsState)(using Context): String
69-
7067
/** Creates a help message for a subset of options based on cond */
71-
def availableOptionsMsg(cond: Setting[?] => Boolean)(using settings: ConcreteSettings)(using SettingsState): String =
68+
protected def availableOptionsMsg(cond: Setting[?] => Boolean)(using settings: ConcreteSettings)(using SettingsState): String =
7269
val ss = (settings.allSettings filter cond).toList sortBy (_.name)
7370
val width = (ss map (_.name.length)).max
7471
def format(s: String) = ("%-" + width + "s") format s
@@ -90,8 +87,9 @@ trait CliCommand:
9087

9188
ss.map(helpStr).mkString("", "\n", s"\n${format("@<file>")} A text file containing compiler arguments (options and source files).\n")
9289

90+
protected def shortUsage: String = s"Usage: $cmdName <options> <source files>"
9391

94-
def createUsageMsg(label: String, shouldExplain: Boolean, cond: Setting[?] => Boolean)(using settings: ConcreteSettings)(using SettingsState): String =
92+
protected def createUsageMsg(label: String, shouldExplain: Boolean, cond: Setting[?] => Boolean)(using settings: ConcreteSettings)(using SettingsState): String =
9593
val prefix = List(
9694
Some(shortUsage),
9795
Some(explainAdvanced) filter (_ => shouldExplain),
@@ -100,42 +98,50 @@ trait CliCommand:
10098

10199
prefix + "\n" + availableOptionsMsg(cond)
102100

103-
def isStandard(s: Setting[?])(using settings: ConcreteSettings)(using SettingsState): Boolean = !isAdvanced(s) && !isPrivate(s)
104-
def isAdvanced(s: Setting[?])(using settings: ConcreteSettings)(using SettingsState): Boolean = s.name.startsWith("-X") && s.name != "-X"
105-
def isPrivate(s: Setting[?])(using settings: ConcreteSettings)(using SettingsState): Boolean = s.name.startsWith("-Y") && s.name != "-Y"
101+
protected def isStandard(s: Setting[?])(using settings: ConcreteSettings)(using SettingsState): Boolean =
102+
!isAdvanced(s) && !isPrivate(s)
103+
protected def isAdvanced(s: Setting[?])(using settings: ConcreteSettings)(using SettingsState): Boolean =
104+
s.name.startsWith("-X") && s.name != "-X"
105+
protected def isPrivate(s: Setting[?])(using settings: ConcreteSettings)(using SettingsState): Boolean =
106+
s.name.startsWith("-Y") && s.name != "-Y"
106107

107108
/** Messages explaining usage and options */
108-
def usageMessage(using settings: ConcreteSettings)(using SettingsState) = createUsageMsg("where possible standard", shouldExplain = false, isStandard)
109-
def xusageMessage(using settings: ConcreteSettings)(using SettingsState) = createUsageMsg("Possible advanced", shouldExplain = true, isAdvanced)
110-
def yusageMessage(using settings: ConcreteSettings)(using SettingsState) = createUsageMsg("Possible private", shouldExplain = true, isPrivate)
111-
112-
def phasesMessage: String =
109+
protected def usageMessage(using settings: ConcreteSettings)(using SettingsState) =
110+
createUsageMsg("where possible standard", shouldExplain = false, isStandard)
111+
protected def xusageMessage(using settings: ConcreteSettings)(using SettingsState) =
112+
createUsageMsg("Possible advanced", shouldExplain = true, isAdvanced)
113+
protected def yusageMessage(using settings: ConcreteSettings)(using SettingsState) =
114+
createUsageMsg("Possible private", shouldExplain = true, isPrivate)
115+
116+
protected def phasesMessage: String =
113117
(new Compiler()).phases.map {
114118
case List(single) => single.phaseName
115119
case more => more.map(_.phaseName).mkString("{", ", ", "}")
116120
}.mkString("\n")
117121

118122
/** Provide usage feedback on argument summary, assuming that all settings
119123
* are already applied in context.
120-
* @return The list of files passed as arguments.
124+
* @return Either Some list of files passed as arguments or None if further processing should be interrupted.
121125
*/
122-
def checkUsage(summary: ArgsSummary, sourcesRequired: Boolean)(using settings: ConcreteSettings)(using SettingsState)(using Context): List[String] =
126+
def checkUsage(summary: ArgsSummary, sourcesRequired: Boolean)(using settings: ConcreteSettings)(using SettingsState, Context): Option[List[String]] =
123127
// Print all warnings encountered during arguments parsing
124128
summary.warnings.foreach(report.warning(_))
125129

126130
if summary.errors.nonEmpty then
127131
summary.errors foreach (report.error(_))
128132
report.echo(ifErrorsMsg)
129-
Nil
133+
None
130134
else if settings.version.value then
131135
report.echo(versionMsg)
132-
Nil
133-
else if shouldStopWithInfo then
134-
report.echo(infoMessage)
135-
Nil
136+
None
137+
else if isHelpFlag then
138+
report.echo(helpMsg)
139+
None
140+
else if (sourcesRequired && summary.arguments.isEmpty)
141+
report.echo(usageMessage)
142+
None
136143
else
137-
if (sourcesRequired && summary.arguments.isEmpty) report.echo(usageMessage)
138-
summary.arguments
144+
Some(summary.arguments)
139145

140146
extension [T](setting: Setting[T])
141147
protected def value(using ss: SettingsState): T = setting.valueIn(ss)

compiler/src/dotty/tools/dotc/config/CompilerCommand.scala

+4-7
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,16 @@ import Properties._
99

1010
import scala.collection.JavaConverters._
1111

12-
object CompilerCommand extends CliCommand:
13-
type ConcreteSettings = ScalaSettings
14-
override def cmdName: String = "scalac"
15-
override def versionMsg: String = s"Scala compiler $versionString -- $copyrightString"
16-
override def ifErrorsMsg: String = " scalac -help gives more information"
12+
abstract class CompilerCommand extends CliCommand:
13+
final type ConcreteSettings = ScalaSettings
1714

18-
def infoMessage(using settings: ScalaSettings)(using SettingsState)(using Context): String =
15+
final def helpMsg(using settings: ScalaSettings)(using SettingsState, Context): String =
1916
if (settings.help.value) usageMessage
2017
else if (settings.Xhelp.value) xusageMessage
2118
else if (settings.Yhelp.value) yusageMessage
2219
else if (settings.showPlugins.value) ctx.base.pluginDescriptions
2320
else if (settings.XshowPhases.value) phasesMessage
2421
else ""
2522

26-
def shouldStopWithInfo(using settings: ScalaSettings)(using SettingsState): Boolean =
23+
final def isHelpFlag(using settings: ScalaSettings)(using SettingsState): Boolean =
2724
Set(settings.help, settings.Xhelp, settings.Yhelp, settings.showPlugins, settings.XshowPhases) exists (_.value)

compiler/src/dotty/tools/dotc/decompiler/Main.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ object Main extends dotc.Driver {
1818
new TASTYDecompiler
1919
}
2020

21-
override def setup(args0: Array[String], rootCtx: Context): (List[AbstractFile], Context) = {
21+
override def setup(args0: Array[String], rootCtx: Context): (Option[List[AbstractFile]], Context) = {
2222
var args = args0.filter(a => a != "-decompile")
2323
if (!args.contains("-from-tasty")) args = "-from-tasty" +: args
2424
if (args.contains("-d")) args = "-color:never" +: args

compiler/src/dotty/tools/repl/Main.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ package dotty.tools.repl
33
/** Main entry point to the REPL */
44
object Main {
55
def main(args: Array[String]): Unit =
6-
new ReplDriver(args).runUntilQuit()
6+
new ReplDriver(args).tryRunning
77
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package dotty.tools.repl
2+
3+
import dotty.tools.dotc.config.Properties._
4+
import dotty.tools.dotc.config.CompilerCommand
5+
6+
object ReplCommand extends CompilerCommand:
7+
override def cmdName: String = "scala"
8+
override def versionMsg: String = s"Scala code runner $versionString -- $copyrightString"
9+
override def ifErrorsMsg: String = " scala -help gives more information"

compiler/src/dotty/tools/repl/ReplDriver.scala

+14-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import dotty.tools.dotc.reporting.{Message, Diagnostic}
2222
import dotty.tools.dotc.util.Spans.Span
2323
import dotty.tools.dotc.util.{SourceFile, SourcePosition}
2424
import dotty.tools.dotc.{CompilationUnit, Driver}
25+
import dotty.tools.dotc.config.CompilerCommand
2526
import dotty.tools.io._
2627
import org.jline.reader._
2728

@@ -67,7 +68,8 @@ class ReplDriver(settings: Array[String],
6768
private def initialCtx = {
6869
val rootCtx = initCtx.fresh.addMode(Mode.ReadPositions | Mode.Interactive | Mode.ReadComments)
6970
rootCtx.setSetting(rootCtx.settings.YcookComments, true)
70-
val ictx = setup(settings, rootCtx)._2
71+
val (files, ictx) = setup(settings, rootCtx)
72+
shouldStart = files.isDefined
7173
ictx.base.initialize()(using ictx)
7274
ictx
7375
}
@@ -91,13 +93,24 @@ class ReplDriver(settings: Array[String],
9193
}
9294

9395
private var rootCtx: Context = _
96+
private var shouldStart: Boolean = _
9497
private var compiler: ReplCompiler = _
9598
private var rendering: Rendering = _
9699

97100
// initialize the REPL session as part of the constructor so that once `run`
98101
// is called, we're in business
99102
resetToInitial()
100103

104+
override protected def command: CompilerCommand = ReplCommand
105+
106+
/** Try to run REPL if there is nothing that prevents us doing so.
107+
*
108+
* Possible reason for unsuccessful run are raised flags in CLI like --help or --version
109+
*/
110+
final def tryRunning = if shouldStart then
111+
println("Starting scala3 REPL...")
112+
runUntilQuit()
113+
101114
/** Run REPL with `state` until `:quit` command found
102115
*
103116
* This method is the main entry point into the REPL. Its effects are not

compiler/src/dotty/tools/scripting/ScriptingDriver.scala

+24-22
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,30 @@ import sys.process._
1919
class ScriptingDriver(compilerArgs: Array[String], scriptFile: File, scriptArgs: Array[String]) extends Driver:
2020
def compileAndRun(pack:(Path, String, String) => Boolean = null): Unit =
2121
val outDir = Files.createTempDirectory("scala3-scripting")
22-
val (toCompile, rootCtx) = setup(compilerArgs :+ scriptFile.getAbsolutePath, initCtx.fresh)
23-
given Context = rootCtx.fresh.setSetting(rootCtx.settings.outputDir,
24-
new PlainDirectory(Directory(outDir)))
25-
26-
if doCompile(newCompiler, toCompile).hasErrors then
27-
throw ScriptingException("Errors encountered during compilation")
28-
29-
try
30-
val (mainClass, mainMethod) = detectMainClassAndMethod(outDir, ctx.settings.classpath.value, scriptFile)
31-
val invokeMain: Boolean =
32-
Option(pack) match
33-
case Some(func) =>
34-
func(outDir, ctx.settings.classpath.value, mainClass)
35-
case None =>
36-
true
37-
end match
38-
if invokeMain then mainMethod.invoke(null, scriptArgs)
39-
catch
40-
case e: java.lang.reflect.InvocationTargetException =>
41-
throw e.getCause
42-
finally
43-
deleteFile(outDir.toFile)
22+
val (toCompileOrNone, rootCtx) = setup(compilerArgs :+ scriptFile.getAbsolutePath, initCtx.fresh)
23+
toCompileOrNone.map { toCompile =>
24+
given Context = rootCtx.fresh.setSetting(rootCtx.settings.outputDir,
25+
new PlainDirectory(Directory(outDir)))
26+
27+
if doCompile(newCompiler, toCompile).hasErrors then
28+
throw ScriptingException("Errors encountered during compilation")
29+
30+
try
31+
val (mainClass, mainMethod) = detectMainClassAndMethod(outDir, ctx.settings.classpath.value, scriptFile)
32+
val invokeMain: Boolean =
33+
Option(pack) match
34+
case Some(func) =>
35+
func(outDir, ctx.settings.classpath.value, mainClass)
36+
case None =>
37+
true
38+
end match
39+
if invokeMain then mainMethod.invoke(null, scriptArgs)
40+
catch
41+
case e: java.lang.reflect.InvocationTargetException =>
42+
throw e.getCause
43+
finally
44+
deleteFile(outDir.toFile)
45+
}
4446
end compileAndRun
4547

4648
private def deleteFile(target: File): Unit =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package dotty.tools.dotc
2+
3+
import org.junit.Test
4+
import org.junit.Assert._
5+
import org.junit.Rule
6+
import org.junit.rules.TemporaryFolder
7+
import dotty.tools.dotc.config.Settings._
8+
9+
class ScalaCommandTest:
10+
11+
private val _temporaryFolder = new TemporaryFolder
12+
13+
@Rule
14+
def temporaryFolder = _temporaryFolder
15+
16+
@Test def `Simple one parameter`: Unit =
17+
val settings = config.ScalaSettings()
18+
val args = "-cp path/to/classes1:other/path/to/classes2 files".split(" ")
19+
val summary = ScalacCommand.distill(args, settings, settings.defaultState)
20+
given SettingsState = summary.sstate
21+
assertEquals("path/to/classes1:other/path/to/classes2", settings.classpath.value)
22+
assertEquals("files" :: Nil, summary.arguments)
23+
24+
@Test def `Unfold @file`: Unit =
25+
val settings = config.ScalaSettings()
26+
val file = temporaryFolder.newFile("config")
27+
val writer = java.io.FileWriter(file);
28+
writer.write("-sourceroot myNewRoot someMoreFiles");
29+
writer.close();
30+
val args = s"-cp path/to/classes1:other/path/to/classes2 @$file someFiles".split(" ")
31+
val summary = ScalacCommand.distill(args, settings, settings.defaultState)
32+
33+
given SettingsState = summary.sstate
34+
assertEquals("path/to/classes1:other/path/to/classes2", settings.classpath.value)
35+
assertEquals("myNewRoot", settings.sourceroot.value)
36+
assertEquals("someMoreFiles" :: "someFiles" :: Nil, summary.arguments)
37+
38+
extension [T](setting: Setting[T])
39+
private def value(using ss: SettingsState): T = setting.valueIn(ss)

0 commit comments

Comments
 (0)