Изучение дерева выражений в макросе scala - PullRequest
0 голосов
/ 05 декабря 2018

Я изучаю scala, и для выполнения задания мне нужно написать макрос.
Макрос должен исследовать дерево выражений и затем создать пользовательский Expression.Мне удалось «взглянуть» на выражение, добавив println(showRaw(exprTree)).Однако я все еще не смог перебрать его и построить Expression

У меня есть следующие два файла:
ExpressionImplicits.scala :

import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context

// Expression is defined elsewhere and mainly only overrides toString()
abstract class Expression
case class Var(name: String) extends Expression
case class Number(num: Double) extends Expression
case class BinOp(operator: String, left: Expression, right: Expression) extends Expression

class ExpressionImplicitsImpl(val c: Context) {

  import c.universe._

  // Task complete macro
  // Add necessary definitions here
  // This definition was added by me
  def expr(exprTree: c.Expr[AnyRef]): c.Expr[Expression] = {
    println(showRaw(exprTree)) 
    //prints
    //Expr(Function(List(ValDef(Modifiers(PARAM), TermName("x"), TypeTree().setOriginal(Select(Ident(scala), scala.Double)), EmptyTree)), Apply(Select(Apply(Select(Ident(TermName("x")), TermName("$times")), List(Literal(Constant(2)))), TermName("$plus")), List(Apply(Select(Literal(Constant(3.0)), TermName("$times")), List(Ident(TermName("x"))))))))
    //Expr(Function(List(ValDef(Modifiers(PARAM), TermName("x"), TypeTree().setOriginal(Select(Ident(scala), scala.Double)), EmptyTree), ValDef(Modifiers(PARAM), TermName("y"), TypeTree().setOriginal(Select(Ident(scala), scala.Double)), EmptyTree)), Apply(Select(Apply(Select(Ident(TermName("x")), TermName("$times")), List(Ident(TermName("y")))), TermName("$times")), List(Ident(TermName("x"))))))

  }
}

// This definition is given
object ExpressionImplicits {
  def expr(exprTree: AnyRef): Expression = macro ExpressionImplicitsImpl.expr
}

ExpressionsTest.scala :

object ExpressionsTest {
  def main(args: Array[String]) {

    import ExpressionImplicits._
    val e1 = expr { (x: Double) => (x * 2) + (3.0 * x) }

    println(e1) // BinOp(+,BinOp(*,Var(x),Number(2.0)),BinOp(*,Number(3.0),Var(x)))

    val e2 = expr { (x: Double, y: Double) => x * y * x }
    println(e2) // BinOp(*,BinOp(*,Var(x),Var(y)),Var(x))

    // val e3 = expr { (x: Double) => x.toInt } // Fails during compilation
  }

}

1 Ответ

0 голосов
/ 06 декабря 2018

Вы очень близки.Теперь вам нужно сопоставлять только выражения, которые showRaw выгружены.

Вот полное решение :

object ExpressionImplicits {

  def expr(expr: AnyRef): Expression = macro expr_impl

  def expr_impl(c: blackbox.Context)(expr: c.Expr[AnyRef]): c.Expr[Expression] = {
    import c.universe._

    def treeToExpression(functionBody: c.Tree): c.Expr[Expression] = {
      functionBody match {
        case Apply(Select(leftTree, operator), List(rightTree)) =>
          val operatorName = Constant(operator.toString)
          c.Expr[Expression](q"sk.ygor.stackoverflow.q53326545.macros.BinOp($operatorName, ${treeToExpression(leftTree)}, ${treeToExpression(rightTree)})")
        case Ident(TermName(varName)) =>
          c.Expr[Expression](q"sk.ygor.stackoverflow.q53326545.macros.Var($varName)")
        case Literal(Constant(num)) if num.isInstanceOf[java.lang.Number] =>
          c.Expr[Expression](q"sk.ygor.stackoverflow.q53326545.macros.Number(${num.asInstanceOf[java.lang.Number].doubleValue()})")
        case unsupported =>
          sys.error("Unsupported function body: " + unsupported);
      }
    }

    expr.tree match {
      case Function(_, body) => treeToExpression(body)
      case unsupported =>
        sys.error("Only functions are accepted. Got: " + unsupported);
    }

  }
}

Вы должны попытаться понять, что такоепроисходит:

  • Мы выполняем обход дерева через сопоставление с образцом и рекурсию.Ссылка уже была предоставлена ​​в комментарии: https://docs.scala -lang.org / Overviews / отражение / symbols-trees-types.html # traversing-trees
  • Только первое совпадение проверяет, чтоверхнее дерево является определением функции
  • Мы рекурсивно сопоставляем тело функции
    • List(rightTree) означает, что мы ожидаем методы с ровно одним аргументом, например x.foo(y), x foo y, x.+(y), x + y, но не x.foo(), x.foo(y, z), x.+(y, z)
  • Мы строим и объединяем части выходного дерева, используя макрос Scala квазицитаты
  • Мы используем полностью определенные имена для BinOp, Var и Number, поэтому потребителю макроса не нужно импортировать эти подклассы
...