scala.tools.nsc.util.CommandLineParser.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala-compiler Show documentation
Show all versions of scala-compiler Show documentation
Compiler for the Scala Programming Language
/* NEST (New Scala Test)
* Copyright 2007-2013 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.
*
* XXX Note this has been completely obsolesced by scala.tools.cmd.
* I checked it back in as part of rolling partest back a month
* rather than go down the rabbit hole of unravelling dependencies.
*/
trait ParserUtil extends Parsers {
protected implicit 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 }
}
}
case class CommandLine(
args: List[String],
unaryArguments: List[String],
binaryArguments: List[String]
) {
def this(args: List[String]) = this(args, Nil, Nil)
def this(args: Array[String]) = this(args.toList, Nil, Nil)
def this(line: String) = this(CommandLineParser tokenize line, Nil, Nil)
def withUnaryArgs(xs: List[String]) = copy(unaryArguments = xs)
def withBinaryArgs(xs: List[String]) = copy(binaryArguments = xs)
def originalArgs = args
def assumeBinary = true
def enforceArity = true
def onlyKnownOptions = false
val Terminator = "--"
val ValueForUnaryOption = "true" // so if --opt is given, x(--opt) = true
def mapForUnary(opt: String) = Map(opt -> ValueForUnaryOption)
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 residualBuffer = new ListBuffer[String]
def stripQuotes(s: String) = {
def isQuotedBy(c: Char) = s.length > 0 && s.head == c && s.last == c
if (List('"', '\'') exists isQuotedBy) s.tail.init else s
}
def isValidOption(s: String) = !onlyKnownOptions || (unaryArguments contains s) || (binaryArguments contains s)
def isOption(s: String) = (s startsWith "-") && (isValidOption(s) || { unknownOption(s) ; false })
def isUnary(s: String) = isOption(s) && (unaryArguments contains s)
def isBinary(s: String) = isOption(s) && !isUnary(s) && (assumeBinary || (binaryArguments contains s))
def unknownOption(opt: String) =
errorFn("Option '%s' not recognized.".format(opt))
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] = {
def residual(xs: List[String]) = { residualBuffer ++= xs ; Map[String, String]() }
if (args.isEmpty) return Map()
val hd :: rest = args
if (rest.isEmpty) {
if (isBinary(hd) && enforceArity)
missingArg(hd, "EOF")
if (isOption(hd)) mapForUnary(hd) else residual(args)
}
else
if (hd == Terminator) residual(rest)
else {
val hd1 :: hd2 :: rest = args
if (hd2 == Terminator) mapForUnary(hd1) ++ residual(rest)
else if (isUnary(hd1)) mapForUnary(hd1) ++ loop(hd2 :: rest)
else if (isBinary(hd1)) {
// Disabling this check so
// --scalacopts "-verbose" works. We can't tell if it's quoted,
// the shell does us in.
//
// if (isOption(hd2) && enforceArity)
// missingArg(hd1, hd2)
Map(hd1 -> hd2) ++ loop(rest)
}
else { residual(List(hd1)) ++ loop(hd2 :: rest) }
}
}
(loop(args), residualBuffer map stripQuotes toList)
}
def isSet(arg: String) = args contains arg
def get(arg: String) = argMap get arg
def getOrElse(arg: String, orElse: => String) = if (isSet(arg)) apply(arg) else orElse
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)
)
/** Apparently windows can't deal with the quotes sticking around. */
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 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] = {
parse(commandLine, line.trim) match {
case Success(args, _) => args
case NoSuccess(msg, rest) => errorFn(msg) ; Nil
}
}
def apply(line: String) = new CommandLine(tokenize(line))
}