Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

revert argument parsing changes (PRs 10114 and 10123) #10128

Merged
merged 3 commits into from Sep 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/compiler/scala/tools/nsc/CompilerCommand.scala
Expand Up @@ -126,11 +126,11 @@ class CompilerCommand(arguments: List[String], val settings: Settings) {
def expandArg(arg: String): List[String] = {
import java.nio.file.{Files, Paths}
import scala.jdk.CollectionConverters._
def stripComment(s: String) = s.takeWhile(_ != '#').trim()
val file = Paths.get(arg.stripPrefix("@"))
def stripComment(s: String) = s.takeWhile(_ != '#')
val file = Paths.get(arg stripPrefix "@")
if (!Files.exists(file))
throw new java.io.FileNotFoundException(s"argument file $file could not be found")
Files.readAllLines(file).asScala.map(stripComment).filter(_ != "").toList
settings.splitParams(Files.readAllLines(file).asScala.map(stripComment).mkString(" "))
}

// override this if you don't want arguments processed here
Expand Down
Expand Up @@ -973,5 +973,5 @@ class MutableSettings(val errorFn: String => Unit, val pathFactory: PathFactory)
}

private object Optionlike {
def unapply(s: String): Boolean = s.startsWith("-") && s != "-"
def unapply(s: String): Boolean = s.startsWith("-")
}
97 changes: 64 additions & 33 deletions src/library/scala/sys/process/Parser.scala
Expand Up @@ -13,8 +13,6 @@
package scala.sys.process

import scala.annotation.tailrec
import collection.mutable.ListBuffer
import Character.isWhitespace

/** A simple enough command line parser using shell quote conventions.
*/
Expand All @@ -23,54 +21,87 @@ private[scala] object Parser {
private final val SQ = '\''
private final val EOF = -1

/** Split the line into tokens separated by whitespace.
/** Split the line into tokens separated by whitespace or quotes.
*
* Tokens may be surrounded by quotes and may contain whitespace or escaped quotes.
*
* @return list of tokens
* @return either an error message or reverse list of tokens
*/
def tokenize(line: String, errorFn: String => Unit): List[String] = {
val accum = ListBuffer.empty[String]
val buf = new java.lang.StringBuilder
var pos = 0
import Character.isWhitespace
import java.lang.{StringBuilder => Builder}
import collection.mutable.ArrayBuffer

def cur: Int = if (done) EOF else line.charAt(pos)
def bump() = pos += 1
def put() = { buf.append(cur.toChar); bump() }
def done = pos >= line.length
var accum: List[String] = Nil
var pos = 0
var start = 0
val qpos = new ArrayBuffer[Int](16) // positions of paired quotes

def skipWhitespace() = while (isWhitespace(cur)) bump()
def cur: Int = if (done) EOF else line.charAt(pos)
def bump() = pos += 1
def done = pos >= line.length

// Collect to end of word, handling quotes. False for missing end quote.
def word(): Boolean = {
// Skip to the next quote as given.
def skipToQuote(q: Int): Boolean = {
var escaped = false
def terminal: Boolean = cur match {
case _ if escaped => escaped = false ; false
case '\\' => escaped = true ; false
case `q` | EOF => true
case _ => false
}
while (!terminal) bump()
!done
}
// Skip to a word boundary, where words can be quoted and quotes can be escaped
def skipToDelim(): Boolean = {
var escaped = false
var Q = EOF
var lastQ = 0
def inQuote = Q != EOF
def badquote() = errorFn(s"Unmatched quote [${lastQ}](${line.charAt(lastQ)})")
def finish(): Boolean = if (!inQuote) !escaped else { badquote(); false}
def quote() = { qpos += pos ; bump() }
@tailrec def advance(): Boolean = cur match {
case EOF => finish()
case _ if escaped => escaped = false; put(); advance()
case '\\' => escaped = true; bump(); advance()
case q if q == Q => Q = EOF; bump(); advance()
case q @ (DQ | SQ) if !inQuote => Q = q; lastQ = pos; bump(); advance()
case c if isWhitespace(c) && !inQuote => finish()
case _ => put(); advance()
case _ if escaped => escaped = false ; bump() ; advance()
case '\\' => escaped = true ; bump() ; advance()
case q @ (DQ | SQ) => { quote() ; skipToQuote(q) } && { quote() ; advance() }
case EOF => true
case c if isWhitespace(c) => true
case _ => bump(); advance()
}
advance()
}
def skipWhitespace() = while (isWhitespace(cur)) bump()
def copyText() = {
val buf = new Builder
var p = start
var i = 0
while (p < pos) {
if (i >= qpos.size) {
buf.append(line, p, pos)
p = pos
} else if (p == qpos(i)) {
buf.append(line, qpos(i)+1, qpos(i+1))
p = qpos(i+1)+1
i += 2
} else {
buf.append(line, p, qpos(i))
p = qpos(i)
}
}
buf.toString
}
def text() = {
val res = buf.toString
buf.setLength(0)
val res =
if (qpos.isEmpty) line.substring(start, pos)
else if (qpos(0) == start && qpos(1) == pos) line.substring(start+1, pos-1)
else copyText()
qpos.clear()
res
}
def badquote() = errorFn(s"Unmatched quote [${qpos.last}](${line.charAt(qpos.last)})")

@tailrec def loop(): List[String] = {
skipWhitespace()
if (done) accum.toList
else if (!word()) Nil
start = pos
if (done) accum.reverse
else if (!skipToDelim()) { badquote() ; Nil }
else {
accum += text()
accum ::= text()
loop()
}
}
Expand Down
16 changes: 4 additions & 12 deletions test/junit/scala/sys/process/ParserTest.scala
Expand Up @@ -51,17 +51,9 @@ class ParserTest {
@Test def `leading quote is escaped`: Unit = {
check("echo", "hello, world!")("""echo "hello, world!" """)
check("echo", "hello, world!")("""echo hello,' 'world! """)
check("echo", """"hello,""", """world!"""")("""echo \"hello, world!\" """)
check("""a"b"c""")("""a\"b\"c""")
check("a", "'b", "'", "c")("""a \'b \' c""")
check("a", """\b """, "c")("""a \\'b ' c""")
}
/* backslash is stripped in normal shell usage.
➜ ~ ls \"hello world\"
ls: cannot access '"hello': No such file or directory
ls: cannot access 'world"': No such file or directory
*/
@Test def `escaped quotes lose backslash`: Unit = {
check("ls", "\"hello", "world\"")("""ls \"hello world\"""")
check("echo", """\"hello,""", """world!\"""")("""echo \"hello, world!\" """)
check("""a\"b\"c""")("""a\"b\"c""")
check("a", "\\'b", "\\'", "c")("""a \'b \' c""")
check("a", "\\\\b ", "c")("""a \\'b ' c""")
}
}