Skip to content

Commit

Permalink
Merge pull request #10128 from SethTisue/revert-10114-10123
Browse files Browse the repository at this point in the history
  • Loading branch information
SethTisue committed Sep 1, 2022
2 parents f69fe8b + e5fe919 commit d578a02
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 49 deletions.
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""")
}
}

0 comments on commit d578a02

Please sign in to comment.