diff --git a/compiler/src/dotty/tools/dotc/config/CommandLineParser.scala b/compiler/src/dotty/tools/dotc/config/CommandLineParser.scala new file mode 100644 index 000000000000..2298b8b209cb --- /dev/null +++ b/compiler/src/dotty/tools/dotc/config/CommandLineParser.scala @@ -0,0 +1,68 @@ +package dotty.tools.dotc +package config + +import scala.annotation.tailrec +import dotty.tools.sharable + +/** A simple (overly so) command line parser. + * !!! This needs a thorough test suite to make sure quoting is + * done correctly and portably. + */ +object CommandLineParser { + // splits a string into a quoted prefix and the rest of the string, + // taking escaping into account (using \) + // `"abc"def` will match as `DoubleQuoted(abc, def)` + private class QuotedExtractor(quote: Char) { + def unapply(in: String): Option[(String, String)] = { + val del = quote.toString + if (in startsWith del) { + var escaped = false + val (quoted, next) = (in substring 1) span { + case `quote` if !escaped => false + case '\\' if !escaped => escaped = true; true + case _ => escaped = false; true + } + // the only way to get out of the above loop is with an empty next or !escaped + // require(next.isEmpty || !escaped) + if (next startsWith del) Some((quoted, next substring 1)) + else None + } else None + } + } + private object DoubleQuoted extends QuotedExtractor('"') + private object SingleQuoted extends QuotedExtractor('\'') + @sharable private val Word = """(\S+)(.*)""".r + + // parse `in` for an argument, return it and the remainder of the input (or an error message) + // (argument may be in single/double quotes, taking escaping into account, quotes are stripped) + private def argument(in: String): Either[String, (String, String)] = in match { + case DoubleQuoted(arg, rest) => Right((arg, rest)) + case SingleQuoted(arg, rest) => Right((arg, rest)) + case Word(arg, rest) => Right((arg, rest)) + case _ => Left(s"Illegal argument: $in") + } + + // parse a list of whitespace-separated arguments (ignoring whitespace in quoted arguments) + @tailrec private def commandLine(in: String, accum: List[String] = Nil): Either[String, (List[String], String)] = { + val trimmed = in.trim + if (trimmed.isEmpty) Right((accum.reverse, "")) + else argument(trimmed) match { + case Right((arg, next)) => + (next span Character.isWhitespace) match { + case("", rest) if rest.nonEmpty => Left("Arguments should be separated by whitespace.") // TODO: can this happen? + case(ws, rest) => commandLine(rest, arg :: accum) + } + case Left(msg) => Left(msg) + } + } + + class ParseException(msg: String) extends RuntimeException(msg) + + def tokenize(line: String): List[String] = tokenize(line, x => throw new ParseException(x)) + def tokenize(line: String, errorFn: String => Unit): List[String] = { + commandLine(line) match { + case Right((args, _)) => args + case Left(msg) => errorFn(msg) ; Nil + } + } +} diff --git a/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala b/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala index ad6734675e98..6158c83c4958 100644 --- a/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala +++ b/compiler/src/dotty/tools/dotc/config/CompilerCommand.scala @@ -1,7 +1,7 @@ package dotty.tools.dotc package config -import java.io.File +import java.nio.file.{Files, Paths} import Settings._ import core.Contexts._ import util.DotClass @@ -37,14 +37,18 @@ object CompilerCommand extends DotClass { * Expands all arguments starting with @ to the contents of the * file named like each argument. */ - def expandArg(arg: String): List[String] = unsupported("expandArg")/*{ + def expandArg(arg: String): List[String] = { def stripComment(s: String) = s takeWhile (_ != '#') - val file = File(arg stripPrefix "@") - if (!file.exists) - throw new java.io.FileNotFoundException("argument file %s could not be found" format file.name) + val path = Paths.get(arg stripPrefix "@") + if (!Files.exists(path)) + throw new java.io.FileNotFoundException("argument file %s could not be found" format path.getFileName) - settings splitParams (file.lines() map stripComment mkString " ") - }*/ + import scala.collection.JavaConversions._ + val lines = Files.readAllLines(path) // default to UTF-8 encoding + + val params = lines map stripComment mkString " " + CommandLineParser.tokenize(params) + } // expand out @filename to the contents of that filename def expandedArguments = args.toList flatMap { diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index a9d0d7e17718..20d5d61b3701 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -208,7 +208,8 @@ class CompilationTests extends ParallelTesting { defaultOutputDir + "lib/src/:" + // as well as bootstrapped compiler: defaultOutputDir + "dotty1/dotty/:" + - Jars.dottyInterfaces + Jars.dottyInterfaces, + "-Ycheck-reentrant" ) def lib =