!par upgrade akka.parboiled internal copy to parboiled 2.1.0

This commit is contained in:
Mathias 2015-09-28 17:32:27 +02:00
parent cc661409f9
commit aed50bc07d
15 changed files with 3293 additions and 580 deletions

View file

@ -16,8 +16,6 @@
package akka.parboiled2
import scala.language.experimental.macros
import scala.collection.immutable
import scala.reflect.macros.Context
import akka.shapeless.HList

View file

@ -0,0 +1,277 @@
/*
* Copyright (C) 2009-2013 Mathias Doenitz, Alexander Myltsev
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package akka.parboiled2
import scala.annotation.tailrec
import java.lang.{ StringBuilder JStringBuilder }
import scala.collection.immutable.VectorBuilder
/**
* Abstraction for error formatting logic.
* Instantiate with a custom configuration or override with custom logic.
*
* @param showExpected whether a description of the expected input is to be shown
* @param showPosition whether the error position is to be shown
* @param showLine whether the input line with a error position indicator is to be shown
* @param showTraces whether the error's rule trace are to be shown
* @param showFrameStartOffset whether formatted traces should include the frame start offset
* @param expandTabs whether and how tabs in the error input line are to be expanded.
* The value indicates the column multiples that a tab represents
* (equals the number of spaces that a leading tab is expanded into).
* Set to a value < 0 to disable tab expansion.
* @param traceCutOff the maximum number of (trailing) characters shown for a rule trace
*/
class ErrorFormatter(showExpected: Boolean = true,
showPosition: Boolean = true,
showLine: Boolean = true,
showTraces: Boolean = false,
showFrameStartOffset: Boolean = true,
expandTabs: Int = -1,
traceCutOff: Int = 120) {
/**
* Formats the given [[ParseError]] into a String using the settings configured for this formatter instance.
*/
def format(error: ParseError, input: ParserInput): String =
format(new JStringBuilder(128), error, input).toString
/**
* Formats the given [[ParseError]] into the given StringBuilder
* using the settings configured for this formatter instance.
*/
def format(sb: JStringBuilder, error: ParseError, input: ParserInput): JStringBuilder = {
formatProblem(sb, error, input)
import error._
if (showExpected) formatExpected(sb, error)
if (showPosition) sb.append(" (line ").append(position.line).append(", column ").append(position.column).append(')')
if (showLine) formatErrorLine(sb.append(':').append('\n'), error, input)
if (showTraces) sb.append('\n').append('\n').append(formatTraces(error)) else sb
}
/**
* Formats a description of the error's cause into a single line String.
*/
def formatProblem(error: ParseError, input: ParserInput): String =
formatProblem(new JStringBuilder(64), error, input).toString
/**
* Formats a description of the error's cause into the given StringBuilder.
*/
def formatProblem(sb: JStringBuilder, error: ParseError, input: ParserInput): JStringBuilder = {
val ix = error.position.index
if (ix < input.length) {
val chars = mismatchLength(error)
if (chars == 1) sb.append("Invalid input '").append(CharUtils.escape(input charAt ix)).append(''')
else sb.append("Invalid input \"").append(CharUtils.escape(input.sliceString(ix, ix + chars))).append('"')
} else sb.append("Unexpected end of input")
}
/**
* Determines the number of characters to be shown as "mismatched" for the given [[ParseError]].
*/
def mismatchLength(error: ParseError): Int =
// Failing negative syntactic predicates, i.e. with a succeeding inner match, do not contribute
// to advancing the principal error location (PEL). Therefore it might be that their succeeding inner match
// reaches further than the PEL. In these cases we want to show the complete inner match as "mismatched",
// not just the piece up to the PEL. This is what this method corrects for.
error.effectiveTraces.foldLeft(error.principalPosition.index - error.position.index + 1) { (len, trace)
import RuleTrace._
trace.terminal match {
case NotPredicate(_, x)
math.max(trace.prefix.collectFirst { case NonTerminal(Atomic, off) off + x } getOrElse x, len)
case _ len
}
}
/**
* Formats what is expected at the error location into a single line String including text padding.
*/
def formatExpected(error: ParseError): String =
formatExpected(new JStringBuilder(64), error).toString
/**
* Formats what is expected at the error location into the given StringBuilder including text padding.
*/
def formatExpected(sb: JStringBuilder, error: ParseError): JStringBuilder =
sb.append(", expected ").append(formatExpectedAsString(error))
/**
* Formats what is expected at the error location into a single line String.
*/
def formatExpectedAsString(error: ParseError): String =
formatExpectedAsString(new JStringBuilder(64), error).toString
/**
* Formats what is expected at the error location into the given StringBuilder.
*/
def formatExpectedAsString(sb: JStringBuilder, error: ParseError): JStringBuilder = {
@tailrec def rec(remaining: List[String]): JStringBuilder =
remaining match {
case Nil sb.append("???")
case head :: Nil sb.append(head)
case head :: last :: Nil sb.append(head).append(" or ").append(last)
case head :: tail sb.append(head).append(", "); rec(tail)
}
rec(formatExpectedAsList(error))
}
/**
* Formats what is expected at the error location as a [[List]] of Strings.
*/
def formatExpectedAsList(error: ParseError): List[String] = {
val distinctStrings: Set[String] = error.effectiveTraces.map(formatAsExpected)(collection.breakOut)
distinctStrings.toList
}
/**
* Formats the given trace into an "expected" string.
*/
def formatAsExpected(trace: RuleTrace): String =
if (trace.prefix.isEmpty) formatTerminal(trace.terminal)
else formatNonTerminal(trace.prefix.head, showFrameStartOffset = false)
/**
* Formats the input line in which the error occurred and underlines
* the given error's position in the line with a caret.
*/
def formatErrorLine(error: ParseError, input: ParserInput): String =
formatErrorLine(new JStringBuilder(64), error, input).toString
/**
* Formats the input line in which the error occurred and underlines
* the given error's position in the line with a caret.
*/
def formatErrorLine(sb: JStringBuilder, error: ParseError, input: ParserInput): JStringBuilder = {
import error.position._
val (expandedCol, expandedLine) = expandErrorLineTabs(input getLine line, column)
sb.append(expandedLine).append('\n')
for (i 1 until expandedCol) sb.append(' ')
sb.append('^')
}
/**
* Performs tab expansion as configured by the `expandTabs` member.
* The `errorColumn` as well as the returned [[Int]] value are both 1-based.
*/
def expandErrorLineTabs(line: String, errorColumn: Int): (Int, String) = {
val sb = new StringBuilder
@tailrec def rec(inCol: Int, errorCol: Int): Int =
if (inCol < line.length) {
val ec = if (inCol == errorColumn - 1) sb.length else errorCol
line.charAt(inCol) match {
case '\t' sb.append(new String(Array.fill[Char](expandTabs - (sb.length % expandTabs))(' ')))
case c sb.append(c)
}
rec(inCol + 1, ec)
} else errorCol + 1
if (expandTabs >= 0) rec(0, 0) -> sb.toString()
else errorColumn -> line
}
/**
* Formats a [[Vector]] of [[RuleTrace]] instances into a String.
*/
def formatTraces(error: ParseError): String = {
import error._
traces.map(formatTrace(_, position.index)).mkString(traces.size + " rule" + (if (traces.size != 1) "s" else "") +
" mismatched at error location:\n ", "\n ", "\n")
}
/**
* Formats a [[RuleTrace]] into a String.
*/
def formatTrace(trace: RuleTrace, errorIndex: Int): String = {
import RuleTrace._
val sb = new JStringBuilder
val doSep: String JStringBuilder = sb.append
val dontSep: String JStringBuilder = _ sb
def render(names: List[String], sep: String = "") = if (names.nonEmpty) names.reverse.mkString("", ":", sep) else ""
@tailrec def rec(remainingPrefix: List[RuleTrace.NonTerminal], names: List[String],
sep: String JStringBuilder): JStringBuilder =
remainingPrefix match {
case NonTerminal(Named(name), _) :: tail
rec(tail, name :: names, sep)
case NonTerminal(RuleCall, _) :: tail
sep(" ").append('/').append(render(names)).append("/ ")
rec(tail, Nil, dontSep)
case NonTerminal(Sequence, _) :: tail if names.isEmpty
rec(tail, Nil, sep)
case NonTerminal(Sequence, _) :: tail
sep(" / ").append(render(names))
rec(tail, Nil, doSep)
case x :: tail
sep(" / ").append(render(names, ":")).append(formatNonTerminal(x))
rec(tail, Nil, doSep)
case Nil
sep(" / ").append(render(names, ":")).append(formatTerminal(trace.terminal))
}
rec(trace.prefix, Nil, dontSep)
if (sb.length > traceCutOff) "..." + sb.substring(math.max(sb.length - traceCutOff - 3, 0)) else sb.toString
}
/**
* Formats the head element of a [[RuleTrace]] into a String.
*/
def formatNonTerminal(nonTerminal: RuleTrace.NonTerminal,
showFrameStartOffset: Boolean = showFrameStartOffset): String = {
import RuleTrace._
import CharUtils.escape
val keyString = nonTerminal.key match {
case Action "<action>"
case Atomic "atomic"
case AndPredicate "&"
case Capture "capture"
case Cut "cut"
case FirstOf "|"
case x: IgnoreCaseString '"' + escape(x.string) + '"'
case x: MapMatch x.map.toString()
case x: Named x.name
case OneOrMore "+"
case Optional "?"
case Quiet "quiet"
case RuleCall "call"
case Run "<run>"
case RunSubParser "runSubParser"
case Sequence "~"
case x: StringMatch '"' + escape(x.string) + '"'
case x: Times "times"
case ZeroOrMore "*"
}
if (nonTerminal.offset != 0 && showFrameStartOffset) keyString + ':' + nonTerminal.offset else keyString
}
def formatTerminal(terminal: RuleTrace.Terminal): String = {
import RuleTrace._
import CharUtils.escape
terminal match {
case ANY "ANY"
case AnyOf(s) '[' + escape(s) + ']'
case CharMatch(c) "'" + escape(c) + '\''
case CharPredicateMatch(_) "<CharPredicate>"
case CharRange(from, to) s"'${escape(from)}'-'${escape(to)}'"
case Fail(expected) expected
case IgnoreCaseChar(c) "'" + escape(c) + '\''
case NoneOf(s) s"[^${escape(s)}]"
case NotPredicate(NotPredicate.Terminal(t), _) "!" + formatTerminal(t)
case NotPredicate(NotPredicate.RuleCall(t), _) "!" + t
case NotPredicate(NotPredicate.Named(n), _) "!" + n
case NotPredicate(NotPredicate.Anonymous, _) "!<anon>"
case SemanticPredicate "test"
}
}
}

View file

@ -16,112 +16,142 @@
package akka.parboiled2
import CharUtils.escape
import scala.annotation.tailrec
import scala.collection.immutable
case class ParseError(position: Position, traces: Seq[RuleTrace]) extends RuntimeException {
def formatExpectedAsString: String = {
val expected = formatExpectedAsSeq
expected.size match {
case 0 "??"
case 1 expected.head
case _ expected.init.mkString(", ") + " or " + expected.last
case class ParseError(position: Position,
principalPosition: Position,
traces: immutable.Seq[RuleTrace]) extends RuntimeException {
require(principalPosition.index >= position.index, "principalPosition must be > position")
def format(parser: Parser): String = format(parser.input)
def format(parser: Parser, formatter: ErrorFormatter): String = format(parser.input, formatter)
def format(input: ParserInput): String = format(input, new ErrorFormatter())
def format(input: ParserInput, formatter: ErrorFormatter): String = formatter.format(this, input)
override def toString = s"ParseError($position, $principalPosition, <${traces.size} traces>)"
lazy val effectiveTraces: immutable.Seq[RuleTrace] =
traces map {
val commonPrefixLen = RuleTrace.commonNonAtomicPrefixLength(traces)
if (commonPrefixLen > 0) t t.copy(prefix = t.prefix.drop(commonPrefixLen)).dropUnreportedPrefix
else _.dropUnreportedPrefix
}
}
def formatExpectedAsSeq: Seq[String] =
traces.map { trace
if (trace.frames.nonEmpty) {
val exp = trace.frames.last.format
val nonEmptyExp = if (exp.isEmpty) "?" else exp
if (trace.isNegated) "!" + nonEmptyExp else nonEmptyExp
} else "???"
}.distinct
def formatTraces: String =
traces.map(_.format).mkString(traces.size + " rule" + (if (traces.size != 1) "s" else "") +
" mismatched at error location:\n ", "\n ", "\n")
}
/**
* Defines a position in an [[ParserInput]].
*
* @param index index into the input buffer (0-based)
* @param line the text line the error occurred in (1-based)
* @param column the text column the error occurred in (1-based)
*/
case class Position(index: Int, line: Int, column: Int)
// outermost (i.e. highest-level) rule first
case class RuleTrace(frames: Seq[RuleFrame]) {
def format: String =
frames.size match {
case 0 "<empty>"
case 1 frames.head.format
case _
// we don't want to show intermediate Sequence and RuleCall frames in the trace
def show(frame: RuleFrame) = !(frame.isInstanceOf[RuleFrame.Sequence] || frame.isInstanceOf[RuleFrame.RuleCall])
frames.init.filter(show).map(_.format).mkString("", " / ", " / " + frames.last.format)
}
def isNegated: Boolean = (frames.count(_.anon == RuleFrame.NotPredicate) & 0x01) > 0
}
sealed abstract class RuleFrame {
import RuleFrame._
def anon: RuleFrame.Anonymous
def format: String =
this match {
case Named(name, _) name
case Sequence(_) "~"
case FirstOf(_) "|"
case CharMatch(c) "'" + escape(c) + '\''
case StringMatch(s) '"' + escape(s) + '"'
case MapMatch(m) m.toString()
case IgnoreCaseChar(c) "'" + escape(c) + '\''
case IgnoreCaseString(s) '"' + escape(s) + '"'
case CharPredicateMatch(_, name) if (name.nonEmpty) name else "<anon predicate>"
case RuleCall(callee) '(' + callee + ')'
case AnyOf(s) '[' + escape(s) + ']'
case NoneOf(s) s"[^${escape(s)}]"
case Times(_, _) "times"
case CharRange(from, to) s"'${escape(from)}'-'${escape(to)}'"
case AndPredicate "&"
case NotPredicate "!"
case SemanticPredicate "test"
case ANY "ANY"
case _ {
val s = toString
s.updated(0, s.charAt(0).toLower)
}
}
}
object RuleFrame {
def apply(frame: Anonymous, name: String): RuleFrame =
if (name.isEmpty) frame else Named(name, frame)
case class Named(name: String, anon: Anonymous) extends RuleFrame
sealed abstract class Anonymous extends RuleFrame {
def anon: Anonymous = this
object Position {
def apply(index: Int, input: ParserInput): Position = {
@tailrec def rec(ix: Int, line: Int, col: Int): Position =
if (ix >= index) Position(index, line, col)
else if (ix >= input.length || input.charAt(ix) != '\n') rec(ix + 1, line, col + 1)
else rec(ix + 1, line + 1, 1)
rec(ix = 0, line = 1, col = 1)
}
case class Sequence(subs: Int) extends Anonymous
case class FirstOf(subs: Int) extends Anonymous
case class CharMatch(char: Char) extends Anonymous
case class StringMatch(string: String) extends Anonymous
case class MapMatch(map: Map[String, Any]) extends Anonymous
case class IgnoreCaseChar(char: Char) extends Anonymous
case class IgnoreCaseString(string: String) extends Anonymous
case class CharPredicateMatch(predicate: CharPredicate, name: String) extends Anonymous
case class AnyOf(string: String) extends Anonymous
case class NoneOf(string: String) extends Anonymous
case class Times(min: Int, max: Int) extends Anonymous
case class RuleCall(callee: String) extends Anonymous
case class CharRange(from: Char, to: Char) extends Anonymous
case object ANY extends Anonymous
case object Optional extends Anonymous
case object ZeroOrMore extends Anonymous
case object OneOrMore extends Anonymous
case object AndPredicate extends Anonymous
case object NotPredicate extends Anonymous
case object SemanticPredicate extends Anonymous
case object Capture extends Anonymous
case object Run extends Anonymous
case object Push extends Anonymous
case object Drop extends Anonymous
case object Action extends Anonymous
case object RunSubParser extends Anonymous
}
case class RuleTrace(prefix: List[RuleTrace.NonTerminal], terminal: RuleTrace.Terminal) {
import RuleTrace._
/**
* Returns a RuleTrace starting with the first [[RuleTrace.Atomic]] element or the first sub-trace whose
* offset from the reported error index is zero (e.g. the [[RuleTrace.Terminal]]).
* If this is wrapped in one or more [[RuleTrace.NonTerminal.Named]] the outermost of these is returned instead.
*/
def dropUnreportedPrefix: RuleTrace = {
@tailrec def rec(current: List[NonTerminal], named: List[NonTerminal]): List[NonTerminal] =
current match {
case NonTerminal(Named(_), _) :: tail rec(tail, if (named.isEmpty) current else named)
case NonTerminal(RuleCall, _) :: tail rec(tail, named) // RuleCall elements allow the name to be carried over
case NonTerminal(Atomic, _) :: tail if (named.isEmpty) tail else named
case x :: tail if (x.offset >= 0 && named.nonEmpty) named else rec(tail, Nil)
case Nil named
}
val newPrefix = rec(prefix, Nil)
if (newPrefix ne prefix) copy(prefix = newPrefix) else this
}
/**
* Wraps this trace with a [[RuleTrace.Named]] wrapper if the given name is non-empty.
*/
def named(name: String): RuleTrace = {
val newHead = NonTerminal(Named(name), if (prefix.isEmpty) 0 else prefix.head.offset)
if (name.isEmpty) this else copy(prefix = newHead :: prefix)
}
}
object RuleTrace {
def commonNonAtomicPrefixLength(traces: Seq[RuleTrace]): Int =
if (traces.size > 1) {
val tracesTail = traces.tail
def hasElem(ix: Int, elem: NonTerminal): RuleTrace Boolean =
_.prefix.drop(ix) match {
case `elem` :: _ true
case _ false
}
@tailrec def rec(current: List[NonTerminal], namedIx: Int, ix: Int): Int =
current match {
case head :: tail if tracesTail forall hasElem(ix, head)
head.key match {
case Named(_) rec(tail, if (namedIx >= 0) namedIx else ix, ix + 1)
case RuleCall rec(tail, namedIx, ix + 1) // RuleCall elements allow the name to be carried over
case Atomic if (namedIx >= 0) namedIx else ix // Atomic elements always terminate a common prefix
case _ rec(tail, -1, ix + 1) // otherwise the name chain is broken
}
case _ if (namedIx >= 0) namedIx else ix
}
rec(traces.head.prefix, namedIx = -1, ix = 0)
} else 0
// offset: the number of characters before the reported error index that the rule corresponding
// to this trace head started matching.
final case class NonTerminal(key: NonTerminalKey, offset: Int)
sealed trait NonTerminalKey
case object Action extends NonTerminalKey
case object AndPredicate extends NonTerminalKey
case object Atomic extends NonTerminalKey
case object Capture extends NonTerminalKey
case object Cut extends NonTerminalKey
case object FirstOf extends NonTerminalKey
final case class IgnoreCaseString(string: String) extends NonTerminalKey
final case class MapMatch(map: Map[String, Any]) extends NonTerminalKey
final case class Named(name: String) extends NonTerminalKey
case object OneOrMore extends NonTerminalKey
case object Optional extends NonTerminalKey
case object Quiet extends NonTerminalKey
case object RuleCall extends NonTerminalKey
case object Run extends NonTerminalKey
case object RunSubParser extends NonTerminalKey
case object Sequence extends NonTerminalKey
final case class StringMatch(string: String) extends NonTerminalKey
final case class Times(min: Int, max: Int) extends NonTerminalKey
case object ZeroOrMore extends NonTerminalKey
sealed trait Terminal
case object ANY extends Terminal
final case class AnyOf(string: String) extends Terminal
final case class CharMatch(char: Char) extends Terminal
final case class CharPredicateMatch(predicate: CharPredicate) extends Terminal
final case class CharRange(from: Char, to: Char) extends Terminal
final case class Fail(expected: String) extends Terminal
final case class IgnoreCaseChar(char: Char) extends Terminal
final case class NoneOf(string: String) extends Terminal
final case class NotPredicate(base: NotPredicate.Base, baseMatchLength: Int) extends Terminal
case object SemanticPredicate extends Terminal
object NotPredicate {
sealed trait Base
case object Anonymous extends Base
final case class Named(name: String) extends Base
final case class RuleCall(target: String) extends Base
final case class Terminal(terminal: RuleTrace.Terminal) extends Base
}
}

View file

@ -39,6 +39,12 @@ abstract class Parser(initialValueStackSize: Int = 16,
*/
def rule[I <: HList, O <: HList](r: Rule[I, O]): Rule[I, O] = macro ParserMacros.ruleImpl[I, O]
/**
* Converts a compile-time only rule definition into the corresponding rule method implementation
* with an explicitly given name.
*/
def namedRule[I <: HList, O <: HList](name: String)(r: Rule[I, O]): Rule[I, O] = macro ParserMacros.namedRuleImpl[I, O]
/**
* The index of the next (yet unmatched) input character.
* Might be equal to `input.length`!
@ -65,7 +71,7 @@ abstract class Parser(initialValueStackSize: Int = 16,
* i.e. depending on the ParserInput implementation you might get an exception
* when calling this method before any character was matched by the parser.
*/
def charAt(offset: Int): Char = input.charAt(cursor + offset)
def charAt(offset: Int): Char = input.charAt(_cursor + offset)
/**
* Same as `charAt` but range-checked.
@ -73,7 +79,7 @@ abstract class Parser(initialValueStackSize: Int = 16,
* If this index is out of range the method returns `EOI`.
*/
def charAtRC(offset: Int): Char = {
val ix = cursor + offset
val ix = _cursor + offset
if (0 <= ix && ix < input.length) input.charAt(ix) else EOI
}
@ -85,33 +91,18 @@ abstract class Parser(initialValueStackSize: Int = 16,
def valueStack: ValueStack = _valueStack
/**
* Pretty prints the given `ParseError` instance in the context of the `ParserInput` of this parser.
* The maximum number of error traces that parser will collect in case of a parse error.
* Override with a custom value if required.
* Set to zero to completely disable error trace collection (which will cause `formatError`
* to no be able to render any "expected" string!).
*/
def formatError(error: ParseError, showExpected: Boolean = true, showPosition: Boolean = true,
showLine: Boolean = true, showTraces: Boolean = false): String = {
val sb = new java.lang.StringBuilder(formatErrorProblem(error))
import error._
if (showExpected) sb.append(", expected ").append(formatExpectedAsString)
if (showPosition) sb.append(" (line ").append(position.line).append(", column ").append(position.column).append(')')
if (showLine) sb.append(':').append('\n').append(formatErrorLine(error))
if (showTraces) sb.append('\n').append('\n').append(formatTraces)
sb.toString
}
def errorTraceCollectionLimit: Int = 24
/**
* Pretty prints the input line in which the error occurred and underlines the error position in the line
* with a caret.
* Formats the given [[ParseError]] into a String using the given formatter instance.
*/
def formatErrorProblem(error: ParseError): String =
if (error.position.index < input.length) s"Invalid input '${CharUtils.escape(input charAt error.position.index)}'"
else "Unexpected end of input"
/**
* Pretty prints the input line in which the error occurred and underlines the error position in the line
* with a caret.
*/
def formatErrorLine(error: ParseError): String =
(input getLine error.position.line) + '\n' + (" " * (error.position.column - 1) + '^')
def formatError(error: ParseError, formatter: ErrorFormatter = new ErrorFormatter()): String =
formatter.format(error, input)
////////////////////// INTERNAL /////////////////////////
@ -124,77 +115,109 @@ abstract class Parser(initialValueStackSize: Int = 16,
// the value stack instance we operate on
private var _valueStack: ValueStack = _
// the highest input index we have seen in the current run
// special value: -1 (not collecting errors)
private var maxCursor: Int = _
// the number of times we have already seen a character mismatch at the error index
private var mismatchesAtErrorCursor: Int = _
// the index of the RuleStack we are currently constructing
// for the ParseError to be (potentially) returned in the current parser run,
// special value: -1 (during the run to establish the error location (maxCursor))
private var currentErrorRuleStackIx: Int = _
// the current ErrorAnalysisPhase or null (in the initial run)
private var phase: ErrorAnalysisPhase = _
def copyStateFrom(other: Parser, offset: Int): Unit = {
_cursorChar = other._cursorChar
_cursor = other._cursor - offset
_valueStack = other._valueStack
maxCursor = other.maxCursor - offset
mismatchesAtErrorCursor = other.mismatchesAtErrorCursor
currentErrorRuleStackIx = other.currentErrorRuleStackIx
phase = other.phase
if (phase ne null) phase.applyOffset(offset)
}
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
def __collectingErrors = maxCursor >= 0
def __inErrorAnalysis = phase ne null
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
def __run[L <: HList](rule: RuleN[L])(implicit scheme: Parser.DeliveryScheme[L]): scheme.Result = {
def runRule(errorRuleStackIx: Int = -1): Boolean = {
def runRule(): Boolean = {
_cursor = -1
__advance()
valueStack.clear()
mismatchesAtErrorCursor = 0
currentErrorRuleStackIx = errorRuleStackIx
rule ne null
try rule ne null
catch {
case CutError false
}
}
def phase0_initialRun() = {
_valueStack = new ValueStack(initialValueStackSize, maxValueStackSize)
runRule()
}
def phase1_establishPrincipalErrorIndex(): Int = {
val phase1 = new EstablishingPrincipalErrorIndex()
phase = phase1
if (runRule()) sys.error("Parsing unexpectedly succeeded while trying to establish the principal error location")
phase1.maxCursor
}
def phase2_establishReportedErrorIndex(principalErrorIndex: Int) = {
val phase2 = new EstablishingReportedErrorIndex(principalErrorIndex)
phase = phase2
if (runRule()) sys.error("Parsing unexpectedly succeeded while trying to establish the reported error location")
phase2
}
def phase3_determineReportQuiet(reportedErrorIndex: Int): Boolean = {
phase = new DetermineReportQuiet(reportedErrorIndex)
try {
if (runRule()) sys.error("Parsing unexpectedly succeeded while trying to determine quiet reporting")
true // if we got here we can only reach the reportedErrorIndex via quiet rules
} catch {
case UnquietMismatch false // we mismatched beyond the reportedErrorIndex outside of a quiet rule
}
}
@tailrec
def errorPosition(ix: Int = 0, line: Int = 1, col: Int = 1): Position =
if (ix >= maxCursor) Position(maxCursor, line, col)
else if (ix >= input.length || input.charAt(ix) != '\n') errorPosition(ix + 1, line, col + 1)
else errorPosition(ix + 1, line + 1, 1)
def phase4_collectRuleTraces(reportedErrorIndex: Int, principalErrorIndex: Int, reportQuiet: Boolean)(
phase3: CollectingRuleTraces = new CollectingRuleTraces(reportedErrorIndex, reportQuiet),
traces: VectorBuilder[RuleTrace] = new VectorBuilder): ParseError = {
@tailrec
def buildParseError(errorRuleIx: Int = 0, traces: VectorBuilder[RuleTrace] = new VectorBuilder): ParseError = {
val ruleFrames: List[RuleFrame] =
try {
runRule(errorRuleIx)
Nil // we managed to complete the run w/o exception, i.e. we have collected all frames
} catch {
case e: Parser.CollectingRuleStackException e.ruleFrames
}
if (ruleFrames.isEmpty) ParseError(errorPosition(), traces.result())
else buildParseError(errorRuleIx + 1, traces += RuleTrace(ruleFrames.toVector))
def done = {
val principalErrorPos = Position(principalErrorIndex, input)
val reportedErrorPos = if (reportedErrorIndex != principalErrorIndex) Position(reportedErrorIndex, input) else principalErrorPos
ParseError(reportedErrorPos, principalErrorPos, traces.result())
}
if (phase3.traceNr < errorTraceCollectionLimit) {
val trace: RuleTrace =
try {
phase = phase3
runRule()
null // we managed to complete the run w/o exception, i.e. we have collected all traces
} catch {
case e: TracingBubbleException e.trace
}
if (trace eq null) done
else phase4_collectRuleTraces(reportedErrorIndex, principalErrorIndex,
reportQuiet)(new CollectingRuleTraces(reportedErrorIndex, reportQuiet, phase3.traceNr + 1), traces += trace)
} else done
}
_valueStack = new ValueStack(initialValueStackSize, maxValueStackSize)
try {
maxCursor = -1
if (runRule())
if (phase0_initialRun())
scheme.success(valueStack.toHList[L]())
else {
maxCursor = 0 // establish the error location with the next run
if (runRule()) sys.error("Parsing unexpectedly succeeded while trying to establish the error location")
// now maxCursor holds the error location, we can now build the parser error info
scheme.parseError(buildParseError())
val principalErrorIndex = phase1_establishPrincipalErrorIndex()
val p2 = phase2_establishReportedErrorIndex(principalErrorIndex)
val reportQuiet = phase3_determineReportQuiet(principalErrorIndex)
val parseError = phase4_collectRuleTraces(p2.reportedErrorIndex, principalErrorIndex, reportQuiet)()
scheme.parseError(parseError)
}
} catch {
case NonFatal(e) scheme.failure(e)
case e: Parser.Fail
val pos = Position(cursor, input)
scheme.parseError(ParseError(pos, pos, RuleTrace(Nil, RuleTrace.Fail(e.expected)) :: Nil))
case NonFatal(e)
e.printStackTrace()
scheme.failure(e)
} finally {
phase = null
}
}
@ -216,7 +239,10 @@ abstract class Parser(initialValueStackSize: Int = 16,
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
def __updateMaxCursor(): Boolean = {
if (_cursor > maxCursor) maxCursor = _cursor
phase match {
case x: EstablishingPrincipalErrorIndex if (_cursor > x.maxCursor) x.maxCursor = _cursor
case _ // nothing to do
}
true
}
@ -237,28 +263,100 @@ abstract class Parser(initialValueStackSize: Int = 16,
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
def __enterNotPredicate: Int = {
val saved = maxCursor
maxCursor = -1 // disables maxCursor update as well as error rulestack collection
def __enterNotPredicate(): AnyRef = {
val saved = phase
phase = null
saved
}
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
def __exitNotPredicate(saved: Int): Unit = maxCursor = saved
def __exitNotPredicate(saved: AnyRef): Unit = phase = saved.asInstanceOf[ErrorAnalysisPhase]
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
def __enterAtomic(start: Int): Boolean =
phase match {
case null false
case x: EstablishingReportedErrorIndex if x.currentAtomicStart == Int.MinValue
x.currentAtomicStart = start
true
case _ false
}
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
def __exitAtomic(saved: Boolean): Unit =
if (saved) {
phase match {
case x: EstablishingReportedErrorIndex x.currentAtomicStart = Int.MinValue
case _ throw new IllegalStateException
}
}
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
def __enterQuiet(): Int =
phase match {
case x: DetermineReportQuiet
if (x.inQuiet) -1
else {
x.inQuiet = true
0
}
case x: CollectingRuleTraces if !x.reportQuiet
val saved = x.minErrorIndex
x.minErrorIndex = Int.MaxValue // disables triggering of StartTracingException in __registerMismatch
saved
case _ -1
}
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
def __exitQuiet(saved: Int): Unit =
if (saved >= 0) {
phase match {
case x: DetermineReportQuiet x.inQuiet = false
case x: CollectingRuleTraces x.minErrorIndex = saved
case _ throw new IllegalStateException
}
}
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
def __registerMismatch(): Boolean = {
if (currentErrorRuleStackIx >= 0 && _cursor == maxCursor) {
if (mismatchesAtErrorCursor < currentErrorRuleStackIx) mismatchesAtErrorCursor += 1
else throw new Parser.CollectingRuleStackException
phase match {
case null | _: EstablishingPrincipalErrorIndex // nothing to do
case x: CollectingRuleTraces
if (_cursor >= x.minErrorIndex) {
if (x.errorMismatches == x.traceNr) throw Parser.StartTracingException else x.errorMismatches += 1
}
case x: EstablishingReportedErrorIndex
if (x.currentAtomicStart > x.maxAtomicErrorStart) x.maxAtomicErrorStart = x.currentAtomicStart
case x: DetermineReportQuiet
// stop this run early because we just learned that reporting quiet traces is unnecessary
if (_cursor >= x.minErrorIndex & !x.inQuiet) throw UnquietMismatch
}
false
}
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
def __bubbleUp(terminal: RuleTrace.Terminal): Nothing = __bubbleUp(Nil, terminal)
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
def __bubbleUp(prefix: List[RuleTrace.NonTerminal], terminal: RuleTrace.Terminal): Nothing =
throw new TracingBubbleException(RuleTrace(prefix, terminal))
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
@ -276,7 +374,7 @@ abstract class Parser(initialValueStackSize: Int = 16,
*/
@tailrec final def __matchString(string: String, ix: Int = 0): Boolean =
if (ix < string.length)
if (cursorChar == string.charAt(ix)) {
if (_cursorChar == string.charAt(ix)) {
__advance()
__matchString(string, ix + 1)
} else false
@ -285,17 +383,18 @@ abstract class Parser(initialValueStackSize: Int = 16,
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
@tailrec final def __matchStringWrapped(string: String, ruleName: String, ix: Int = 0): Boolean =
@tailrec final def __matchStringWrapped(string: String, ix: Int = 0): Boolean =
if (ix < string.length)
if (cursorChar == string.charAt(ix)) {
if (_cursorChar == string.charAt(ix)) {
__advance()
__updateMaxCursor()
__matchStringWrapped(string, ruleName, ix + 1)
__matchStringWrapped(string, ix + 1)
} else {
try __registerMismatch()
catch {
case e: Parser.CollectingRuleStackException
e.save(RuleFrame(RuleFrame.StringMatch(string), ruleName), RuleFrame.CharMatch(string charAt ix))
case Parser.StartTracingException
import RuleTrace._
__bubbleUp(NonTerminal(StringMatch(string), -ix) :: Nil, CharMatch(string charAt ix))
}
}
else true
@ -305,7 +404,7 @@ abstract class Parser(initialValueStackSize: Int = 16,
*/
@tailrec final def __matchIgnoreCaseString(string: String, ix: Int = 0): Boolean =
if (ix < string.length)
if (Character.toLowerCase(cursorChar) == string.charAt(ix)) {
if (Character.toLowerCase(_cursorChar) == string.charAt(ix)) {
__advance()
__matchIgnoreCaseString(string, ix + 1)
} else false
@ -314,17 +413,18 @@ abstract class Parser(initialValueStackSize: Int = 16,
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
@tailrec final def __matchIgnoreCaseStringWrapped(string: String, ruleName: String, ix: Int = 0): Boolean =
@tailrec final def __matchIgnoreCaseStringWrapped(string: String, ix: Int = 0): Boolean =
if (ix < string.length)
if (Character.toLowerCase(cursorChar) == string.charAt(ix)) {
if (Character.toLowerCase(_cursorChar) == string.charAt(ix)) {
__advance()
__updateMaxCursor()
__matchIgnoreCaseStringWrapped(string, ruleName, ix + 1)
__matchIgnoreCaseStringWrapped(string, ix + 1)
} else {
try __registerMismatch()
catch {
case e: Parser.CollectingRuleStackException
e.save(RuleFrame(RuleFrame.IgnoreCaseString(string), ruleName), RuleFrame.IgnoreCaseChar(string charAt ix))
case Parser.StartTracingException
import RuleTrace._
__bubbleUp(NonTerminal(IgnoreCaseString(string), -ix) :: Nil, IgnoreCaseChar(string charAt ix))
}
}
else true
@ -334,7 +434,7 @@ abstract class Parser(initialValueStackSize: Int = 16,
*/
@tailrec final def __matchAnyOf(string: String, ix: Int = 0): Boolean =
if (ix < string.length)
if (string.charAt(ix) == cursorChar) __advance()
if (string.charAt(ix) == _cursorChar) __advance()
else __matchAnyOf(string, ix + 1)
else false
@ -343,7 +443,7 @@ abstract class Parser(initialValueStackSize: Int = 16,
*/
@tailrec final def __matchNoneOf(string: String, ix: Int = 0): Boolean =
if (ix < string.length)
cursorChar != EOI && string.charAt(ix) != cursorChar && __matchNoneOf(string, ix + 1)
_cursorChar != EOI && string.charAt(ix) != _cursorChar && __matchNoneOf(string, ix + 1)
else __advance()
/**
@ -365,25 +465,47 @@ abstract class Parser(initialValueStackSize: Int = 16,
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
def __matchMapWrapped(m: Map[String, Any], ruleName: String): Boolean = {
def __matchMapWrapped(m: Map[String, Any]): Boolean = {
val keys = m.keysIterator
val start = _cursor
try {
while (keys.hasNext) {
val mark = __saveState
val key = keys.next()
if (__matchStringWrapped(key, "")) {
if (__matchStringWrapped(key)) {
__push(m(key))
return true
} else __restoreState(mark)
}
false
} catch {
case e: Parser.CollectingRuleStackException e.save(RuleFrame(RuleFrame.MapMatch(m), ruleName))
case e: TracingBubbleException e.bubbleUp(RuleTrace.MapMatch(m), start)
}
}
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
def __hardFail(expected: String) = throw new Parser.Fail(expected)
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
class TracingBubbleException(private var _trace: RuleTrace) extends RuntimeException with NoStackTrace {
def trace = _trace
def bubbleUp(key: RuleTrace.NonTerminalKey, start: Int): Nothing = throw prepend(key, start)
def prepend(key: RuleTrace.NonTerminalKey, start: Int): this.type = {
val offset = phase match {
case x: CollectingRuleTraces start - x.minErrorIndex
case _ throw new IllegalStateException
}
_trace = _trace.copy(prefix = RuleTrace.NonTerminal(key, offset) :: _trace.prefix)
this
}
}
protected class __SubParserInput extends ParserInput {
val offset = cursor // the number of chars the input the sub-parser sees is offset from the outer input start
val offset = _cursor // the number of chars the input the sub-parser sees is offset from the outer input start
def getLine(line: Int): String = ??? // TODO
def sliceCharArray(start: Int, end: Int): Array[Char] = input.sliceCharArray(start + offset, end + offset)
def sliceString(start: Int, end: Int): String = input.sliceString(start + offset, end + offset)
@ -435,13 +557,71 @@ object Parser {
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
class CollectingRuleStackException extends RuntimeException with NoStackTrace {
private[this] var frames = List.empty[RuleFrame]
def save(newFrames: RuleFrame*): Nothing = {
frames = newFrames.foldRight(frames)(_ :: _)
throw this
object StartTracingException extends RuntimeException with NoStackTrace
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
object CutError extends RuntimeException with NoStackTrace
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
object UnquietMismatch extends RuntimeException with NoStackTrace
/**
* THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
*/
class Fail(val expected: String) extends RuntimeException with NoStackTrace
// error analysis happens in 4 phases:
// 0: initial run, no error analysis
// 1: EstablishingPrincipalErrorIndex (1 run)
// 2: EstablishingReportedErrorIndex (1 run)
// 3: CollectingRuleTraces (n runs)
private sealed trait ErrorAnalysisPhase {
def applyOffset(offset: Int): Unit
}
// establish the max cursor value reached in a run
private class EstablishingPrincipalErrorIndex(var maxCursor: Int = 0) extends ErrorAnalysisPhase {
def applyOffset(offset: Int) = maxCursor -= offset
}
// establish the largest match-start index of all outermost atomic rules
// that we are in when mismatching at the principal error index
// or -1 if no atomic rule fails with a mismatch at the principal error index
private class EstablishingReportedErrorIndex(
private var _principalErrorIndex: Int,
var currentAtomicStart: Int = Int.MinValue,
var maxAtomicErrorStart: Int = Int.MinValue) extends ErrorAnalysisPhase {
def reportedErrorIndex = if (maxAtomicErrorStart >= 0) maxAtomicErrorStart else _principalErrorIndex
def applyOffset(offset: Int) = {
_principalErrorIndex -= offset
if (currentAtomicStart != Int.MinValue) currentAtomicStart -= offset
if (maxAtomicErrorStart != Int.MinValue) maxAtomicErrorStart -= offset
}
def ruleFrames: List[RuleFrame] = frames
}
// determine whether the reported error location can only be reached via quiet rules
// in which case we need to report them even though they are marked as "quiet"
private class DetermineReportQuiet(
private var _minErrorIndex: Int, // the smallest index at which a mismatch triggers a StartTracingException
var inQuiet: Boolean = false // are we currently in a quiet rule?
) extends ErrorAnalysisPhase {
def minErrorIndex = _minErrorIndex
def applyOffset(offset: Int) = _minErrorIndex -= offset
}
// collect the traces of all mismatches happening at an index >= minErrorIndex (the reported error index)
// by throwing a StartTracingException which gets turned into a TracingBubbleException by the terminal rule
private class CollectingRuleTraces(
var minErrorIndex: Int, // the smallest index at which a mismatch triggers a StartTracingException
val reportQuiet: Boolean, // do we need to trace mismatches from quiet rules?
val traceNr: Int = 0, // the zero-based index number of the RuleTrace we are currently building
var errorMismatches: Int = 0 // the number of times we have already seen a mismatch at >= minErrorIndex
) extends ErrorAnalysisPhase {
def applyOffset(offset: Int) = minErrorIndex -= offset
}
}
@ -474,16 +654,26 @@ object ParserMacros {
type ParserContext = Context { type PrefixType = Parser }
def ruleImpl[I <: HList: ctx.WeakTypeTag, O <: HList: ctx.WeakTypeTag](ctx: ParserContext)(r: ctx.Expr[Rule[I, O]]): ctx.Expr[Rule[I, O]] = {
val opTreeCtx = new OpTreeContext[ctx.type] { val c: ctx.type = ctx }
val opTree = opTreeCtx.OpTree(r.tree)
import ctx.universe._
val ruleName =
ctx.enclosingMethod match {
case DefDef(_, name, _, _, _, _) name.decoded
case _ ctx.abort(r.tree.pos, "`rule` can only be used from within a method")
}
reify {
ctx.Expr[RuleX](opTree.renderRule(ruleName)).splice.asInstanceOf[Rule[I, O]]
}
namedRuleImpl(ctx)(ctx.Expr[String](Literal(Constant(ruleName))))(r)
}
def namedRuleImpl[I <: HList: ctx.WeakTypeTag, O <: HList: ctx.WeakTypeTag](ctx: ParserContext)(name: ctx.Expr[String])(r: ctx.Expr[Rule[I, O]]): ctx.Expr[Rule[I, O]] = {
val opTreeCtx = new OpTreeContext[ctx.type] { val c: ctx.type = ctx }
val opTree = opTreeCtx.RuleCall(Left(opTreeCtx.OpTree(r.tree)), name.tree)
import ctx.universe._
val ruleTree = q"""
def wrapped: Boolean = ${opTree.render(wrapped = true)}
val matched =
if (__inErrorAnalysis) wrapped
else ${opTree.render(wrapped = false)}
if (matched) akka.parboiled2.Rule else null""" // we encode the "matched" boolean as 'ruleResult ne null'
reify { ctx.Expr[RuleX](ruleTree).splice.asInstanceOf[Rule[I, O]] }
}
}

View file

@ -16,7 +16,6 @@
package akka.parboiled2
import java.nio.charset.Charset
import scala.annotation.tailrec
import java.nio.ByteBuffer
@ -55,8 +54,10 @@ object ParserInput {
val Empty = apply(Array.empty[Byte])
implicit def apply(bytes: Array[Byte]): ByteArrayBasedParserInput = new ByteArrayBasedParserInput(bytes)
implicit def apply(bytes: Array[Byte], endIndex: Int): ByteArrayBasedParserInput = new ByteArrayBasedParserInput(bytes, endIndex)
implicit def apply(string: String): StringBasedParserInput = new StringBasedParserInput(string)
implicit def apply(chars: Array[Char]): CharArrayBasedParserInput = new CharArrayBasedParserInput(chars)
implicit def apply(chars: Array[Char], endIndex: Int): CharArrayBasedParserInput = new CharArrayBasedParserInput(chars, endIndex)
abstract class DefaultParserInput extends ParserInput {
def getLine(line: Int): String = {
@ -83,11 +84,12 @@ object ParserInput {
* 7-bit ASCII characters (0x00-0x7F) then UTF-8 is fine, since the first 127 UTF-8 characters are
* encoded with only one byte that is identical to 7-bit ASCII and ISO-8859-1.
*/
class ByteArrayBasedParserInput(bytes: Array[Byte]) extends DefaultParserInput {
class ByteArrayBasedParserInput(bytes: Array[Byte], endIndex: Int = 0) extends DefaultParserInput {
val length = if (endIndex <= 0 || endIndex > bytes.length) bytes.length else endIndex
def charAt(ix: Int) = (bytes(ix) & 0xFF).toChar
def length = bytes.length
def sliceString(start: Int, end: Int) = new String(bytes, start, end - start, `ISO-8859-1`)
def sliceCharArray(start: Int, end: Int) = `ISO-8859-1`.decode(ByteBuffer.wrap(bytes)).array()
def sliceCharArray(start: Int, end: Int) =
`ISO-8859-1`.decode(ByteBuffer.wrap(java.util.Arrays.copyOfRange(bytes, start, end))).array()
}
class StringBasedParserInput(string: String) extends DefaultParserInput {
@ -101,9 +103,9 @@ object ParserInput {
}
}
class CharArrayBasedParserInput(chars: Array[Char]) extends DefaultParserInput {
class CharArrayBasedParserInput(chars: Array[Char], endIndex: Int = 0) extends DefaultParserInput {
val length = if (endIndex <= 0 || endIndex > chars.length) chars.length else endIndex
def charAt(ix: Int) = chars(ix)
def length = chars.length
def sliceString(start: Int, end: Int) = new String(chars, start, end - start)
def sliceCharArray(start: Int, end: Int) = java.util.Arrays.copyOfRange(chars, start, end)
}

View file

@ -18,6 +18,7 @@ package akka.parboiled2
import scala.annotation.unchecked.uncheckedVariance
import scala.reflect.internal.annotations.compileTimeOnly
import scala.collection.immutable
import akka.parboiled2.support._
import akka.shapeless.HList
@ -48,6 +49,14 @@ sealed class Rule[-I <: HList, +O <: HList] extends RuleX {
def ~[I2 <: HList, O2 <: HList](that: Rule[I2, O2])(implicit i: TailSwitch[I2, O @uncheckedVariance, I @uncheckedVariance],
o: TailSwitch[O @uncheckedVariance, I2, O2]): Rule[i.Out, o.Out] = `n/a`
/**
* Same as `~` but with "cut" semantics, meaning that the parser will never backtrack across this boundary.
* If the rule being concatenated doesn't match a parse error will be triggered immediately.
*/
@compileTimeOnly("Calls to `~!~` must be inside `rule` macro")
def ~!~[I2 <: HList, O2 <: HList](that: Rule[I2, O2])(implicit i: TailSwitch[I2, O @uncheckedVariance, I @uncheckedVariance],
o: TailSwitch[O @uncheckedVariance, I2, O2]): Rule[i.Out, o.Out] = `n/a`
/**
* Combines this rule with the given other one in a way that the resulting rule matches if this rule matches
* or the other one matches. If this rule doesn't match the parser is reset and the given alternative tried.
@ -63,6 +72,42 @@ sealed class Rule[-I <: HList, +O <: HList] extends RuleX {
*/
@compileTimeOnly("Calls to `unary_!` must be inside `rule` macro")
def unary_!(): Rule0 = `n/a`
/**
* Attaches the given explicit name to this rule.
*/
@compileTimeOnly("Calls to `named` must be inside `rule` macro")
def named(name: String): this.type = `n/a`
/**
* Postfix shortcut for `optional`.
*/
@compileTimeOnly("Calls to `.?` must be inside `rule` macro")
def ?(implicit l: Lifter[Option, I @uncheckedVariance, O @uncheckedVariance]): Rule[l.In, l.OptionalOut] = `n/a`
/**
* Postfix shortcut for `zeroOrMore`.
*/
@compileTimeOnly("Calls to `.*` must be inside `rule` macro")
def *(implicit l: Lifter[immutable.Seq, I @uncheckedVariance, O @uncheckedVariance]): Rule[l.In, l.OptionalOut] with Repeated = `n/a`
/**
* Postfix shortcut for `zeroOrMore(...).separatedBy(...)`.
*/
@compileTimeOnly("Calls to `.*` must be inside `rule` macro")
def *(separator: Rule0)(implicit l: Lifter[immutable.Seq, I @uncheckedVariance, O @uncheckedVariance]): Rule[l.In, l.OptionalOut] = `n/a`
/**
* Postfix shortcut for `oneOrMore`.
*/
@compileTimeOnly("Calls to `.+` must be inside `rule` macro")
def +(implicit l: Lifter[immutable.Seq, I @uncheckedVariance, O @uncheckedVariance]): Rule[l.In, l.StrictOut] with Repeated = `n/a`
/**
* Postfix shortcut for `oneOrMore(...).separatedBy(...)`.
*/
@compileTimeOnly("Calls to `.+` must be inside `rule` macro")
def +(separator: Rule0)(implicit l: Lifter[immutable.Seq, I @uncheckedVariance, O @uncheckedVariance]): Rule[l.In, l.StrictOut] = `n/a`
}
/**
@ -80,4 +125,7 @@ object Rule extends Rule0 {
abstract class RuleDSL
extends RuleDSLBasics
with RuleDSLCombinators
with RuleDSLActions
with RuleDSLActions
// phantom type for WithSeparatedBy pimp
trait Repeated

View file

@ -52,7 +52,7 @@ trait RuleDSLActions {
*
* - a function with one HList parameter the behavior is similar to the previous case with the difference that the
* elements of this parameter HList are mapped against the value stack top. This allows for consumption of an
* arbitrary number of value stack elements.
* arbitrary number of value stack elements. (Note: This feature of ``run`` is not yet currently implemented.)
*
* - any other value the result type of `run` is an always succeeding `Rule0`.
*
@ -60,7 +60,6 @@ trait RuleDSLActions {
* for every rule application anew! (Since the expression is directly transplanted
* into the rule method by the `rule` macro.
*/
@compileTimeOnly("Calls to `run` must be inside `rule` macro")
def run[T](arg: T)(implicit rr: RunResult[T]): rr.Out = `n/a`

View file

@ -49,12 +49,20 @@ trait RuleDSLBasics {
/**
* Matches any single one of the given characters.
*
* Note: This helper has O(n) runtime with n being the length of the given string.
* If your string consists only of 7-bit ASCII chars using a pre-allocated
* [[CharPredicate]] will be more efficient.
*/
@compileTimeOnly("Calls to `anyOf` must be inside `rule` macro")
def anyOf(chars: String): Rule0 = `n/a`
/**
* Matches any single character except the ones in the given string and except EOI.
*
* Note: This helper has O(n) runtime with n being the length of the given string.
* If your string consists only of 7-bit ASCII chars using a pre-allocated
* [[CharPredicate]] will be more efficient.
*/
@compileTimeOnly("Calls to `noneOf` must be inside `rule` macro")
def noneOf(chars: String): Rule0 = `n/a`
@ -92,10 +100,25 @@ trait RuleDSLBasics {
def MATCH: Rule0 = Rule
/**
* A rule that always fails.
* A Rule0 that always fails.
*/
def MISMATCH0: Rule0 = MISMATCH
/**
* A generic Rule that always fails.
*/
def MISMATCH[I <: HList, O <: HList]: Rule[I, O] = null
def MISMATCH0: Rule0 = MISMATCH
/**
* A rule that always fails and causes the parser to immediately terminate the parsing run.
* The resulting parse error only has a single trace with a single frame which holds the given error message.
*/
def fail(expected: String): Rule0 = `n/a`
/**
* Fully generic variant of [[fail]].
*/
def failX[I <: HList, O <: HList](expected: String): Rule[I, O] = `n/a`
@compileTimeOnly("Calls to `str2CharRangeSupport` must be inside `rule` macro")
implicit def str2CharRangeSupport(s: String): CharRangeSupport = `n/a`

View file

@ -31,7 +31,7 @@ trait RuleDSLCombinators {
* Rule[I, O] if r == Rule[I, O <: I] // so called "reduction", which leaves the value stack unchanged on a type level
*/
@compileTimeOnly("Calls to `optional` must be inside `rule` macro")
def optional[I <: HList, O <: HList](r: Rule[I, O])(implicit o: Lifter[Option, I, O]): Rule[o.In, o.Out] = `n/a`
def optional[I <: HList, O <: HList](r: Rule[I, O])(implicit l: Lifter[Option, I, O]): Rule[l.In, l.OptionalOut] = `n/a`
/**
* Runs its inner rule until it fails, always succeeds.
@ -41,7 +41,7 @@ trait RuleDSLCombinators {
* Rule[I, O] if r == Rule[I, O <: I] // so called "reduction", which leaves the value stack unchanged on a type level
*/
@compileTimeOnly("Calls to `zeroOrMore` must be inside `rule` macro")
def zeroOrMore[I <: HList, O <: HList](r: Rule[I, O])(implicit s: Lifter[immutable.Seq, I, O]): Rule[s.In, s.Out] with Repeated = `n/a`
def zeroOrMore[I <: HList, O <: HList](r: Rule[I, O])(implicit l: Lifter[immutable.Seq, I, O]): Rule[l.In, l.OptionalOut] with Repeated = `n/a`
/**
* Runs its inner rule until it fails, succeeds if its inner rule succeeded at least once.
@ -51,7 +51,7 @@ trait RuleDSLCombinators {
* Rule[I, O] if r == Rule[I, O <: I] // so called "reduction", which leaves the value stack unchanged on a type level
*/
@compileTimeOnly("Calls to `oneOrMore` must be inside `rule` macro")
def oneOrMore[I <: HList, O <: HList](r: Rule[I, O])(implicit s: Lifter[immutable.Seq, I, O]): Rule[s.In, s.Out] with Repeated = `n/a`
def oneOrMore[I <: HList, O <: HList](r: Rule[I, O])(implicit l: Lifter[immutable.Seq, I, O]): Rule[l.In, l.StrictOut] with Repeated = `n/a`
/**
* Runs its inner rule but resets the parser (cursor and value stack) afterwards,
@ -60,6 +60,24 @@ trait RuleDSLCombinators {
@compileTimeOnly("Calls to `&` must be inside `rule` macro")
def &(r: Rule[_, _]): Rule0 = `n/a`
/**
* Marks a rule as "undividable" from an error reporting perspective.
* The parser will never report errors *inside* of the marked rule.
* Rather, if the rule mismatches, the error will be reported at the
* very beginning of the attempted rule match.
*/
@compileTimeOnly("Calls to `atomic` must be inside `rule` macro")
def atomic[I <: HList, O <: HList](r: Rule[I, O]): Rule[I, O] = `n/a`
/**
* Marks a rule as "quiet" from an error reporting perspective.
* Quiet rules only show up in error rule traces if no "unquiet" rules match up to the error location.
* This marker frequently used for low-level syntax rules (like whitespace or comments) that might be matched
* essentially everywhere and are therefore not helpful when appearing in the "expected" set of an error report.
*/
@compileTimeOnly("Calls to `atomic` must be inside `rule` macro")
def quiet[I <: HList, O <: HList](r: Rule[I, O]): Rule[I, O] = `n/a`
/**
* Allows creation of a sub parser and running of one of its rules as part of the current parsing process.
* The subparser will start parsing at the current input position and the outer parser (this parser)
@ -75,7 +93,7 @@ trait RuleDSLCombinators {
sealed trait NTimes {
/**
* Repeats the given sub rule `r` the given number of times.
* Both bounds of the range must be non-negative and the upper bound must be >= the lower bound.
* Both bounds of the range must be positive and the upper bound must be >= the lower bound.
* If the upper bound is zero the rule is equivalent to `MATCH`.
*
* Resulting rule type is
@ -84,12 +102,9 @@ trait RuleDSLCombinators {
* Rule[I, O] if r == Rule[I, O <: I] // so called "reduction", which leaves the value stack unchanged on a type level
*/
@compileTimeOnly("Calls to `times` must be inside `rule` macro")
def times[I <: HList, O <: HList](r: Rule[I, O])(implicit s: Lifter[immutable.Seq, I, O]): Rule[s.In, s.Out] with Repeated
def times[I <: HList, O <: HList](r: Rule[I, O])(implicit s: Lifter[immutable.Seq, I, O]): Rule[s.In, s.StrictOut] with Repeated
}
// phantom type for WithSeparatedBy pimp
trait Repeated
@compileTimeOnly("Calls to `rule2WithSeparatedBy` constructor must be inside `rule` macro")
implicit def rule2WithSeparatedBy[I <: HList, O <: HList](r: Rule[I, O] with Repeated): WithSeparatedBy[I, O] = `n/a`
trait WithSeparatedBy[I <: HList, O <: HList] {

View file

@ -22,10 +22,10 @@ import java.nio.charset.Charset
package object parboiled2 {
type Rule0 = RuleN[HNil]
type Rule1[T] = RuleN[T :: HNil]
type Rule2[A, B] = RuleN[A :: B :: HNil]
type RuleN[L <: HList] = Rule[HNil, L]
type PopRule[L <: HList] = Rule[L, HNil]
type Rule1[+T] = RuleN[T :: HNil]
type Rule2[+A, +B] = RuleN[A :: B :: HNil]
type RuleN[+L <: HList] = Rule[HNil, L]
type PopRule[-L <: HList] = Rule[L, HNil]
val EOI = '\uFFFF'

View file

@ -0,0 +1,79 @@
/*
* Copyright (C) 2009-2013 Mathias Doenitz, Alexander Myltsev
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package akka.parboiled2.support
import akka.shapeless._
import akka.parboiled2.Rule
import akka.shapeless.ops.hlist.ReversePrepend
/*
* The main ActionOps boilerplate is generated by a custom SBT sourceGenerator.
* This file only contains support types.
*/
// we want to support the "short case class notation" `... ~> Foo`
// unfortunately the Tree for the function argument to the `apply` overloads above does *not* allow us to inspect the
// function type which is why we capture it separately with this helper type
sealed trait FCapture[T]
object FCapture {
implicit def apply[T]: FCapture[T] = `n/a`
}
// builds `In` and `Out` types according to this logic:
// if (R == Unit)
// In = I, Out = L1 ::: L2
// else if (R <: HList)
// In = I, Out = L1 ::: L2 ::: R
// else if (R <: Rule[I2, O2])
// In = TailSwitch[I2, L1 ::: L2, I], Out = TailSwitch[L1 ::: L2, I2, O2]
// else
// In = I, Out = L1 ::: L2 ::: R :: HNil
sealed trait Join[I <: HList, L1 <: HList, L2 <: HList, R] {
type In <: HList
type Out <: HList
}
object Join {
implicit def join[I <: HList, L1 <: HList, L2 <: HList, R, In0 <: HList, Out0 <: HList](implicit x: Aux[I, L1, L2, R, HNil, In0, Out0]): Join[I, L1, L2, R] { type In = In0; type Out = Out0 } = `n/a`
sealed trait Aux[I <: HList, L1 <: HList, L2 <: HList, R, Acc <: HList, In <: HList, Out <: HList]
object Aux extends Aux1 {
// if R == Unit convert to HNil
implicit def forUnit[I <: HList, L1 <: HList, L2 <: HList, Acc <: HList, Out <: HList](implicit x: Aux[I, L1, L2, HNil, Acc, I, Out]): Aux[I, L1, L2, Unit, Acc, I, Out] = `n/a`
// if R <: HList and L1 non-empty move head of L1 to Acc
implicit def iter1[I <: HList, H, T <: HList, L2 <: HList, R <: HList, Acc <: HList, Out <: HList](implicit x: Aux[I, T, L2, R, H :: Acc, I, Out]): Aux[I, H :: T, L2, R, Acc, I, Out] = `n/a`
// if R <: HList and L1 empty and L2 non-empty move head of L2 to Acc
implicit def iter2[I <: HList, H, T <: HList, R <: HList, Acc <: HList, Out <: HList](implicit x: Aux[I, HNil, T, R, H :: Acc, I, Out]): Aux[I, HNil, H :: T, R, Acc, I, Out] = `n/a`
// if R <: HList and L1 and L2 empty set Out = reversePrepend Acc before R
implicit def terminate[I <: HList, R <: HList, Acc <: HList, Out <: HList](implicit x: ReversePrepend.Aux[Acc, R, Out]): Aux[I, HNil, HNil, R, Acc, I, Out] = `n/a`
// if R <: Rule and L1 non-empty move head of L1 to Acc
implicit def iterRule1[I <: HList, L2 <: HList, I2 <: HList, O2 <: HList, In0 <: HList, Acc <: HList, Out0 <: HList, H, T <: HList](implicit x: Aux[I, T, L2, Rule[I2, O2], H :: Acc, In0, Out0]): Aux[I, H :: T, L2, Rule[I2, O2], HNil, In0, Out0] = `n/a`
// if R <: Rule and L1 empty and Acc non-empty move head of Acc to L2
implicit def iterRule2[I <: HList, L2 <: HList, I2 <: HList, O2 <: HList, In0 <: HList, Out0 <: HList, H, T <: HList](implicit x: Aux[I, HNil, H :: L2, Rule[I2, O2], T, In0, Out0]): Aux[I, HNil, L2, Rule[I2, O2], H :: T, In0, Out0] = `n/a`
// if R <: Rule and L1 and Acc empty set In and Out to tailswitches result
implicit def terminateRule[I <: HList, O <: HList, I2 <: HList, O2 <: HList, In <: HList, Out <: HList](implicit i: TailSwitch.Aux[I2, I2, O, O, I, HNil, In], o: TailSwitch.Aux[O, O, I2, I2, O2, HNil, Out]): Aux[I, HNil, O, Rule[I2, O2], HNil, In, Out] = `n/a`
}
abstract class Aux1 {
// convert R to R :: HNil
implicit def forAny[I <: HList, L1 <: HList, L2 <: HList, R, Acc <: HList, Out <: HList](implicit x: Aux[I, L1, L2, R :: HNil, Acc, I, Out]): Aux[I, L1, L2, R, Acc, I, Out] = `n/a`
}
}

View file

@ -21,13 +21,30 @@ import akka.shapeless._
@implicitNotFound("The `optional`, `zeroOrMore`, `oneOrMore` and `times` modifiers " +
"can only be used on rules of type `Rule0`, `Rule1[T]` and `Rule[I, O <: I]`!")
sealed trait Lifter[M[_], I <: HList, O <: HList] { type In <: HList; type Out <: HList }
sealed trait Lifter[M[_], I <: HList, O <: HList] {
type In <: HList
type StrictOut <: HList
type OptionalOut <: HList
}
object Lifter extends LowerPriorityLifter {
implicit def forRule0[M[_]]: Lifter[M, HNil, HNil] { type In = HNil; type Out = HNil } = `n/a`
implicit def forRule1[M[_], T]: Lifter[M, HNil, T :: HNil] { type In = HNil; type Out = M[T] :: HNil } = `n/a`
implicit def forRule0[M[_]]: Lifter[M, HNil, HNil] {
type In = HNil
type StrictOut = HNil
type OptionalOut = StrictOut
} = `n/a`
implicit def forRule1[M[_], T]: Lifter[M, HNil, T :: HNil] {
type In = HNil
type StrictOut = M[T] :: HNil
type OptionalOut = StrictOut
} = `n/a`
}
sealed abstract class LowerPriorityLifter {
implicit def forReduction[M[_], L <: HList, R <: L]: Lifter[M, L, R] { type In = L; type Out = R } = `n/a`
implicit def forReduction[M[_], L <: HList, R <: L]: Lifter[M, L, R] {
type In = L
type StrictOut = R
type OptionalOut = L
} = `n/a`
}

View file

@ -23,35 +23,54 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
val c: OpTreeCtx
import c.universe._
lazy val ruleType = typeOf[Rule[_, _]]
sealed trait OpTree {
// renders a Boolean Tree
def render(wrapped: Boolean): Tree
}
sealed abstract class OpTree {
def ruleFrame: Tree
// renders a RuleX Tree
def renderRule(ruleName: String): Tree = q"""
// split out into separate method so as to not double the rule method size
// which would effectively decrease method inlining by about 50%
def wrapped: Boolean = ${render(wrapped = true, ruleName)}
val matched =
if (__collectingErrors) wrapped
else ${render(wrapped = false)}
if (matched) akka.parboiled2.Rule else null""" // we encode the "matched" boolean as 'ruleResult ne null'
sealed abstract class NonTerminalOpTree extends OpTree {
def bubbleUp: Tree
// renders a Boolean Tree
def render(wrapped: Boolean, ruleName: String = ""): Tree =
def render(wrapped: Boolean): Tree =
if (wrapped) q"""
val start = cursor
try ${renderInner(wrapped)}
catch {
case e: akka.parboiled2.Parser.CollectingRuleStackException
e.save(akka.parboiled2.RuleFrame($ruleFrame, $ruleName))
}"""
catch { case e: akka.parboiled2.Parser#TracingBubbleException $bubbleUp }"""
else renderInner(wrapped)
// renders a Boolean Tree
protected def renderInner(wrapped: Boolean): Tree
}
sealed abstract class DefaultNonTerminalOpTree extends NonTerminalOpTree {
def bubbleUp: Tree = q"e.bubbleUp($ruleTraceNonTerminalKey, start)"
def ruleTraceNonTerminalKey: Tree
}
sealed abstract class TerminalOpTree extends OpTree {
def bubbleUp: Tree = q"__bubbleUp($ruleTraceTerminal)"
def ruleTraceTerminal: Tree
// renders a Boolean Tree
final def render(wrapped: Boolean): Tree =
if (wrapped) q"""
try ${renderInner(wrapped)}
catch { case akka.parboiled2.Parser.StartTracingException $bubbleUp }"""
else renderInner(wrapped)
// renders a Boolean Tree
protected def renderInner(wrapped: Boolean): Tree
}
sealed abstract class PotentiallyNamedTerminalOpTree(arg: Tree) extends TerminalOpTree {
override def bubbleUp = callName(arg) match {
case Some(name) q"__bubbleUp(akka.parboiled2.RuleTrace.NonTerminal(akka.parboiled2.RuleTrace.Named($name), 0) :: Nil, $ruleTraceTerminal)"
case None super.bubbleUp
}
def ruleTraceTerminal: Tree
}
def collector(lifterTree: Tree): Collector =
lifterTree match {
case q"support.this.$a.forRule0[$b]" rule0Collector
@ -62,6 +81,7 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
val opTreePF: PartialFunction[Tree, OpTree] = {
case q"$lhs.~[$a, $b]($rhs)($c, $d)" Sequence(OpTree(lhs), OpTree(rhs))
case q"$lhs.~!~[$a, $b]($rhs)($c, $d)" Cut(OpTree(lhs), OpTree(rhs))
case q"$lhs.|[$a, $b]($rhs)" FirstOf(OpTree(lhs), OpTree(rhs))
case q"$a.this.ch($c)" CharMatch(c)
case q"$a.this.str($s)" StringMatch(s)
@ -71,18 +91,28 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
case q"$a.this.anyOf($s)" AnyOf(s)
case q"$a.this.noneOf($s)" NoneOf(s)
case q"$a.this.ANY" ANY
case q"$a.this.optional[$b, $c]($arg)($o)" Optional(OpTree(arg), collector(o))
case q"$a.this.zeroOrMore[$b, $c]($arg)($s)" ZeroOrMore(OpTree(arg), collector(s))
case q"$a.this.oneOrMore[$b, $c]($arg)($s)" OneOrMore(OpTree(arg), collector(s))
case q"$a.this.optional[$b, $c]($arg)($l)" Optional(OpTree(arg), collector(l))
case q"$base.?($l)" Optional(OpTree(base), collector(l))
case q"$a.this.zeroOrMore[$b, $c]($arg)($l)" ZeroOrMore(OpTree(arg), collector(l))
case q"$base.*($l)" ZeroOrMore(OpTree(base), collector(l))
case q"$base.*($sep)($l)" ZeroOrMore(OpTree(base), collector(l), Separator(OpTree(sep)))
case q"$a.this.oneOrMore[$b, $c]($arg)($l)" OneOrMore(OpTree(arg), collector(l))
case q"$base.+($l)" OneOrMore(OpTree(base), collector(l))
case q"$base.+($sep)($l)" OneOrMore(OpTree(base), collector(l), Separator(OpTree(sep)))
case q"$base.times[$a, $b]($r)($s)" Times(base, OpTree(r), collector(s))
case q"$a.this.&($arg)" AndPredicate(OpTree(arg))
case q"$a.unary_!()" NotPredicate(OpTree(a))
case q"$a.this.atomic[$b, $c]($arg)" Atomic(OpTree(arg))
case q"$a.this.quiet[$b, $c]($arg)" Quiet(OpTree(arg))
case q"$a.this.test($flag)" SemanticPredicate(flag)
case q"$a.this.capture[$b, $c]($arg)($d)" Capture(OpTree(arg))
case q"$a.this.run[$b]($arg)($c.fromAux[$d, $e]($rr))" RunAction(arg, rr)
case q"$a.this.push[$b]($arg)($hl)" PushAction(arg, hl)
case q"$a.this.drop[$b]($hl)" DropAction(hl)
case q"$a.this.runSubParser[$b, $c]($f)" RunSubParser(f)
case q"$a.this.fail($m)" Fail(m)
case q"$a.this.failX[$b, $c]($m)" Fail(m)
case q"$a.named($name)" Named(OpTree(a), name)
case x @ q"$a.this.str2CharRangeSupport($l).-($r)" CharRange(l, r)
case q"$a.this.charAndValue[$t]($b.any2ArrowAssoc[$t1]($c).->[$t2]($v))($hl)"
Sequence(CharMatch(c), PushAction(v, hl))
@ -90,15 +120,13 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
Sequence(StringMatch(s), PushAction(v, hl))
case q"$a.this.rule2ActionOperator[$b1, $b2]($r)($o).~>.apply[..$e]($f)($g, support.this.FCapture.apply[$ts])"
Sequence(OpTree(r), Action(f, ts))
case x @ q"$a.this.rule2WithSeparatedBy[$b1, $b2]($base.$fun[$d, $e]($arg)($s)).separatedBy($sep)"
val (op, coll, separator) = (OpTree(arg), collector(s), Separator(OpTree(sep)))
fun.decodedName.toString match {
case "zeroOrMore" ZeroOrMore(op, coll, separator)
case "oneOrMore" OneOrMore(op, coll, separator)
case "times" Times(base, op, coll, separator)
case _ c.abort(x.pos, "Unexpected Repeated fun: " + fun)
case x @ q"$a.this.rule2WithSeparatedBy[$b1, $b2]($base).separatedBy($sep)"
OpTree(base) match {
case x: WithSeparator x.withSeparator(Separator(OpTree(sep)))
case _ c.abort(x.pos, "Illegal `separatedBy` base: " + base)
}
case call @ (Apply(_, _) | Select(_, _) | Ident(_)) RuleCall(call)
case call @ (Apply(_, _) | Select(_, _) | Ident(_) | TypeApply(_, _))
RuleCall(Right(call), Literal(Constant(callName(call) getOrElse c.abort(call.pos, "Illegal rule call: " + call))))
}
def OpTree(tree: Tree): OpTree =
@ -112,11 +140,23 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
case _ Sequence(Seq(lhs, rhs))
}
case class Sequence(ops: Seq[OpTree]) extends OpTree {
case class Sequence(ops: Seq[OpTree]) extends DefaultNonTerminalOpTree {
require(ops.size >= 2)
def ruleFrame = q"akka.parboiled2.RuleFrame.Sequence(${ops.size})"
def ruleTraceNonTerminalKey = reify(RuleTrace.Sequence).tree
def renderInner(wrapped: Boolean): Tree =
ops.map(_.render(wrapped)).reduceLeft((l, r) q"$l && $r")
ops.map(_.render(wrapped)).reduceLeft((l, r)
q"val l = $l; if (l) $r else false") // work-around for https://issues.scala-lang.org/browse/SI-8657"
}
case class Cut(lhs: OpTree, rhs: OpTree) extends DefaultNonTerminalOpTree {
def ruleTraceNonTerminalKey = reify(RuleTrace.Cut).tree
def renderInner(wrapped: Boolean): Tree = q"""
var matched = ${lhs.render(wrapped)}
if (matched) {
matched = ${rhs.render(wrapped)}
if (!matched) throw akka.parboiled2.Parser.CutError
true
} else false""" // work-around for https://issues.scala-lang.org/browse/SI-8657
}
def FirstOf(lhs: OpTree, rhs: OpTree): FirstOf =
@ -127,16 +167,17 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
case _ FirstOf(Seq(lhs, rhs))
}
case class FirstOf(ops: Seq[OpTree]) extends OpTree {
def ruleFrame = q"akka.parboiled2.RuleFrame.FirstOf(${ops.size})"
case class FirstOf(ops: Seq[OpTree]) extends DefaultNonTerminalOpTree {
def ruleTraceNonTerminalKey = reify(RuleTrace.FirstOf).tree
def renderInner(wrapped: Boolean): Tree =
q"""val mark = __saveState; ${
ops.map(_.render(wrapped)).reduceLeft((l, r) q"$l || { __restoreState(mark); $r }")
ops.map(_.render(wrapped)).reduceLeft((l, r)
q"val l = $l; if (!l) { __restoreState(mark); $r } else true // work-around for https://issues.scala-lang.org/browse/SI-8657")
}"""
}
case class CharMatch(charTree: Tree) extends OpTree {
def ruleFrame = q"akka.parboiled2.RuleFrame.CharMatch($charTree)"
case class CharMatch(charTree: Tree) extends TerminalOpTree {
def ruleTraceTerminal = q"akka.parboiled2.RuleTrace.CharMatch($charTree)"
def renderInner(wrapped: Boolean): Tree = {
val unwrappedTree = q"cursorChar == $charTree && __advance()"
if (wrapped) q"$unwrappedTree && __updateMaxCursor() || __registerMismatch()" else unwrappedTree
@ -145,9 +186,7 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
case class StringMatch(stringTree: Tree) extends OpTree {
final private val autoExpandMaxStringLength = 8
def renderInner(wrapped: Boolean): Tree = `n/a`
def ruleFrame = q"akka.parboiled2.RuleFrame.StringMatch($stringTree)"
override def render(wrapped: Boolean, ruleName: String = ""): Tree = {
def render(wrapped: Boolean): Tree = {
def unrollUnwrapped(s: String, ix: Int = 0): Tree =
if (ix < s.length) q"""
if (cursorChar == ${s charAt ix}) {
@ -158,17 +197,16 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
def unrollWrapped(s: String, ix: Int = 0): Tree =
if (ix < s.length) {
val ch = s charAt ix
q"""
if (cursorChar == $ch) {
q"""if (cursorChar == $ch) {
__advance()
__updateMaxCursor()
${unrollWrapped(s, ix + 1)}
} else {
try __registerMismatch()
catch {
case e: akka.parboiled2.Parser.CollectingRuleStackException
e.save(akka.parboiled2.RuleFrame(akka.parboiled2.RuleFrame.StringMatch($s), $ruleName),
akka.parboiled2.RuleFrame.CharMatch($ch))
case akka.parboiled2.Parser.StartTracingException
import akka.parboiled2.RuleTrace._
__bubbleUp(NonTerminal(StringMatch($stringTree), -$ix) :: Nil, CharMatch($ch))
}
}"""
} else q"true"
@ -177,18 +215,14 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
case Literal(Constant(s: String)) if s.length <= autoExpandMaxStringLength
if (s.isEmpty) q"true" else if (wrapped) unrollWrapped(s) else unrollUnwrapped(s)
case _
if (wrapped) q"__matchStringWrapped($stringTree, $ruleName)"
if (wrapped) q"__matchStringWrapped($stringTree)"
else q"__matchString($stringTree)"
}
}
}
case class MapMatch(mapTree: Tree) extends OpTree {
def ruleFrame = q"akka.parboiled2.RuleFrame.MapMatch($mapTree)"
def renderInner(wrapped: Boolean): Tree = `n/a`
override def render(wrapped: Boolean, ruleName: String = ""): Tree =
if (wrapped) q"__matchMapWrapped($mapTree, $ruleName)"
else q"__matchMap($mapTree)"
def render(wrapped: Boolean): Tree = if (wrapped) q"__matchMapWrapped($mapTree)" else q"__matchMap($mapTree)"
}
def IgnoreCase(argTree: Tree): OpTree = {
@ -198,8 +232,8 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
else c.abort(argTree.pos, "Unexpected `ignoreCase` argument type: " + argTypeSymbol)
}
case class IgnoreCaseChar(charTree: Tree) extends OpTree {
def ruleFrame = q"akka.parboiled2.RuleFrame.IgnoreCaseChar($charTree)"
case class IgnoreCaseChar(charTree: Tree) extends TerminalOpTree {
def ruleTraceTerminal = q"akka.parboiled2.RuleTrace.IgnoreCaseChar($charTree)"
def renderInner(wrapped: Boolean): Tree = {
val unwrappedTree = q"_root_.java.lang.Character.toLowerCase(cursorChar) == $charTree && __advance()"
if (wrapped) q"$unwrappedTree && __updateMaxCursor() || __registerMismatch()" else unwrappedTree
@ -208,9 +242,7 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
case class IgnoreCaseString(stringTree: Tree) extends OpTree {
final private val autoExpandMaxStringLength = 8
def renderInner(wrapped: Boolean): Tree = `n/a`
def ruleFrame = q"akka.parboiled2.RuleFrame.IgnoreCaseString($stringTree)"
override def render(wrapped: Boolean, ruleName: String = ""): Tree = {
def render(wrapped: Boolean): Tree = {
def unrollUnwrapped(s: String, ix: Int = 0): Tree =
if (ix < s.length) q"""
if (_root_.java.lang.Character.toLowerCase(cursorChar) == ${s charAt ix}) {
@ -221,17 +253,16 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
def unrollWrapped(s: String, ix: Int = 0): Tree =
if (ix < s.length) {
val ch = s charAt ix
q"""
if (_root_.java.lang.Character.toLowerCase(cursorChar) == $ch) {
q"""if (_root_.java.lang.Character.toLowerCase(cursorChar) == $ch) {
__advance()
__updateMaxCursor()
${unrollWrapped(s, ix + 1)}
} else {
try __registerMismatch()
catch {
case e: akka.parboiled2.Parser.CollectingRuleStackException
e.save(akka.parboiled2.RuleFrame(akka.parboiled2.RuleFrame.IgnoreCaseString($s), $ruleName),
akka.parboiled2.RuleFrame.IgnoreCaseChar($ch))
case akka.parboiled2.Parser.StartTracingException
import akka.parboiled2.RuleTrace._
__bubbleUp(NonTerminal(IgnoreCaseString($stringTree), -$ix) :: Nil, IgnoreCaseChar($ch))
}
}"""
} else q"true"
@ -240,48 +271,50 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
case Literal(Constant(s: String)) if s.length <= autoExpandMaxStringLength
if (s.isEmpty) q"true" else if (wrapped) unrollWrapped(s) else unrollUnwrapped(s)
case _
if (wrapped) q"__matchIgnoreCaseStringWrapped($stringTree, $ruleName)"
if (wrapped) q"__matchIgnoreCaseStringWrapped($stringTree)"
else q"__matchIgnoreCaseString($stringTree)"
}
}
}
case class CharPredicateMatch(predicateTree: Tree) extends OpTree {
def predicateName = callName(predicateTree) getOrElse ""
def ruleFrame = q"akka.parboiled2.RuleFrame.CharPredicateMatch($predicateTree, $predicateName)"
case class CharPredicateMatch(predicateTree: Tree) extends PotentiallyNamedTerminalOpTree(predicateTree) {
def ruleTraceTerminal = q"akka.parboiled2.RuleTrace.CharPredicateMatch($predicateTree)"
def renderInner(wrapped: Boolean): Tree = {
val unwrappedTree = q"$predicateTree(cursorChar) && __advance()"
if (wrapped) q"$unwrappedTree && __updateMaxCursor() || __registerMismatch()" else unwrappedTree
}
}
case class AnyOf(stringTree: Tree) extends OpTree {
def ruleFrame = q"akka.parboiled2.RuleFrame.AnyOf($stringTree)"
def renderInner(wrapped: Boolean): Tree =
if (wrapped) q"__matchAnyOf($stringTree) && __updateMaxCursor() || __registerMismatch()"
else q"__matchAnyOf($stringTree)"
case class AnyOf(stringTree: Tree) extends TerminalOpTree {
def ruleTraceTerminal = q"akka.parboiled2.RuleTrace.AnyOf($stringTree)"
def renderInner(wrapped: Boolean): Tree = {
val unwrappedTree = q"__matchAnyOf($stringTree)"
if (wrapped) q"$unwrappedTree && __updateMaxCursor() || __registerMismatch()" else unwrappedTree
}
}
case class NoneOf(stringTree: Tree) extends OpTree {
def ruleFrame = q"akka.parboiled2.RuleFrame.NoneOf($stringTree)"
def renderInner(wrapped: Boolean): Tree =
if (wrapped) q"__matchNoneOf($stringTree) && __updateMaxCursor() || __registerMismatch()"
else q"__matchNoneOf($stringTree)"
case class NoneOf(stringTree: Tree) extends TerminalOpTree {
def ruleTraceTerminal = q"akka.parboiled2.RuleTrace.NoneOf($stringTree)"
def renderInner(wrapped: Boolean): Tree = {
val unwrappedTree = q"__matchNoneOf($stringTree)"
if (wrapped) q"$unwrappedTree && __updateMaxCursor() || __registerMismatch()" else unwrappedTree
}
}
case object ANY extends OpTree {
def ruleFrame = reify(RuleFrame.ANY).tree
case object ANY extends TerminalOpTree {
def ruleTraceTerminal = reify(RuleTrace.ANY).tree
def renderInner(wrapped: Boolean): Tree = {
val unwrappedTree = q"cursorChar != EOI && __advance()"
if (wrapped) q"$unwrappedTree && __updateMaxCursor() || __registerMismatch()" else unwrappedTree
}
}
case class Optional(op: OpTree, collector: Collector) extends OpTree {
def ruleFrame = reify(RuleFrame.Optional).tree
case class Optional(op: OpTree, collector: Collector) extends DefaultNonTerminalOpTree {
def ruleTraceNonTerminalKey = reify(RuleTrace.Optional).tree
def renderInner(wrapped: Boolean): Tree = q"""
val mark = __saveState
if (${op.render(wrapped)}) {
val matched = ${op.render(wrapped)}
if (matched) {
${collector.pushSomePop}
} else {
__restoreState(mark)
@ -290,8 +323,13 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
true"""
}
case class ZeroOrMore(op: OpTree, collector: Collector, separator: Separator = null) extends OpTree {
def ruleFrame = reify(RuleFrame.ZeroOrMore).tree
sealed abstract class WithSeparator extends DefaultNonTerminalOpTree {
def withSeparator(sep: Separator): OpTree
}
case class ZeroOrMore(op: OpTree, collector: Collector, separator: Separator = null) extends WithSeparator {
def withSeparator(sep: Separator) = copy(separator = sep)
def ruleTraceNonTerminalKey = reify(RuleTrace.ZeroOrMore).tree
def renderInner(wrapped: Boolean): Tree = {
val recurse =
if (separator eq null) q"rec(__saveState)"
@ -300,19 +338,22 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
q"""
${collector.valBuilder}
@_root_.scala.annotation.tailrec def rec(mark: akka.parboiled2.Parser.Mark): akka.parboiled2.Parser.Mark =
if (${op.render(wrapped)}) {
@_root_.scala.annotation.tailrec def rec(mark: akka.parboiled2.Parser.Mark): akka.parboiled2.Parser.Mark = {
val matched = ${op.render(wrapped)}
if (matched) {
${collector.popToBuilder}
$recurse
} else mark
}
__restoreState(rec(__saveState))
${collector.pushBuilderResult}"""
}
}
case class OneOrMore(op: OpTree, collector: Collector, separator: Separator = null) extends OpTree {
def ruleFrame = reify(RuleFrame.OneOrMore).tree
case class OneOrMore(op: OpTree, collector: Collector, separator: Separator = null) extends WithSeparator {
def withSeparator(sep: Separator) = copy(separator = sep)
def ruleTraceNonTerminalKey = reify(RuleTrace.OneOrMore).tree
def renderInner(wrapped: Boolean): Tree = {
val recurse =
if (separator eq null) q"rec(__saveState)"
@ -322,11 +363,13 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
val firstMark = __saveState
${collector.valBuilder}
@_root_.scala.annotation.tailrec def rec(mark: akka.parboiled2.Parser.Mark): akka.parboiled2.Parser.Mark =
if (${op.render(wrapped)}) {
@_root_.scala.annotation.tailrec def rec(mark: akka.parboiled2.Parser.Mark): akka.parboiled2.Parser.Mark = {
val matched = ${op.render(wrapped)}
if (matched) {
${collector.popToBuilder}
$recurse
} else mark
}
val mark = rec(firstMark)
mark != firstMark && {
@ -340,7 +383,7 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
base match {
case q"$a.this.int2NTimes($n)" n match {
case Literal(Constant(i: Int))
if (i < 0) c.abort(base.pos, "`x` in `x.times` must be non-negative")
if (i <= 0) c.abort(base.pos, "`x` in `x.times` must be positive")
else if (i == 1) rule
else Times(rule, q"val min, max = $n", collector, separator)
case x @ (Ident(_) | Select(_, _)) Times(rule, q"val min = $n; val max = min", collector, separator)
@ -349,8 +392,8 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
case q"$a.this.range2NTimes($r)" r match {
case q"scala.this.Predef.intWrapper($mn).to($mx)" (mn, mx) match {
case (Literal(Constant(min: Int)), Literal(Constant(max: Int)))
if (min < 0) c.abort(mn.pos, "`min` in `(min to max).times` must be non-negative")
else if (max < 0) c.abort(mx.pos, "`max` in `(min to max).times` must be non-negative")
if (min <= 0) c.abort(mn.pos, "`min` in `(min to max).times` must be positive")
else if (max <= 0) c.abort(mx.pos, "`max` in `(min to max).times` must be positive")
else if (max < min) c.abort(mx.pos, "`max` in `(min to max).times` must be >= `min`")
else Times(rule, q"val min = $mn; val max = $mx", collector, separator)
case ((Ident(_) | Select(_, _)), (Ident(_) | Select(_, _)))
@ -364,9 +407,10 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
case _ c.abort(base.pos, "Invalid base expression for `.times(...)`: " + base)
}
case class Times(op: OpTree, init: Tree, collector: Collector, separator: Separator) extends OpTree {
case class Times(op: OpTree, init: Tree, collector: Collector, separator: Separator) extends WithSeparator {
def withSeparator(sep: Separator) = copy(separator = sep)
val Block(inits, _) = init
def ruleFrame = q"..$inits; akka.parboiled2.RuleFrame.Times(min, max)"
def ruleTraceNonTerminalKey = q"..$inits; akka.parboiled2.RuleTrace.Times(min, max)"
def renderInner(wrapped: Boolean): Tree = {
val recurse =
if (separator eq null) q"rec(count + 1, __saveState)"
@ -379,7 +423,8 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
..$inits
@_root_.scala.annotation.tailrec def rec(count: Int, mark: akka.parboiled2.Parser.Mark): Boolean = {
if (${op.render(wrapped)}) {
val matched = ${op.render(wrapped)}
if (matched) {
${collector.popToBuilder}
if (count < max) $recurse else true
} else (count > min) && { __restoreState(mark); true }
@ -389,51 +434,87 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
}
}
case class AndPredicate(op: OpTree) extends OpTree {
def ruleFrame = reify(RuleFrame.AndPredicate).tree
case class AndPredicate(op: OpTree) extends DefaultNonTerminalOpTree {
def ruleTraceNonTerminalKey = reify(RuleTrace.AndPredicate).tree
def renderInner(wrapped: Boolean): Tree = q"""
val mark = __saveState
val result = ${op.render(wrapped)}
val matched = ${op.render(wrapped)}
__restoreState(mark)
result"""
matched"""
}
case class NotPredicate(op: OpTree) extends OpTree {
def renderInner(wrapped: Boolean): Tree = `n/a`
def ruleFrame = reify(RuleFrame.NotPredicate).tree
override def render(wrapped: Boolean, ruleName: String = ""): Tree = {
def render(wrapped: Boolean): Tree = {
val unwrappedTree = q"""
val mark = __saveState
val saved = __enterNotPredicate
val result = ${op.render(wrapped)}
val saved = __enterNotPredicate()
val matched = ${op.render(wrapped)}
__exitNotPredicate(saved)
${if (wrapped) q"matchEnd = cursor" else q"()"}
__restoreState(mark)
!result"""
if (wrapped) q"""
!matched"""
if (wrapped) {
val base = op match {
case x: TerminalOpTree q"akka.parboiled2.RuleTrace.NotPredicate.Terminal(${x.ruleTraceTerminal})"
case x: RuleCall q"akka.parboiled2.RuleTrace.NotPredicate.RuleCall(${x.calleeNameTree})"
case x: StringMatch q"""akka.parboiled2.RuleTrace.NotPredicate.Named('"' + ${x.stringTree} + '"')"""
case x: IgnoreCaseString q"""akka.parboiled2.RuleTrace.NotPredicate.Named('"' + ${x.stringTree} + '"')"""
case x: Named q"akka.parboiled2.RuleTrace.NotPredicate.Named(${x.stringTree})"
case _ q"akka.parboiled2.RuleTrace.NotPredicate.Anonymous"
}
q"""
var matchEnd = 0
try $unwrappedTree || __registerMismatch()
catch {
case e: akka.parboiled2.Parser.CollectingRuleStackException
e.save(akka.parboiled2.RuleFrame($ruleFrame, $ruleName), ${op.ruleFrame})
case akka.parboiled2.Parser.StartTracingException __bubbleUp {
akka.parboiled2.RuleTrace.NotPredicate($base, matchEnd - cursor)
}
}"""
else unwrappedTree
} else unwrappedTree
}
}
case class SemanticPredicate(flagTree: Tree) extends OpTree {
def ruleFrame = reify(RuleFrame.SemanticPredicate).tree
case class Atomic(op: OpTree) extends DefaultNonTerminalOpTree {
def ruleTraceNonTerminalKey = reify(RuleTrace.Atomic).tree
def renderInner(wrapped: Boolean): Tree =
if (wrapped) flagTree else q"$flagTree || __registerMismatch()"
if (wrapped) q"""
val saved = __enterAtomic(start)
val matched = ${op.render(wrapped)}
__exitAtomic(saved)
matched"""
else op.render(wrapped)
}
case class Capture(op: OpTree) extends OpTree {
def ruleFrame = reify(RuleFrame.Capture).tree
case class Quiet(op: OpTree) extends DefaultNonTerminalOpTree {
def ruleTraceNonTerminalKey = reify(RuleTrace.Quiet).tree
def renderInner(wrapped: Boolean): Tree =
if (wrapped) q"""
val saved = __enterQuiet()
val matched = ${op.render(wrapped)}
__exitQuiet(saved)
matched"""
else op.render(wrapped)
}
case class SemanticPredicate(flagTree: Tree) extends TerminalOpTree {
def ruleTraceTerminal = reify(RuleTrace.SemanticPredicate).tree
def renderInner(wrapped: Boolean): Tree =
if (wrapped) q"$flagTree || __registerMismatch()" else flagTree
}
case class Capture(op: OpTree) extends DefaultNonTerminalOpTree {
def ruleTraceNonTerminalKey = reify(RuleTrace.Capture).tree
def renderInner(wrapped: Boolean): Tree = q"""
val start = cursor
${op.render(wrapped)} && {valueStack.push(input.sliceString(start, cursor)); true}"""
${if (!wrapped) q"val start = cursor" else q"();"}
val matched = ${op.render(wrapped)}
if (matched) {
valueStack.push(input.sliceString(start, cursor))
true
} else false"""
}
case class RunAction(argTree: Tree, rrTree: Tree) extends OpTree {
def ruleFrame = reify(RuleFrame.Run).tree
case class RunAction(argTree: Tree, rrTree: Tree) extends DefaultNonTerminalOpTree {
def ruleTraceNonTerminalKey = reify(RuleTrace.Run).tree
def renderInner(wrapped: Boolean): Tree = {
def renderFunctionAction(resultTypeTree: Tree, argTypeTrees: Tree*): Tree = {
def actionBody(tree: Tree): Tree =
@ -443,9 +524,9 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
case q"(..$args ⇒ $body)"
def rewrite(tree: Tree): Tree =
tree match {
case Block(statements, res) block(statements, rewrite(res))
case x if resultTypeTree.tpe <:< ruleType expand(x, wrapped)
case x q"__push($x)"
case Block(statements, res) block(statements, rewrite(res))
case x if isSubClass(resultTypeTree.tpe, "akka.parboiled2.Rule") expand(x, wrapped)
case x q"__push($x)"
}
val valDefs = args.zip(argTypeTrees).map { case (a, t) q"val ${a.name} = valueStack.pop().asInstanceOf[${t.tpe}]" }.reverse
block(valDefs, rewrite(body))
@ -476,8 +557,7 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
}
case class PushAction(argTree: Tree, hlTree: Tree) extends OpTree {
def ruleFrame = reify(RuleFrame.Push).tree
def renderInner(wrapped: Boolean): Tree =
def render(wrapped: Boolean): Tree =
block(hlTree match {
case q"support.this.HListable.fromUnit" argTree
case q"support.this.HListable.fromHList[$t]" q"valueStack.pushAll(${c.resetLocalAttrs(argTree)})"
@ -487,8 +567,7 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
}
case class DropAction(hlTree: Tree) extends OpTree {
def ruleFrame = reify(RuleFrame.Drop).tree
def renderInner(wrapped: Boolean): Tree =
def render(wrapped: Boolean): Tree =
hlTree match {
case q"support.this.HListable.fromUnit" q"true"
case q"support.this.HListable.fromAnyRef[$t]" q"valueStack.pop(); true"
@ -503,10 +582,16 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
}
}
case class RuleCall(call: Tree) extends OpTree {
def calleeName = callName(call) getOrElse c.abort(call.pos, "Illegal rule call: " + call)
def ruleFrame = q"akka.parboiled2.RuleFrame.RuleCall($calleeName)"
def renderInner(wrapped: Boolean): Tree = q"$call ne null"
case class RuleCall(call: Either[OpTree, Tree], calleeNameTree: Tree) extends NonTerminalOpTree {
def bubbleUp = q"""
import akka.parboiled2.RuleTrace._
e.prepend(RuleCall, start).bubbleUp(Named($calleeNameTree), start)"""
override def render(wrapped: Boolean) =
call match {
case Left(_) super.render(wrapped)
case Right(x) q"$x ne null"
}
def renderInner(wrapped: Boolean) = call.asInstanceOf[Left[OpTree, Tree]].a.render(wrapped)
}
def CharRange(lowerTree: Tree, upperTree: Tree): CharacterRange = {
@ -522,8 +607,8 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
CharacterRange(lowerBoundChar, upperBoundChar)
}
case class CharacterRange(lowerBound: Char, upperBound: Char) extends OpTree {
def ruleFrame = q"akka.parboiled2.RuleFrame.CharRange($lowerBound, $upperBound)"
case class CharacterRange(lowerBound: Char, upperBound: Char) extends TerminalOpTree {
def ruleTraceTerminal = q"akka.parboiled2.RuleTrace.CharRange($lowerBound, $upperBound)"
def renderInner(wrapped: Boolean): Tree = {
val unwrappedTree = q"""
val char = cursorChar
@ -532,12 +617,12 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
}
}
case class Action(actionTree: Tree, actionTypeTree: Tree) extends OpTree {
case class Action(actionTree: Tree, actionTypeTree: Tree) extends DefaultNonTerminalOpTree {
val actionType: List[Type] = actionTypeTree.tpe match {
case TypeRef(_, _, args) if args.nonEmpty args
case x c.abort(actionTree.pos, "Unexpected action type: " + x)
}
def ruleFrame = reify(RuleFrame.Action).tree
def ruleTraceNonTerminalKey = reify(RuleTrace.Action).tree
def renderInner(wrapped: Boolean): Tree = {
val argTypes = actionType dropRight 1
@ -556,9 +641,9 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
case q"(..$args ⇒ $body)"
def rewrite(tree: Tree): Tree =
tree match {
case Block(statements, res) block(statements, rewrite(res))
case x if actionType.last <:< ruleType expand(x, wrapped)
case x q"__push($x)"
case Block(statements, res) block(statements, rewrite(res))
case x if isSubClass(actionType.last, "akka.parboiled2.Rule") expand(x, wrapped)
case x q"__push($x)"
}
block(popToVals(args.map(_.name)), rewrite(body))
}
@ -567,8 +652,8 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
}
}
case class RunSubParser(fTree: Tree) extends OpTree {
def ruleFrame = reify(RuleFrame.RunSubParser).tree
case class RunSubParser(fTree: Tree) extends DefaultNonTerminalOpTree {
def ruleTraceNonTerminalKey = reify(RuleTrace.RunSubParser).tree
def renderInner(wrapped: Boolean): Tree = {
def rewrite(arg: TermName, tree: Tree): Tree =
tree match {
@ -588,6 +673,15 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
}
}
case class Fail(stringTree: Tree) extends OpTree {
def render(wrapped: Boolean): Tree = q"throw new akka.parboiled2.Parser.Fail($stringTree)"
}
case class Named(op: OpTree, stringTree: Tree) extends DefaultNonTerminalOpTree {
def ruleTraceNonTerminalKey = q"akka.parboiled2.RuleTrace.Named($stringTree)"
def renderInner(wrapped: Boolean): Tree = op.render(wrapped)
}
/////////////////////////////////// helpers ////////////////////////////////////
class Collector(
@ -613,8 +707,8 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
def Separator(op: OpTree): Separator = wrapped op.render(wrapped)
lazy val HListConsTypeSymbol = typeOf[akka.shapeless.::[_, _]].typeSymbol
lazy val HNilTypeSymbol = typeOf[akka.shapeless.HNil].typeSymbol
lazy val HListConsTypeSymbol = c.mirror.staticClass("shapeless.$colon$colon")
lazy val HNilTypeSymbol = c.mirror.staticClass("shapeless.HNil")
// tries to match and expand the leaves of the given Tree
def expand(tree: Tree, wrapped: Boolean): Tree =
@ -629,10 +723,11 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
@tailrec
private def callName(tree: Tree): Option[String] =
tree match {
case Ident(name) Some(name.decodedName.toString)
case Select(_, name) Some(name.decodedName.toString)
case Apply(fun, _) callName(fun)
case _ None
case Ident(name) Some(name.decodedName.toString)
case Select(_, name) Some(name.decodedName.toString)
case Apply(fun, _) callName(fun)
case TypeApply(fun, _) callName(fun)
case _ None
}
def block(a: Tree, b: Tree): Tree =
@ -652,4 +747,6 @@ trait OpTreeContext[OpTreeCtx <: ParserMacros.ParserContext] {
case Block(a, b) block(stmts ::: a ::: Nil, b)
case _ Block(stmts, expr)
}
def isSubClass(t: Type, fqn: String) = t.baseClasses.contains(c.mirror.staticClass(fqn))
}

View file

@ -17,5 +17,5 @@
package akka.parboiled2
package object support {
private[parboiled2] def `n/a` = throw new IllegalStateException
private[parboiled2] def `n/a` = throw new IllegalStateException("Untranslated compile-time only call")
}