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

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

/*
 * Copyright 2009-2019 Mathias Doenitz
 *
 * 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 org.parboiled2.support.hlist.HList

private[parboiled2] trait ParserMacroMethods { parser: Parser =>

  /** Converts a compile-time only rule definition into the corresponding rule method implementation.
    */
  inline def rule[I <: HList, O <: HList](inline r: Rule[I, O]): Rule[I, O] = ${ ParserMacros.ruleImpl('parser, 'r) }

  /** Converts a compile-time only rule definition into the corresponding rule method implementation
    * with an explicitly given name.
    */
  inline def namedRule[I <: HList, O <: HList](name: String)(inline r: Rule[I, O]): Rule[I, O] = ${
    ParserMacros.nameRuleImpl('parser, 'name, 'r)
  }

}

private[parboiled2] trait RuleRunnable {

  /** THIS IS NOT PUBLIC API and might become hidden in future. Use only if you know what you are doing!
    */
  extension [L <: HList](inline rule: RuleN[L]) {
    inline def run()(using scheme: Parser.DeliveryScheme[L]): scheme.Result =
      ${ ParserMacros.runImpl[L, scheme.Result]()('rule, 'scheme) }
  }
}

object ParserMacros {
  import scala.quoted._
  import scala.compiletime._

  // TODO: the `R` type parameter is a workaround for https://github.com/lampepfl/dotty/issues/13376
  // Discussion at https://github.com/sirthias/parboiled2/pull/274#issuecomment-904926294
  def runImpl[L <: HList: Type, R: Type]()(ruleExpr: Expr[RuleN[L]], schemeExpr: Expr[Parser.DeliveryScheme[L]])(using
      Quotes
  ): Expr[R] = {
    import quotes.reflect.*

    /*
    the `rule.run()` macro supports two scenarios (`rule` has type `RuleN[L]`):

    1. someParserExpression.rule[targs](args).run()(deliveryScheme)
       is re-written to
       { val p = someParserExpression
         p.__run[L](p.rule[targs](args))(deliveryScheme) }

    2. Within a Parser subclass:
         rule(...).run()(deliveryScheme)
       is re-written to
         this.__run[L](rule(...))(deliveryScheme)
       Note that `rule` is also a macro, we work with the macro expansion of the `rule` call.
     */

    case class RuleFromParser(parser: Term, rule: Symbol, targs: List[TypeTree], argss: List[List[Term]]) {
      def ruleCall[P](localParser: Expr[P]): Expr[RuleN[L]] = {
        val r = Select(localParser.asTerm, rule)
        argss.foldLeft(if (targs.isEmpty) r else TypeApply(r, targs))((t, args) => Apply(t, args)).asExprOf[RuleN[L]]
      }
    }

    object RuleFromParser {
      def dissect(t: Term, targs: List[TypeTree], argss: List[List[Term]]): (Term, List[TypeTree], List[List[Term]]) =
        t.underlyingArgument match {
          case Apply(f, args)      => dissect(f, targs, args :: argss)
          case TypeApply(f, targs) => dissect(f, targs, argss)
          case t                   => (t, targs, argss)
        }

      def unapply(t: Term): Option[RuleFromParser] = dissect(t, Nil, Nil) match {
        case (rule @ Select(parser, _), targs, argss) if parser.tpe <:< TypeRepr.of[Parser] =>
          Some(RuleFromParser(parser, rule.symbol, targs, argss))
        case _ => None
      }
    }

    def isRuleMacro(sym: Symbol) =
      sym.owner == TypeRepr.of[ParserMacroMethods].typeSymbol &&
        (sym.name == "rule" || sym.name == "namedRule")

    ruleExpr.asTerm match {
      case RuleFromParser(rule) =>
        rule.parser.tpe.asType match {
          case '[pT] =>
            // TODO: the parser type `pT` is not bounded by `<: Parser`, not sure how to do that.
            // This is why `asInstanceOf[Parser]` is needed below
            val parserExpr = rule.parser.asExprOf[pT]
            '{
              val p: pT = $parserExpr
              p.asInstanceOf[Parser].__run[L](${ rule.ruleCall('p) })($schemeExpr).asInstanceOf[R]
            }
        }
      case Inlined(_, _, Inlined(Some(ruleMacro), List(ValDef(_, _, Some(parserThis))), rule))
          if rule.tpe <:< TypeRepr.of[RuleX] && isRuleMacro(ruleMacro.symbol) =>
        // The `Inlined` tree for the `rule` macro has a binding for the parser instance.
        // TODO: we re-use the rhs of that binding (parserThis), I didn't manage to create the right This() tree.
        '{ ${ parserThis.asExprOf[Parser] }.__run[L]($ruleExpr)($schemeExpr).asInstanceOf[R] }
      case r =>
        report.error(s"""Cannot rewrite `myRule.run()` call for rule: ${ruleExpr.show}
                        |`myRule` needs to be either of the form `someParser.someRule[targs](args)`
                        |or it needs to be a `rule(...)` definition within a Parser subclass.""".stripMargin)
        throw new MatchError(r)
    }
  }

  def ruleImpl[I <: HList: Type, O <: HList: Type](parser: Expr[Parser], r: Expr[Rule[I, O]])(using
      Quotes
  ): Expr[Rule[I, O]] = {
    import quotes.reflect.*
    nameRuleImpl(parser, Expr(Symbol.spliceOwner.owner.name), r)
  }

  def nameRuleImpl[I <: HList: Type, O <: HList: Type](parser: Expr[Parser], name: Expr[String], r: Expr[Rule[I, O]])(
      using Quotes
  ): Expr[Rule[I, O]] = {
    import quotes.reflect.*

    val ctx    = new support.OpTreeContext(parser)
    val opTree = ctx.topLevel(ctx.deconstruct(r), name)

    '{
      def wrapped: Boolean = ${ opTree.render(wrapped = true) }
      val matched =
        if ($parser.__inErrorAnalysis) wrapped
        else ${ opTree.render(wrapped = false) }
      if (matched) org.parboiled2.Rule.asInstanceOf[Rule[I, O]] else null
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy