diff options
author | Paul Phillips <paulp@improving.org> | 2010-02-22 21:47:41 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2010-02-22 21:47:41 +0000 |
commit | acb161272fae6d1d999d2b302feb0b9a6381b83f (patch) | |
tree | 492ef971602c244e82047cdc8adff6dcb13ebabc /src/compiler | |
parent | 669ce2013ec01091760ab8ed5cd8814471869a96 (diff) | |
download | scala-acb161272fae6d1d999d2b302feb0b9a6381b83f.tar.gz scala-acb161272fae6d1d999d2b302feb0b9a6381b83f.tar.bz2 scala-acb161272fae6d1d999d2b302feb0b9a6381b83f.zip |
A simple command line parser to consolidate a b...
A simple command line parser to consolidate a bunch of different
implementations floating around trunk. And rolled it out in partest's
ConsoleRunner. Review by community.
Diffstat (limited to 'src/compiler')
-rw-r--r-- | src/compiler/scala/tools/nsc/util/CommandLineParser.scala | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/src/compiler/scala/tools/nsc/util/CommandLineParser.scala b/src/compiler/scala/tools/nsc/util/CommandLineParser.scala new file mode 100644 index 0000000000..3197260443 --- /dev/null +++ b/src/compiler/scala/tools/nsc/util/CommandLineParser.scala @@ -0,0 +1,166 @@ +/* NEST (New Scala Test) + * Copyright 2007-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package util + +import scala.util.parsing.combinator._ +import scala.util.parsing.input.{ Reader } +import scala.util.parsing.input.CharArrayReader.EofCh +import scala.collection.mutable.ListBuffer + +/** A simple command line parser to replace the several different + * simple ones spread around trunk. + */ + +trait ParserUtil extends Parsers { + class ParserPlus[+T](underlying: Parser[T]) { + def !~>[U](p: => Parser[U]): Parser[U] = (underlying ~! p) ^^ { case a~b => b } + def <~![U](p: => Parser[U]): Parser[T] = (underlying ~! p) ^^ { case a~b => a } + } + protected implicit def parser2parserPlus[T](p: Parser[T]): ParserPlus[T] = new ParserPlus(p) +} + +class CommandLine(val args: List[String], val unaryArguments: List[String]) { + def this(args: List[String]) = this(args, Nil) + def this(line: String) = this(CommandLineParser tokenize line) + + def withUnaryArguments(xs: List[String]) = new CommandLine(args, xs) + def enforceArity = true + def errorFn(msg: String) = println(msg) + + /** argMap is option -> argument (or "" if it is a unary argument) + * residualArgs are what is left after removing the options and their args. + */ + lazy val (argMap, residualArgs) = { + val residual = new ListBuffer[String] + def isOption(s: String) = s startsWith "-" + def isUnary(s: String) = isOption(s) && (unaryArguments contains s) + def isBinary(s: String) = isOption(s) && !(unaryArguments contains s) + def missingArg(opt: String, what: String) = + errorFn("Option '%s' requires argument, found %s instead.".format(opt, what)) + + def loop(args: List[String]): Map[String, String] = args match { + case Nil => Map() + case x :: xs if !isOption(x) => residual += x ; loop(xs) + case x :: xs if isUnary(x) => Map(x -> "") ++ loop(xs) + case x :: Nil => if (enforceArity) missingArg(x, "EOF") ; Map(x -> "") + case "--" :: xs => residual ++= xs ; Map() + case x :: "--" :: xs => residual ++= xs ; Map(x -> "") + case x1 :: x2 :: xs => + if (enforceArity && isOption(x2)) + missingArg(x1, x2) + + if (isOption(x2)) Map(x1 -> "") ++ loop(x2 :: xs) + else Map(x1 -> x2) ++ loop(xs) + } + + val (unaries, rest) = args partition (unaryArguments contains _) + val map = loop(rest) + + (map ++ Map(unaries map (x => x -> ""): _*), residual.toList) + } + + def isSet(arg: String) = args contains arg + def get(arg: String) = argMap get arg + def apply(arg: String) = argMap(arg) + + override def toString() = "CommandLine(\n%s)\n" format (args map (" " + _ + "\n") mkString) +} + +object CommandLineParser extends RegexParsers with ParserUtil { + override def skipWhitespace = false + + def elemExcept(xs: Elem*): Parser[Elem] = elem("elemExcept", x => x != EofCh && !(xs contains x)) + def elemOf(xs: Elem*): Parser[Elem] = elem("elemOf", xs contains _) + def escaped(ch: Char): Parser[String] = "\\" + ch + def mkQuoted(ch: Char): Parser[String] = ( + elem(ch) !~> rep(escaped(ch) | elemExcept(ch)) <~ ch ^^ (_.mkString) + | failure("Unmatched %s in input." format ch) + ) + + lazy val squoted: Parser[String] = mkQuoted('\'') ^^ (x => "'%s'" format x) + lazy val dquoted: Parser[String] = mkQuoted('"') ^^ (x => "\"" + x + "\"") + lazy val token: Parser[String] = """\S+""".r + + lazy val argument: Parser[String] = squoted | dquoted | token + lazy val commandLine: Parser[List[String]] = phrase(repsep(argument, whiteSpace)) + + class ParseError(msg: String) extends RuntimeException(msg) + + def tokenize(line: String): List[String] = tokenize(line, x => throw new ParseError(x)) + def tokenize(line: String, errorFn: String => Unit): List[String] = { + parse(commandLine, line.trim) match { + case Success(args, _) => args + case NoSuccess(msg, rest) => errorFn(msg) ; Nil + } + } + def apply(line: String) = new CommandLine(tokenize(line)) + + /** + * Split command line parameters by space, properly process quoted parameter + * XXX legacy, kept around for comparison. + * Note that this doesn't honor single quotes. + */ + def splitParams(line: String, errorFn: String => Unit): List[String] = { + def parse(from: Int, i: Int, args: List[String]): List[String] = { + if (i < line.length) { + line.charAt(i) match { + case ' ' => + val args1 = fetchArg(from, i) :: args + val j = skipS(i + 1) + if (j >= 0) { + parse(j, j, args1) + } else args1 + case '"' => + val j = skipTillQuote(i + 1) + if (j > 0) { + parse(from, j + 1, args) + } else { + errorFn("Parameters '" + line + "' with unmatched quote at " + i + ".") + Nil + } + case _ => parse(from, i + 1, args) + } + } else { // done + if (i > from) { + fetchArg(from, i) :: args + } else args + } + } + + def fetchArg(from: Int, until: Int) = { + if (line.charAt(from) == '"') { + line.substring(from + 1, until - 1) + } else { + line.substring(from, until) + } + } + + def skipTillQuote(i: Int): Int = { + if (i < line.length) { + line.charAt(i) match { + case '"' => i + case _ => skipTillQuote(i + 1) + } + } else -1 + } + + def skipS(i: Int): Int = { + if (i < line.length) { + line.charAt(i) match { + case ' ' => skipS(i + 1) + case _ => i + } + } else -1 + } + + // begin split + val j = skipS(0) + if (j >= 0) { + parse(j, j, Nil).reverse + } else Nil + } +} |