Revert "process.Parser strips escaping backslash" · scala/scala@864148d

@@ -13,8 +13,6 @@

1313

package scala.sys.process

14141515

import 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 {

2321

private final val SQ = '\''

2422

private 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

*/

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

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 = {

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

}