Revert "process.Parser strips escaping backslash" · scala/scala@864148d
@@ -13,8 +13,6 @@
1313package scala.sys.process
14141515import scala.annotation.tailrec
16-import collection.mutable.ListBuffer
17-import Character.isWhitespace
18161917/** A simple enough command line parser using shell quote conventions.
2018 */
@@ -23,54 +21,87 @@ private[scala] object Parser {
2321private final val SQ = '\''
2422private final val EOF = -1
252326- /** 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 */
3228def 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
363237-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
413742-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
434144-// 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 = {
4656var 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+ }
6388def 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 }
72103else {
73- accum += text()
104+ accum ::= text()
74105 loop()
75106 }
76107 }