Skip to content

Commit bac75e5

Browse files
committed
Upgrade syntax of CommandLineParser and test
1 parent 0fd3170 commit bac75e5

File tree

2 files changed

+61
-65
lines changed

2 files changed

+61
-65
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,96 @@
1-
package dotty.tools.dotc
2-
package config
1+
package dotty.tools.dotc.config
32

43
import scala.annotation.tailrec
4+
import scala.collection.mutable.ArrayBuffer
5+
import java.lang.Character.isWhitespace
56

67
/** A simple enough command line parser.
78
*/
8-
object CommandLineParser {
9-
private final val DQ = '"'
10-
private final val SQ = '\''
9+
object CommandLineParser:
10+
inline private val DQ = '"'
11+
inline private val SQ = '\''
12+
inline private val EOF = -1
1113

1214
/** Split the line into tokens separated by whitespace or quotes.
1315
*
14-
* @return either an error message or reverse list of tokens
16+
* Invoke `errorFn` with message on bad quote.
1517
*/
16-
private def tokens(in: String) = {
17-
import Character.isWhitespace
18-
import java.lang.{StringBuilder => Builder}
19-
import collection.mutable.ArrayBuffer
18+
def tokenize(line: String, errorFn: String => Unit): List[String] =
2019

2120
var accum: List[String] = Nil
22-
var pos = 0
21+
22+
var pos = 0
2323
var start = 0
24-
val qpos = new ArrayBuffer[Int](16) // positions of paired quotes
24+
val qpos = new ArrayBuffer[Int](16) // positions of paired quotes
2525

26-
def cur: Int = if (done) -1 else in.charAt(pos)
27-
def bump() = pos += 1
28-
def done = pos >= in.length
26+
inline def cur = if done then EOF else line.charAt(pos): Int
27+
inline def bump() = pos += 1
28+
inline def done = pos >= line.length
2929

30-
def skipToQuote(q: Int) = {
30+
def skipToQuote(q: Int): Boolean =
3131
var escaped = false
32-
def terminal = in.charAt(pos) match {
32+
def terminal = cur match
3333
case _ if escaped => escaped = false ; false
3434
case '\\' => escaped = true ; false
35-
case `q` => true
35+
case `q` | EOF => true
3636
case _ => false
37-
}
38-
while (!done && !terminal) pos += 1
37+
while !terminal do bump()
3938
!done
40-
}
41-
@tailrec
42-
def skipToDelim(): Boolean =
43-
cur match {
44-
case q @ (DQ | SQ) => { qpos += pos; bump(); skipToQuote(q) } && { qpos += pos; bump(); skipToDelim() }
39+
40+
@tailrec def skipToDelim(): Boolean =
41+
inline def quote() = { qpos += pos ; bump() }
42+
cur match
43+
case q @ (DQ | SQ) => { quote() ; skipToQuote(q) } && { quote() ; skipToDelim() }
4544
case -1 => true
4645
case c if isWhitespace(c) => true
4746
case _ => bump(); skipToDelim()
48-
}
49-
def skipWhitespace() = while (isWhitespace(cur)) pos += 1
50-
def copyText() = {
51-
val buf = new Builder
47+
48+
def copyText(): String =
49+
val buf = new java.lang.StringBuilder
5250
var p = start
5351
var i = 0
54-
while (p < pos) {
55-
if (i >= qpos.size) {
56-
buf.append(in, p, pos)
52+
while p < pos do
53+
if i >= qpos.size then
54+
buf.append(line, p, pos)
5755
p = pos
58-
} else if (p == qpos(i)) {
59-
buf.append(in, qpos(i)+1, qpos(i+1))
56+
else if p == qpos(i) then
57+
buf.append(line, qpos(i)+1, qpos(i+1))
6058
p = qpos(i+1)+1
6159
i += 2
62-
} else {
63-
buf.append(in, p, qpos(i))
60+
else
61+
buf.append(line, p, qpos(i))
6462
p = qpos(i)
65-
}
66-
}
6763
buf.toString
68-
}
69-
def text() = {
64+
65+
def text(): String =
7066
val res =
71-
if (qpos.isEmpty) in.substring(start, pos)
72-
else if (qpos(0) == start && qpos(1) == pos) in.substring(start+1, pos-1)
67+
if qpos.isEmpty then line.substring(start, pos)
68+
else if qpos(0) == start && qpos(1) == pos then line.substring(start+1, pos-1)
7369
else copyText()
7470
qpos.clear()
7571
res
76-
}
77-
def badquote = Left("Unmatched quote")
7872

79-
@tailrec def loop(): Either[String, List[String]] = {
73+
inline def badquote() = errorFn(s"Unmatched quote [${qpos.last}](${line.charAt(qpos.last)})")
74+
75+
inline def skipWhitespace() = while isWhitespace(cur) do pos += 1
76+
77+
@tailrec def loop(): List[String] =
8078
skipWhitespace()
8179
start = pos
82-
if (done) Right(accum)
83-
else if (!skipToDelim()) badquote
84-
else {
80+
if done then
81+
accum.reverse
82+
else if !skipToDelim() then
83+
badquote()
84+
Nil
85+
else
8586
accum = text() :: accum
8687
loop()
87-
}
88-
}
89-
loop()
90-
}
88+
end loop
9189

92-
class ParseException(msg: String) extends RuntimeException(msg)
90+
loop()
9391

94-
def tokenize(line: String, errorFn: String => Unit): List[String] =
95-
tokens(line) match {
96-
case Right(args) => args.reverse
97-
case Left(msg) => errorFn(msg) ; Nil
98-
}
92+
end tokenize
9993

10094
def tokenize(line: String): List[String] = tokenize(line, x => throw new ParseException(x))
101-
}
95+
96+
class ParseException(msg: String) extends RuntimeException(msg)

compiler/test/dotty/tools/dotc/config/CommandLineParserTest.scala

+7-6
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ class CommandLineParserTest:
99

1010
private def check(tokens: String*)(input: String): Unit = assertEquals(tokens, tokenize(input))
1111

12-
private def checkFails(input: String): Unit =
13-
var failed = false
14-
val res = tokenize(input, _ => failed = true)
15-
assertTrue(s"Expected bad tokenization for [$input] but result was [$res]", failed)
12+
private def checkFails(input: String, output: String): Unit =
13+
var txt: String = null
14+
val res = tokenize(input, msg => txt = msg)
15+
assertTrue(s"Expected bad tokenization for [$input] but result was [$res]", txt ne null)
16+
assertEquals(output, txt)
1617

1718
@Test def parserTokenizes() =
1819
check()("")
@@ -39,5 +40,5 @@ class CommandLineParserTest:
3940
check("x'y'z")("""x"'y'"z""")
4041
check("abcxyz")(""""abc"xyz""")
4142
// missing quotes
42-
checkFails(""""x""") // was assertEquals(List("\"x"), tokenize(""""x"""))
43-
checkFails("""x'""")
43+
checkFails(""""x""", "Unmatched quote [0](\")") // was assertEquals(List("\"x"), tokenize(""""x"""))
44+
checkFails("""x'""", "Unmatched quote [1](')")

0 commit comments

Comments
 (0)