Skip to content

Commit b22ae5e

Browse files
committed
fix: nontermination with some dangling operators
1 parent ed91206 commit b22ae5e

File tree

2 files changed

+79
-50
lines changed

2 files changed

+79
-50
lines changed

shared/src/main/scala/scala/util/parsing/combinator/Parsers.scala

Lines changed: 51 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ package scala
1414
package util.parsing.combinator
1515

1616
import scala.util.parsing.input._
17+
import scala.util.{Either,Left,Right}
1718
import scala.collection.mutable.ListBuffer
1819
import scala.annotation.tailrec
1920
import scala.language.implicitConversions
@@ -1047,8 +1048,6 @@ trait Parsers {
10471048
= OnceParser{ (for(a <- this; _ <- commit(p)) yield a).named("<~") }
10481049
}
10491050

1050-
import Associativity._
1051-
10521051
/** A parser that respects operator the precedence and associativity
10531052
* conventions specified in its constructor.
10541053
*
@@ -1067,62 +1066,65 @@ trait Parsers {
10671066
*/
10681067
class PrecedenceParser[Exp,Op,E <: Exp](primary: Parser[E],
10691068
binop: Parser[Op],
1070-
prec_table: List[(Associativity, List[Op])],
1069+
prec_table: List[(Associativity.Associativity, List[Op])],
10711070
makeBinop: (Exp, Op, Exp) => Exp) extends Parser[Exp] {
1072-
private def decodePrecedence: (Map[Op, Int], Map[Op, Associativity]) = {
1073-
var precedence = Map.empty[Op, Int]
1074-
var associativity = Map.empty[Op, Associativity]
1075-
var level = prec_table.length
1076-
for ((assoc, ops) <- prec_table) {
1077-
precedence = precedence ++ (for (op <- ops) yield (op, level))
1078-
associativity = associativity ++ (for (op <- ops) yield (op, assoc))
1079-
level -= 1
1080-
}
1081-
(precedence, associativity)
1071+
val empty = Map.empty[Op, (Int, Associativity.Associativity)];
1072+
val opData = prec_table.reverse.zipWithIndex.foldLeft(empty)({
1073+
(map, data) =>
1074+
val ((assoc, ops), index) = data;
1075+
val bindings = for (op <- ops) yield (op, (index+1, assoc));
1076+
map ++ bindings
1077+
})
1078+
def precedence(op: Op): Int = {
1079+
val (level, _) = opData(op);
1080+
level
10821081
}
1083-
val (precedence, associativity) = decodePrecedence
1084-
private class ExpandLeftParser(lhs: Exp, minLevel: Int) extends Parser[Exp] {
1085-
def apply(input: Input): ParseResult[Exp] = {
1086-
(binop ~ primary)(input) match {
1087-
case Success(op ~ rhs, next) if precedence(op) >= minLevel => {
1088-
new ExpandRightParser(rhs, precedence(op), minLevel)(next) match {
1089-
case Success(r, nextInput) => new ExpandLeftParser(makeBinop(lhs, op, r), minLevel)(nextInput);
1090-
case ns => ns // dead code
1082+
1083+
type Expansion = Either[Success[Exp], Success[Exp]];
1084+
1085+
private def expandRight(rhs: Exp, input: Reader[Elem], current: Int): Expansion = {
1086+
binop(input) match {
1087+
case Success(op, _) => {
1088+
val nextLevel = opData(op) match {
1089+
case (l, _) if l > current => Some(current + 1)
1090+
case (l, Associativity.Right) if l == current => Some(current)
1091+
case _ => None
1092+
};
1093+
nextLevel match {
1094+
case Some(nl) => {
1095+
expandLeft(rhs, input, nl) match {
1096+
case Right(Success(r, rnext)) => expandRight(r, rnext, current)
1097+
case result => result
1098+
}
10911099
}
1092-
}
1093-
case _ => {
1094-
Success(lhs, input);
1100+
case None => Right(Success(rhs, input))
10951101
}
10961102
}
1103+
case _ => Left(Success(rhs, input))
10971104
}
10981105
}
10991106

1100-
private class ExpandRightParser(rhs: Exp, currentLevel: Int, minLevel: Int) extends Parser[Exp] {
1101-
private def nextLevel(nextBinop: Op): Option[Int] = {
1102-
if (precedence(nextBinop) > currentLevel) {
1103-
Some(minLevel + 1)
1104-
} else if (precedence(nextBinop) == currentLevel && associativity(nextBinop) == Associativity.Right) {
1105-
Some(minLevel)
1106-
} else {
1107-
None
1108-
}
1109-
}
1110-
def apply(input: Input): ParseResult[Exp] = {
1111-
def done: ParseResult[Exp] = Success(rhs, input)
1112-
binop(input) match {
1113-
case Success(nextBinop,_) => {
1114-
nextLevel(nextBinop) match {
1115-
case Some(level) => {
1116-
new ExpandLeftParser(rhs, level)(input) match {
1117-
case Success(r, next) => new ExpandRightParser(r, currentLevel, minLevel)(next)
1118-
case ns => ns // dead code
1107+
private def expandLeft(lhs: Exp, input: Reader[Elem], minLevel: Int): Expansion = {
1108+
binop(input) match {
1109+
case Success(op, binInput) => {
1110+
val prec = precedence(op);
1111+
if (prec >= minLevel) {
1112+
primary(binInput) match {
1113+
case Success(rhs, next) => {
1114+
expandRight(rhs, next, prec) match {
1115+
case Right(Success(r, rnext)) => {
1116+
expandLeft(makeBinop(lhs, op, r), rnext, minLevel)
1117+
}
1118+
case Left(Success(r, rnext)) => Left(Success(makeBinop(lhs, op, r), rnext))
11191119
}
11201120
}
1121-
case None => done
1121+
case _ => Left(Success(lhs, input))
11221122
}
1123+
} else {
1124+
Left(Success(lhs, input))
11231125
}
1124-
case _ => done
11251126
}
1127+
case _ => Left(Success(lhs, input))
11261128
}
11271129
}
11281130

@@ -1131,7 +1133,10 @@ trait Parsers {
11311133
def apply(input: Input): ParseResult[Exp] = {
11321134
primary(input) match {
11331135
case Success(lhs, next) => {
1134-
new ExpandLeftParser(lhs,0)(next)
1136+
expandLeft(lhs, next, 0) match {
1137+
case Left(result) => result
1138+
case Right(result) => result
1139+
}
11351140
}
11361141
case noSuccess => noSuccess
11371142
}

shared/src/test/scala/scala/util/parsing/combinator/PrecedenceParserTest.scala

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import scala.util.parsing.input.StreamReader
99

1010
class PrecedenceParsersTest {
1111

12-
abstract class Op
12+
trait Token
13+
abstract class Op extends Token
1314
object Plus extends Op {
1415
override def toString = "+"
1516
}
@@ -26,7 +27,7 @@ class PrecedenceParsersTest {
2627
override def toString = "="
2728
}
2829

29-
abstract class Node
30+
abstract class Node extends Token
3031
case class Leaf(v: Int) extends Node {
3132
override def toString = v.toString
3233
}
@@ -41,21 +42,44 @@ class PrecedenceParsersTest {
4142
(Associativity.Right, List(Equals)))
4243
def integer: Parser[Leaf] = "[0-9]+".r ^^ { (s: String) => Leaf(s.toInt) }
4344
def binop: Parser[Op] = "+" ^^^ Plus | "-" ^^^ Minus | "*" ^^^ Mult | "/" ^^^ Divide | "=" ^^^ Equals
45+
def token: Parser[Token] = integer | binop
4446
def expression = new PrecedenceParser(integer, binop, prec, Binop.apply)
4547
}
4648

47-
def testExp(expected: Node, input: String): Unit = {
49+
def checkEnd(next: ArithmeticParser.Input, end: List[Token]): Unit = {
50+
end match {
51+
case head :: rest => {
52+
ArithmeticParser.token(next) match {
53+
case ArithmeticParser.Success(token, scanner) => {
54+
assertEquals(token, head);
55+
checkEnd(scanner, rest);
56+
}
57+
case ns => fail(s"Expected to match $end but got $ns")
58+
}
59+
}
60+
case _ => assertTrue(next.atEnd)
61+
}
62+
}
63+
64+
def testExp(expected: Node, input: String, end: List[Token] = List.empty): Unit = {
4865
ArithmeticParser.expression(StreamReader(new StringReader(input))) match {
4966
case ArithmeticParser.Success(r, next) => {
5067
assertEquals(expected, r);
51-
assertTrue(next.atEnd);
68+
checkEnd(next, end);
5269
}
5370
case e => {
5471
fail(s"Error parsing $input: $e");
5572
}
5673
}
5774
}
5875

76+
@Test
77+
def testGracefulEnd(): Unit = {
78+
testExp(Leaf(4), "4 +", List[Token](Plus));
79+
testExp(Binop(Leaf(1), Plus, Leaf(2)), "1 + 2 *", List[Token](Mult));
80+
testExp(Binop(Leaf(1), Plus, Binop(Leaf(2), Mult, Leaf(3))), "1 + 2 * 3 -", List[Token](Minus));
81+
}
82+
5983
@Test
6084
def basicExpTests: Unit = {
6185
testExp(Leaf(4), "4")

0 commit comments

Comments
 (0)