!par upgrade akka.parboiled internal copy to parboiled 2.1.0
This commit is contained in:
parent
cc661409f9
commit
aed50bc07d
15 changed files with 3293 additions and 580 deletions
|
|
@ -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
|
||||
|
|
|
|||
277
akka-parsing/src/main/scala/akka/parboiled2/ErrorFormatter.scala
Normal file
277
akka-parsing/src/main/scala/akka/parboiled2/ErrorFormatter.scala
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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]] }
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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`
|
||||
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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] {
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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`
|
||||
}
|
||||
}
|
||||
|
|
@ -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`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue