Skip to content

Commit d578a02

Browse files
authored
Merge pull request #10128 from SethTisue/revert-10114-10123
2 parents f69fe8b + e5fe919 commit d578a02

File tree

4 files changed

+72
-49
lines changed

4 files changed

+72
-49
lines changed

src/compiler/scala/tools/nsc/CompilerCommand.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,11 @@ class CompilerCommand(arguments: List[String], val settings: Settings) {
126126
def expandArg(arg: String): List[String] = {
127127
import java.nio.file.{Files, Paths}
128128
import scala.jdk.CollectionConverters._
129-
def stripComment(s: String) = s.takeWhile(_ != '#').trim()
130-
val file = Paths.get(arg.stripPrefix("@"))
129+
def stripComment(s: String) = s.takeWhile(_ != '#')
130+
val file = Paths.get(arg stripPrefix "@")
131131
if (!Files.exists(file))
132132
throw new java.io.FileNotFoundException(s"argument file $file could not be found")
133-
Files.readAllLines(file).asScala.map(stripComment).filter(_ != "").toList
133+
settings.splitParams(Files.readAllLines(file).asScala.map(stripComment).mkString(" "))
134134
}
135135

136136
// override this if you don't want arguments processed here

src/compiler/scala/tools/nsc/settings/MutableSettings.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -973,5 +973,5 @@ class MutableSettings(val errorFn: String => Unit, val pathFactory: PathFactory)
973973
}
974974

975975
private object Optionlike {
976-
def unapply(s: String): Boolean = s.startsWith("-") && s != "-"
976+
def unapply(s: String): Boolean = s.startsWith("-")
977977
}

src/library/scala/sys/process/Parser.scala

Lines changed: 64 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
package scala.sys.process
1414

1515
import scala.annotation.tailrec
16-
import collection.mutable.ListBuffer
17-
import Character.isWhitespace
1816

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

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

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

42-
def skipWhitespace() = while (isWhitespace(cur)) bump()
38+
def cur: Int = if (done) EOF else line.charAt(pos)
39+
def bump() = pos += 1
40+
def done = pos >= line.length
4341

44-
// Collect to end of word, handling quotes. False for missing end quote.
45-
def word(): Boolean = {
42+
// Skip to the next quote as given.
43+
def skipToQuote(q: Int): Boolean = {
44+
var escaped = false
45+
def terminal: Boolean = cur match {
46+
case _ if escaped => escaped = false ; false
47+
case '\\' => escaped = true ; false
48+
case `q` | EOF => true
49+
case _ => false
50+
}
51+
while (!terminal) bump()
52+
!done
53+
}
54+
// Skip to a word boundary, where words can be quoted and quotes can be escaped
55+
def skipToDelim(): Boolean = {
4656
var escaped = false
47-
var Q = EOF
48-
var lastQ = 0
49-
def inQuote = Q != EOF
50-
def badquote() = errorFn(s"Unmatched quote [${lastQ}](${line.charAt(lastQ)})")
51-
def finish(): Boolean = if (!inQuote) !escaped else { badquote(); false}
57+
def quote() = { qpos += pos ; bump() }
5258
@tailrec def advance(): Boolean = cur match {
53-
case EOF => finish()
54-
case _ if escaped => escaped = false; put(); advance()
55-
case '\\' => escaped = true; bump(); advance()
56-
case q if q == Q => Q = EOF; bump(); advance()
57-
case q @ (DQ | SQ) if !inQuote => Q = q; lastQ = pos; bump(); advance()
58-
case c if isWhitespace(c) && !inQuote => finish()
59-
case _ => put(); advance()
59+
case _ if escaped => escaped = false ; bump() ; advance()
60+
case '\\' => escaped = true ; bump() ; advance()
61+
case q @ (DQ | SQ) => { quote() ; skipToQuote(q) } && { quote() ; advance() }
62+
case EOF => true
63+
case c if isWhitespace(c) => true
64+
case _ => bump(); advance()
6065
}
6166
advance()
6267
}
68+
def skipWhitespace() = while (isWhitespace(cur)) bump()
69+
def copyText() = {
70+
val buf = new Builder
71+
var p = start
72+
var i = 0
73+
while (p < pos) {
74+
if (i >= qpos.size) {
75+
buf.append(line, p, pos)
76+
p = pos
77+
} else if (p == qpos(i)) {
78+
buf.append(line, qpos(i)+1, qpos(i+1))
79+
p = qpos(i+1)+1
80+
i += 2
81+
} else {
82+
buf.append(line, p, qpos(i))
83+
p = qpos(i)
84+
}
85+
}
86+
buf.toString
87+
}
6388
def text() = {
64-
val res = buf.toString
65-
buf.setLength(0)
89+
val res =
90+
if (qpos.isEmpty) line.substring(start, pos)
91+
else if (qpos(0) == start && qpos(1) == pos) line.substring(start+1, pos-1)
92+
else copyText()
93+
qpos.clear()
6694
res
6795
}
96+
def badquote() = errorFn(s"Unmatched quote [${qpos.last}](${line.charAt(qpos.last)})")
97+
6898
@tailrec def loop(): List[String] = {
6999
skipWhitespace()
70-
if (done) accum.toList
71-
else if (!word()) Nil
100+
start = pos
101+
if (done) accum.reverse
102+
else if (!skipToDelim()) { badquote() ; Nil }
72103
else {
73-
accum += text()
104+
accum ::= text()
74105
loop()
75106
}
76107
}

test/junit/scala/sys/process/ParserTest.scala

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,9 @@ class ParserTest {
5151
@Test def `leading quote is escaped`: Unit = {
5252
check("echo", "hello, world!")("""echo "hello, world!" """)
5353
check("echo", "hello, world!")("""echo hello,' 'world! """)
54-
check("echo", """"hello,""", """world!"""")("""echo \"hello, world!\" """)
55-
check("""a"b"c""")("""a\"b\"c""")
56-
check("a", "'b", "'", "c")("""a \'b \' c""")
57-
check("a", """\b """, "c")("""a \\'b ' c""")
58-
}
59-
/* backslash is stripped in normal shell usage.
60-
➜ ~ ls \"hello world\"
61-
ls: cannot access '"hello': No such file or directory
62-
ls: cannot access 'world"': No such file or directory
63-
*/
64-
@Test def `escaped quotes lose backslash`: Unit = {
65-
check("ls", "\"hello", "world\"")("""ls \"hello world\"""")
54+
check("echo", """\"hello,""", """world!\"""")("""echo \"hello, world!\" """)
55+
check("""a\"b\"c""")("""a\"b\"c""")
56+
check("a", "\\'b", "\\'", "c")("""a \'b \' c""")
57+
check("a", "\\\\b ", "c")("""a \\'b ' c""")
6658
}
6759
}

0 commit comments

Comments
 (0)