All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.parboiled2.Parser.scala Maven / Gradle / Ivy

/*
 * 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 org.parboiled2

import scala.annotation.tailrec
import scala.collection.immutable.VectorBuilder
import scala.collection.mutable
import scala.util.{ Failure, Success, Try }
import scala.util.control.{ NonFatal, NoStackTrace }
import shapeless._
import org.parboiled2.support._

abstract class Parser(initialValueStackSize: Int = 16,
                      maxValueStackSize: Int = 1024) extends RuleDSL {
  import Parser._

  require(maxValueStackSize <= 65536, "`maxValueStackSize` > 2^16 is not supported") // due to current snapshot design

  /**
   * The input this parser instance is running against.
   */
  def input: ParserInput

  /**
   * Converts a compile-time only rule definition into the corresponding rule method implementation.
   */
  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`!
   */
  def cursor: Int = _cursor

  /**
   * The next (yet unmatched) input character, i.e. the one at the `cursor` index.
   * Identical to `if (cursor < input.length) input.charAt(cursor) else EOI` but more efficient.
   */
  def cursorChar: Char = _cursorChar

  /**
   * Returns the last character that was matched, i.e. the one at index cursor - 1
   * Note: for performance optimization this method does *not* do a range check,
   * 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 lastChar: Char = charAt(-1)

  /**
   * Returns the character at the input index with the given delta to the cursor.
   * Note: for performance optimization this method does *not* do a range check,
   * 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)

  /**
   * Same as `charAt` but range-checked.
   * Returns the input character at the index with the given offset from the cursor.
   * If this index is out of range the method returns `EOI`.
   */
  def charAtRC(offset: Int): Char = {
    val ix = _cursor + offset
    if (0 <= ix && ix < input.length) input.charAt(ix) else EOI
  }

  /**
   * Allows "raw" (i.e. untyped) access to the `ValueStack`.
   * In most cases you shouldn't need to access the value stack directly from your code.
   * Use only if you know what you are doing!
   */
  def valueStack: ValueStack = _valueStack

  /**
   * 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 errorTraceCollectionLimit: Int = 24

  /**
   * Formats the given [[ParseError]] into a String using the given formatter instance.
   */
  def formatError(error: ParseError, formatter: ErrorFormatter = new ErrorFormatter()): String =
    formatter.format(error, input)

  ////////////////////// INTERNAL /////////////////////////

  // the char at the current input index
  private var _cursorChar: Char = _

  // the index of the current input char
  private var _cursor: Int = _

  // the value stack instance we operate on
  private var _valueStack: ValueStack = _

  // 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
    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 __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(): Boolean = {
      _cursor = -1
      __advance()
      valueStack.clear()
      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 phase4_collectRuleTraces(reportedErrorIndex: Int, principalErrorIndex: Int, reportQuiet: Boolean)(
      phase3: CollectingRuleTraces = new CollectingRuleTraces(reportedErrorIndex, reportQuiet),
      traces: VectorBuilder[RuleTrace] = new VectorBuilder): ParseError = {

      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
    }

    try {
      if (phase0_initialRun())
        scheme.success(valueStack.toHList[L]())
      else {
        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 e: Parser.Fail ⇒
        val pos = Position(cursor, input)
        scheme.parseError(ParseError(pos, pos, RuleTrace(Nil, RuleTrace.Fail(e.expected)) :: Nil))
      case NonFatal(e) ⇒
        scheme.failure(e)
    } finally {
      phase = null
    }
  }

  /**
   * THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
   */
  def __advance(): Boolean = {
    var c = _cursor
    val max = input.length
    if (c < max) {
      c += 1
      _cursor = c
      _cursorChar = if (c == max) EOI else input charAt c
    }
    true
  }

  /**
   * THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
   */
  def __updateMaxCursor(): Boolean = {
    phase match {
      case x: EstablishingPrincipalErrorIndex ⇒ if (_cursor > x.maxCursor) x.maxCursor = _cursor
      case _                                  ⇒ // nothing to do
    }
    true
  }

  /**
   * THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
   */
  def __saveState: Mark = new Mark((_cursor.toLong << 32) + (_cursorChar.toLong << 16) + valueStack.size)

  /**
   * THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
   */
  def __restoreState(mark: Mark): Unit = {
    _cursor = (mark.value >>> 32).toInt
    _cursorChar = ((mark.value >>> 16) & 0x000000000000FFFF).toChar
    valueStack.size = (mark.value & 0x000000000000FFFF).toInt
  }

  /**
   * THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
   */
  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: 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 = {
    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!
   */
  def __push(value: Any): Boolean = {
    value match {
      case ()       ⇒
      case x: HList ⇒ valueStack.pushAll(x)
      case x        ⇒ valueStack.push(x)
    }
    true
  }

  /**
   * THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
   */
  @tailrec final def __matchString(string: String, ix: Int = 0): Boolean =
    if (ix < string.length)
      if (_cursorChar == string.charAt(ix)) {
        __advance()
        __matchString(string, ix + 1)
      } else false
    else true

  /**
   * 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, ix: Int = 0): Boolean =
    if (ix < string.length)
      if (_cursorChar == string.charAt(ix)) {
        __advance()
        __updateMaxCursor()
        __matchStringWrapped(string, ix + 1)
      } else {
        try __registerMismatch()
        catch {
          case Parser.StartTracingException ⇒
            import RuleTrace._
            __bubbleUp(NonTerminal(StringMatch(string), -ix) :: Nil, CharMatch(string charAt ix))
        }
      }
    else true

  /**
   * THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
   */
  @tailrec final def __matchIgnoreCaseString(string: String, ix: Int = 0): Boolean =
    if (ix < string.length)
      if (Character.toLowerCase(_cursorChar) == string.charAt(ix)) {
        __advance()
        __matchIgnoreCaseString(string, ix + 1)
      } else false
    else true

  /**
   * 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, ix: Int = 0): Boolean =
    if (ix < string.length)
      if (Character.toLowerCase(_cursorChar) == string.charAt(ix)) {
        __advance()
        __updateMaxCursor()
        __matchIgnoreCaseStringWrapped(string, ix + 1)
      } else {
        try __registerMismatch()
        catch {
          case Parser.StartTracingException ⇒
            import RuleTrace._
            __bubbleUp(NonTerminal(IgnoreCaseString(string), -ix) :: Nil, IgnoreCaseChar(string charAt ix))
        }
      }
    else true

  /**
   * THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
   */
  @tailrec final def __matchAnyOf(string: String, ix: Int = 0): Boolean =
    if (ix < string.length)
      if (string.charAt(ix) == _cursorChar) __advance()
      else __matchAnyOf(string, ix + 1)
    else false

  /**
   * THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
   */
  @tailrec final def __matchNoneOf(string: String, ix: Int = 0): Boolean =
    if (ix < string.length)
      _cursorChar != EOI && string.charAt(ix) != _cursorChar && __matchNoneOf(string, ix + 1)
    else __advance()

  /**
   * THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
   */
  def __matchMap(m: Map[String, Any], ignoreCase: Boolean): Boolean = {
    val prioritizedKeys = new mutable.PriorityQueue[String]()(Ordering.by(_.length))
    prioritizedKeys ++= m.keysIterator
    while (prioritizedKeys.nonEmpty) {
      val mark = __saveState
      val key = prioritizedKeys.dequeue()
      val matchResult =
        if (ignoreCase) __matchIgnoreCaseString(key)
        else __matchString(key)
      if (matchResult) {
        __push(m(key))
        return true
      } else __restoreState(mark)
    }
    false
  }

  /**
   * 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], ignoreCase: Boolean): Boolean = {
    val prioritizedKeys = new mutable.PriorityQueue[String]()(Ordering.by(_.length))
    prioritizedKeys ++= m.keysIterator
    val start = _cursor
    try {
      while (prioritizedKeys.nonEmpty) {
        val mark = __saveState
        val key = prioritizedKeys.dequeue()
        val matchResult =
          if (ignoreCase) __matchIgnoreCaseStringWrapped(key)
          else __matchStringWrapped(key)
        if (matchResult) {
          __push(m(key))
          return true
        } else __restoreState(mark)
      }
      false
    } catch {
      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
    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)
    def length: Int = input.length - offset
    def charAt(ix: Int): Char = input.charAt(offset + ix)
  }
}

object Parser {

  trait DeliveryScheme[L <: HList] {
    type Result
    def success(result: L): Result
    def parseError(error: ParseError): Result
    def failure(error: Throwable): Result
  }

  object DeliveryScheme extends AlternativeDeliverySchemes {
    implicit def Try[L <: HList, Out](implicit unpack: Unpack.Aux[L, Out]) =
      new DeliveryScheme[L] {
        type Result = Try[Out]
        def success(result: L) = Success(unpack(result))
        def parseError(error: ParseError) = Failure(error)
        def failure(error: Throwable) = Failure(error)
      }
  }
  sealed abstract class AlternativeDeliverySchemes {
    implicit def Either[L <: HList, Out](implicit unpack: Unpack.Aux[L, Out]) =
      new DeliveryScheme[L] {
        type Result = Either[ParseError, Out]
        def success(result: L) = Right(unpack(result))
        def parseError(error: ParseError) = Left(error)
        def failure(error: Throwable) = throw error
      }
    implicit def Throw[L <: HList, Out](implicit unpack: Unpack.Aux[L, Out]) =
      new DeliveryScheme[L] {
        type Result = Out
        def success(result: L) = unpack(result)
        def parseError(error: ParseError) = throw error
        def failure(error: Throwable) = throw error
      }
  }

  /**
   * THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
   */
  class Mark private[Parser] (val value: Long) extends AnyVal

  /**
   * THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
   */
  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
    }
  }

  // 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
  }
}

object ParserMacros {
  import scala.reflect.macros.whitebox.Context

  /**
   * THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
   */
  type RunnableRuleContext[L <: HList] = Context { type PrefixType = Rule.Runnable[L] }

  def runImpl[L <: HList: c.WeakTypeTag](c: RunnableRuleContext[L])()(scheme: c.Expr[Parser.DeliveryScheme[L]]): c.Expr[scheme.value.Result] = {
    import c.universe._
    val runCall = c.prefix.tree match {
      case q"parboiled2.this.Rule.Runnable[$l]($ruleExpr)" ⇒ ruleExpr match {
        case q"$p.$r" if p.tpe <:< typeOf[Parser] ⇒ q"val p = $p; p.__run[$l](p.$r)($scheme)"
        case q"$p.$r($args)" if p.tpe <:< typeOf[Parser] ⇒ q"val p = $p; p.__run[$l](p.$r($args))($scheme)"
        case q"$p.$r[$t]" if p.tpe <:< typeOf[Parser] ⇒ q"val p = $p; p.__run[$l](p.$r[$t])($scheme)"
        case q"$p.$r[$t]" if p.tpe <:< typeOf[RuleX] ⇒ q"__run[$l]($ruleExpr)($scheme)"
        case x ⇒ c.abort(x.pos, "Illegal `.run()` call base: " + x)
      }
      case x ⇒ c.abort(x.pos, "Illegal `Runnable.apply` call: " + x)
    }
    c.Expr[scheme.value.Result](runCall)
  }

  /**
   * THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
   */
  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]] = {
    import ctx.universe._
    namedRuleImpl(ctx)(ctx.Expr[String](Literal(Constant(ctx.internal.enclosingOwner.name.decodedName.toString))))(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) org.parboiled2.Rule else null""" // we encode the "matched" boolean as 'ruleResult ne null'

    reify { ctx.Expr[RuleX](ruleTree).splice.asInstanceOf[Rule[I, O]] }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy