diff options
author | Adriaan Moors <adriaan.moors@typesafe.com> | 2013-06-24 13:55:02 -0700 |
---|---|---|
committer | Adriaan Moors <adriaan.moors@typesafe.com> | 2013-06-25 08:56:54 -0700 |
commit | d863c7152cf8c363b4f9c086c62d28b37baf07ea (patch) | |
tree | aa4199b059aa7632f268ab41fc3dac42507f8489 /src/compiler/scala/tools/cmd/CommandLineParser.scala | |
parent | 2a17db002758379fe0b9ee2a4e41ac9e3ca6c30d (diff) | |
download | scala-d863c7152cf8c363b4f9c086c62d28b37baf07ea.tar.gz scala-d863c7152cf8c363b4f9c086c62d28b37baf07ea.tar.bz2 scala-d863c7152cf8c363b4f9c086c62d28b37baf07ea.zip |
Remove dependency on combinators from CommandLinerParser.
tools.cmd.CommandLineParser uses a small hand-rolled parser
TODO: replace partest's usage of scala.tools.nsc.util.CommandLine
by scala.tools.cmd.CommandLine
Diffstat (limited to 'src/compiler/scala/tools/cmd/CommandLineParser.scala')
-rw-r--r-- | src/compiler/scala/tools/cmd/CommandLineParser.scala | 72 |
1 files changed, 72 insertions, 0 deletions
diff --git a/src/compiler/scala/tools/cmd/CommandLineParser.scala b/src/compiler/scala/tools/cmd/CommandLineParser.scala new file mode 100644 index 0000000000..ef55178594 --- /dev/null +++ b/src/compiler/scala/tools/cmd/CommandLineParser.scala @@ -0,0 +1,72 @@ +/* NEST (New Scala Test) + * Copyright 2007-2013 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package cmd + +import scala.annotation.tailrec + +/** 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('\'') + 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("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 + } + } +} |