1313package scala .sys .process
1414
1515import scala .annotation .tailrec
16+ import collection .mutable .ListBuffer
17+ import Character .isWhitespace
1618
1719/** A simple enough command line parser using shell quote conventions.
1820 */
@@ -21,87 +23,54 @@ private[scala] object Parser {
2123 private final val SQ = '\' '
2224 private final val EOF = - 1
2325
24- /** Split the line into tokens separated by whitespace or quotes .
26+ /** Split the line into tokens separated by whitespace.
2527 *
26- * @return either an error message or reverse list of tokens
28+ * Tokens may be surrounded by quotes and may contain whitespace or escaped quotes.
29+ *
30+ * @return list of tokens
2731 */
2832 def tokenize (line : String , errorFn : String => Unit ): List [String ] = {
29- import Character . isWhitespace
30- import java .lang .{ StringBuilder => Builder }
31- import collection . mutable . ArrayBuffer
33+ val accum = ListBuffer .empty[ String ]
34+ val buf = new java.lang.StringBuilder
35+ var pos = 0
3236
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
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
3741
38- def cur : Int = if (done) EOF else line.charAt(pos)
39- def bump () = pos += 1
40- def done = pos >= line.length
42+ def skipWhitespace () = while (isWhitespace(cur)) bump()
4143
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 = {
44+ // Collect to end of word, handling quotes. False for missing end quote.
45+ def word (): Boolean = {
5646 var escaped = false
57- def quote () = { qpos += pos ; bump() }
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 }
5852 @ tailrec def advance (): Boolean = cur match {
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()
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()
6560 }
6661 advance()
6762 }
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- }
8863 def text () = {
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()
64+ val res = buf.toString
65+ buf.setLength(0 )
9466 res
9567 }
96- def badquote () = errorFn(s " Unmatched quote [ ${qpos.last}]( ${line.charAt(qpos.last)}) " )
97-
9868 @ tailrec def loop (): List [String ] = {
9969 skipWhitespace()
100- start = pos
101- if (done) accum.reverse
102- else if (! skipToDelim()) { badquote() ; Nil }
70+ if (done) accum.toList
71+ else if (! word()) Nil
10372 else {
104- accum :: = text()
73+ accum + = text()
10574 loop()
10675 }
10776 }
0 commit comments