1313package scala .sys .process
1414
1515import 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 }
0 commit comments