From 19e68d6974c3067814b818b4dfd7d15461a9ef29 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sun, 13 Oct 2013 06:34:04 -0700 Subject: Rewrites the parser stack reduction logic. Centralizes the scattered logic surrounding erroneous pattern syntax. Consolidates the redundant lookahead implementations. Eliminates var manipulation in favor of recursion. --- .../scala/tools/nsc/ast/parser/Parsers.scala | 246 ++++++++++----------- test/files/neg/t421.check | 2 +- test/files/neg/t5702-neg-bad-and-wild.check | 2 +- test/files/neg/t5702-neg-bad-brace.check | 11 +- 4 files changed, 124 insertions(+), 137 deletions(-) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 07938ec3df..24e35a9fd8 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -11,7 +11,7 @@ package ast.parser import scala.collection.{ mutable, immutable } import mutable.{ ListBuffer, StringBuilder } -import scala.reflect.internal.{ ModifierFlags => Flags } +import scala.reflect.internal.{ Precedence, ModifierFlags => Flags } import scala.reflect.internal.Chars.{ isScalaLetter } import scala.reflect.internal.util.{ SourceFile, Position, FreshNameCreator } import Tokens._ @@ -133,7 +133,9 @@ self => val global: Global import global._ - case class OpInfo(operand: Tree, operator: Name, offset: Offset) + case class OpInfo(lhs: Tree, operator: TermName, offset: Offset) { + def precedence = Precedence(operator.toString) + } class SourceFileParser(val source: SourceFile) extends Parser { @@ -146,17 +148,6 @@ self => def newScanner(): Scanner = new SourceFileScanner(source) - /** Scoping operator used to temporarily look into the future. - * Backs up scanner data before evaluating a block and restores it after. - */ - def lookingAhead[T](body: => T): T = { - val snapshot = (new ScannerData{}).copyFrom(in) - in.nextToken() - val res = body - in copyFrom snapshot - res - } - val in = newScanner() in.init() @@ -284,6 +275,15 @@ self => def unit: CompilationUnit def source: SourceFile + /** Scoping operator used to temporarily look into the future. + * Backs up scanner data before evaluating a block and restores it after. + */ + @inline final def lookingAhead[T](body: => T): T = { + val saved = new ScannerData {} copyFrom in + in.nextToken() + try body finally in copyFrom saved + } + class ParserTreeBuilder extends TreeBuilder { val global: self.global.type = self.global def unit = parser.unit @@ -311,6 +311,7 @@ self => finally classContextBounds = saved } + /** Are we inside the Scala package? Set for files that start with package scala */ private var inScalaPackage = false @@ -642,6 +643,10 @@ self => case INTLIT | LONGLIT | FLOATLIT | DOUBLELIT => true case _ => false } + + def isIdentExcept(except: Name) = isIdent && in.name != except + def isIdentOf(name: Name) = isIdent && in.name == name + def isUnaryOp = isIdent && raw.isUnary(in.name) def isRawStar = isIdent && in.name == raw.STAR def isRawBar = isIdent && in.name == raw.BAR @@ -754,49 +759,60 @@ self => var opstack: List[OpInfo] = Nil - def precedence(operator: Name): Int = - if (operator eq nme.ERROR) -1 - else { - val firstCh = operator.startChar - if (isScalaLetter(firstCh)) 1 - else if (nme.isOpAssignmentName(operator)) 0 - else firstCh match { - case '|' => 2 - case '^' => 3 - case '&' => 4 - case '=' | '!' => 5 - case '<' | '>' => 6 - case ':' => 7 - case '+' | '-' => 8 - case '*' | '/' | '%' => 9 - case _ => 10 - } - } + @deprecated("Use `scala.reflect.internal.Precdence`", "2.11.0") + def precedence(operator: Name): Int = Precedence(operator.toString).level + + private def opHead = opstack.head + private def headPrecedence = opHead.precedence + private def popOpInfo(): OpInfo = try opHead finally opstack = opstack.tail + private def pushOpInfo(top: Tree) { + val opinfo = OpInfo(top, in.name, in.offset) + opstack ::= opinfo + ident() + } - def checkAssoc(offset: Offset, op: Name, leftAssoc: Boolean) = + def checkHeadAssoc(leftAssoc: Boolean) = checkAssoc(opHead.offset, opHead.operator, leftAssoc) + def checkAssoc(offset: Offset, op: Name, leftAssoc: Boolean) = ( if (treeInfo.isLeftAssoc(op) != leftAssoc) - syntaxError( - offset, "left- and right-associative operators with same precedence may not be mixed", skipIt = false) - - def reduceStack(isExpr: Boolean, base: List[OpInfo], top0: Tree, prec: Int, leftAssoc: Boolean): Tree = { - var top = top0 - if (opstack != base && precedence(opstack.head.operator) == prec) - checkAssoc(opstack.head.offset, opstack.head.operator, leftAssoc) - while (opstack != base && - (prec < precedence(opstack.head.operator) || - leftAssoc && prec == precedence(opstack.head.operator))) { - val opinfo = opstack.head - opstack = opstack.tail - val opPos = r2p(opinfo.offset, opinfo.offset, opinfo.offset+opinfo.operator.length) - val lPos = opinfo.operand.pos - val start = if (lPos.isDefined) lPos.start else opPos.start - val rPos = top.pos - val end = if (rPos.isDefined) rPos.end else opPos.end - top = atPos(start, opinfo.offset, end) { - makeBinop(isExpr, opinfo.operand, opinfo.operator.toTermName, top, opPos) - } - } - top + syntaxError(offset, "left- and right-associative operators with same precedence may not be mixed", skipIt = false) + ) + + def finishPostfixOp(start: Int, base: List[OpInfo], opinfo: OpInfo): Tree = { + val od = stripParens(reduceExprStack(base, opinfo.lhs)) + makePostfixSelect(start, opinfo.offset, od, opinfo.operator) + } + + def finishBinaryOp(isExpr: Boolean, opinfo: OpInfo, rhs: Tree): Tree = { + import opinfo._ + val operatorPos: Position = Position.range(rhs.pos.source, offset, offset, offset + operator.length) + val pos = lhs.pos union rhs.pos union operatorPos withPoint offset + + atPos(pos)(makeBinop(isExpr, lhs, operator, rhs, operatorPos)) + } + + def reduceExprStack(base: List[OpInfo], top: Tree): Tree = reduceStack(isExpr = true, base, top) + def reducePatternStack(base: List[OpInfo], top: Tree): Tree = reduceStack(isExpr = false, base, top) + + def reduceStack(isExpr: Boolean, base: List[OpInfo], top: Tree): Tree = { + val opPrecedence = if (isIdent) Precedence(in.name.toString) else Precedence(0) + val leftAssoc = !isIdent || (treeInfo isLeftAssoc in.name) + + reduceStack(isExpr, base, top, opPrecedence, leftAssoc) + } + + def reduceStack(isExpr: Boolean, base: List[OpInfo], top: Tree, opPrecedence: Precedence, leftAssoc: Boolean): Tree = { + def isDone = opstack == base + def lowerPrecedence = !isDone && (opPrecedence < headPrecedence) + def samePrecedence = !isDone && (opPrecedence == headPrecedence) + def canReduce = lowerPrecedence || leftAssoc && samePrecedence + + if (samePrecedence) + checkHeadAssoc(leftAssoc) + + def loop(top: Tree): Tree = + if (canReduce) loop(finishBinaryOp(isExpr, popOpInfo(), top)) else top + + loop(top) } /* -------- IDENTIFIERS AND LITERALS ------------------------------------------- */ @@ -1475,28 +1491,19 @@ self => def postfixExpr(): Tree = { val start = in.offset val base = opstack - var top = prefixExpr() - while (isIdent) { - top = reduceStack(isExpr = true, base, top, precedence(in.name), leftAssoc = treeInfo.isLeftAssoc(in.name)) - val op = in.name - opstack = OpInfo(top, op, in.offset) :: opstack - ident() + def loop(top: Tree): Tree = if (!isIdent) top else { + pushOpInfo(reduceExprStack(base, top)) newLineOptWhenFollowing(isExprIntroToken) - if (isExprIntro) { - val next = prefixExpr() - if (next == EmptyTree) - return reduceStack(isExpr = true, base, top, 0, leftAssoc = true) - top = next - } else { - // postfix expression - val topinfo = opstack.head - opstack = opstack.tail - val od = stripParens(reduceStack(isExpr = true, base, topinfo.operand, 0, leftAssoc = true)) - return makePostfixSelect(start, topinfo.offset, od, topinfo.operator) - } + if (isExprIntro) + prefixExpr() match { + case EmptyTree => reduceExprStack(base, top) + case next => loop(next) + } + else finishPostfixOp(start, base, popOpInfo()) } - reduceStack(isExpr = true, base, top, 0, leftAssoc = true) + + reduceExprStack(base, loop(prefixExpr())) } /** {{{ @@ -1815,69 +1822,52 @@ self => * }}} */ def pattern3(): Tree = { - var top = simplePattern(badPattern3) - // after peekahead - def acceptWildStar() = atPos(top.pos.start, in.prev.offset)(Star(stripParens(top))) - def peekahead() = { - in.prev copyFrom in - in.nextToken() - } - def pushback() = { - in.next copyFrom in - in copyFrom in.prev - } + val top = simplePattern(badPattern3) + val base = opstack // See SI-3189, SI-4832 for motivation. Cf SI-3480 for counter-motivation. // TODO: dredge out the remnants of regexp patterns. - // /{/ peek for _*) or _*} (for xml escape) - if (isSequenceOK) { - top match { - case Ident(nme.WILDCARD) if (isRawStar) => - peekahead() - in.token match { - case RBRACE if (isXML) => return acceptWildStar() - case RPAREN if (!isXML) => return acceptWildStar() - case _ => pushback() - } - case _ => + def peekaheadDelim() = { + def isCloseDelim = in.token match { + case RBRACE => isXML + case RPAREN => !isXML + case _ => false } + lookingAhead(isCloseDelim) && { in.nextToken() ; true } } - val base = opstack - while (isIdent && in.name != raw.BAR) { - top = reduceStack(isExpr = false, base, top, precedence(in.name), leftAssoc = treeInfo.isLeftAssoc(in.name)) - val op = in.name - opstack = OpInfo(top, op, in.offset) :: opstack - ident() - top = simplePattern(badPattern3) + def isWildStar = top match { + case Ident(nme.WILDCARD) if isRawStar => peekaheadDelim() + case _ => false + } + def loop(top: Tree): Tree = reducePatternStack(base, top) match { + case next if isIdentExcept(raw.BAR) => pushOpInfo(next) ; loop(simplePattern(badPattern3)) + case next => next } - stripParens(reduceStack(isExpr = false, base, top, 0, leftAssoc = true)) + if (isSequenceOK && isWildStar) + atPos(top.pos.start, in.prev.offset)(Star(stripParens(top))) + else + stripParens(loop(top)) } + def badPattern3(): Tree = { - def isComma = in.token == COMMA - def isAnyBrace = in.token == RPAREN || in.token == RBRACE - val badStart = "illegal start of simple pattern" + def isComma = in.token == COMMA + def isDelimeter = in.token == RPAREN || in.token == RBRACE + def isCommaOrDelimeter = isComma || isDelimeter + val (isUnderscore, isStar) = opstack match { + case OpInfo(Ident(nme.WILDCARD), nme.STAR, _) :: _ => (true, true) + case OpInfo(_, nme.STAR, _) :: _ => (false, true) + case _ => (false, false) + } + def isSeqPatternClose = isUnderscore && isStar && isSequenceOK && isDelimeter + val msg = (isUnderscore, isStar, isSequenceOK) match { + case (true, true, true) if isComma => "bad use of _* (a sequence pattern must be the last pattern)" + case (true, true, true) if isDelimeter => "bad brace or paren after _*" + case (true, true, false) if isDelimeter => "bad use of _* (sequence pattern not allowed)" + case (false, true, true) if isDelimeter => "use _* to match a sequence" + case (false, true, true) if isCommaOrDelimeter => "trailing * is not a valid pattern" + case _ => "illegal start of simple pattern" + } // better recovery if don't skip delims of patterns - var skip = !(isComma || isAnyBrace) - val msg = if (!opstack.isEmpty && opstack.head.operator == nme.STAR) { - opstack.head.operand match { - case Ident(nme.WILDCARD) => - if (isSequenceOK && isComma) - "bad use of _* (a sequence pattern must be the last pattern)" - else if (isSequenceOK && isAnyBrace) { - skip = true // do skip bad paren; scanner may skip bad brace already - "bad brace or paren after _*" - } else if (!isSequenceOK && isAnyBrace) - "bad use of _* (sequence pattern not allowed)" - else badStart - case _ => - if (isSequenceOK && isAnyBrace) - "use _* to match a sequence" - else if (isComma || isAnyBrace) - "trailing * is not a valid pattern" - else badStart - } - } else { - badStart - } + val skip = !isCommaOrDelimeter || isSeqPatternClose syntaxErrorOrIncompleteAnd(msg, skip)(errorPatternTree) } diff --git a/test/files/neg/t421.check b/test/files/neg/t421.check index dc5fa425ac..d16e541868 100644 --- a/test/files/neg/t421.check +++ b/test/files/neg/t421.check @@ -1,4 +1,4 @@ t421.scala:5: error: star patterns must correspond with varargs parameters case Bar("foo",_*) => sys.error("huh?"); - ^ + ^ one error found diff --git a/test/files/neg/t5702-neg-bad-and-wild.check b/test/files/neg/t5702-neg-bad-and-wild.check index eae81ad5f2..3970c755bd 100644 --- a/test/files/neg/t5702-neg-bad-and-wild.check +++ b/test/files/neg/t5702-neg-bad-and-wild.check @@ -13,7 +13,7 @@ t5702-neg-bad-and-wild.scala:14: error: use _* to match a sequence t5702-neg-bad-and-wild.scala:15: error: trailing * is not a valid pattern case List(x*, 1) => // trailing * is not a valid pattern ^ -t5702-neg-bad-and-wild.scala:16: error: trailing * is not a valid pattern +t5702-neg-bad-and-wild.scala:16: error: illegal start of simple pattern case (1, x*) => // trailing * is not a valid pattern ^ t5702-neg-bad-and-wild.scala:17: error: bad use of _* (sequence pattern not allowed) diff --git a/test/files/neg/t5702-neg-bad-brace.check b/test/files/neg/t5702-neg-bad-brace.check index 503f7d95ed..a4a00814d3 100644 --- a/test/files/neg/t5702-neg-bad-brace.check +++ b/test/files/neg/t5702-neg-bad-brace.check @@ -1,10 +1,7 @@ t5702-neg-bad-brace.scala:14: error: Unmatched closing brace '}' ignored here case List(1, _*} => ^ -t5702-neg-bad-brace.scala:14: error: illegal start of simple pattern - case List(1, _*} => - ^ -t5702-neg-bad-brace.scala:15: error: ')' expected but '}' found. - } - ^ -three errors found +t5702-neg-bad-brace.scala:17: error: eof expected but '}' found. +} +^ +two errors found -- cgit v1.2.3