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

org.scalatest.diagrams.DiagramsMacro.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2001-2012 Artima, Inc.
 *
 * 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.scalatest.diagrams

import org.scalactic._
import scala.quoted._
import org.scalatest.Assertions
import org.scalatest.compatible.Assertion

object DiagramsMacro {
  // Transform the input expression by parsing out the anchor and generate expression that can support diagram rendering
  def parse(using Quotes)(expr: quotes.reflect.Term): quotes.reflect.Term = {
    import quotes.reflect._
    import util._
    import ValDef.let

    expr.tpe.asType match {
      case '[r] =>
        def isXmlSugar(apply: Apply): Boolean = apply.tpe <:< TypeRepr.of[scala.xml.Elem]
        def isJavaStatic(tree: Tree): Boolean = tree.symbol.flags.is(Flags.Static)
        def isImplicitMethodType(tp: TypeRepr): Boolean = tp match {
          case tp: MethodType => tp.isImplicit
          case _ => false
        }

        def selectField(o: Term, name: String): Term = Select.unique(o, name)

        def default(term: Term): Term = term.asExpr match {
          case '{ $x: t } => '{ DiagrammedExpr.simpleExpr[t]($x, ${ getAnchor(term) } ) }.asTerm
        }

        def byNameExpr(term: Term): Term = term.asExpr match {
          case '{ $x: t } => '{ DiagrammedExpr.byNameExpr[t]($x, ${ getAnchor(term) } ) }.asTerm
        }

        def xmlSugarExpr(term: Term): Term = term.asExpr match {
          case '{ $x: t } => '{ 
            DiagrammedExpr.simpleExpr[t]($x, ${ 
              // https://docs.scala-lang.org/scala3/reference/metaprogramming/reflection.html#positions
              val anchor = expr.pos.startColumn - Position.ofMacroExpansion.startColumn
              val c = expr.pos.sourceCode.getOrElse("").head
              Expr(anchor - (if (c == '<') 0 else 1)) 
            } ) 
          }.asTerm
        }

        def getAnchorForSelect(sel: Select): Expr[Int] = {
          if (sel.name == "unary_!")
            Expr(sel.pos.startColumn - Position.ofMacroExpansion.startColumn)
          else {
            val selOffset = sel.pos.endColumn - sel.qualifier.pos.endColumn - sel.name.length
            Expr(sel.qualifier.pos.endColumn + selOffset - Position.ofMacroExpansion.startColumn)
          }
        }

        def getAnchor(expr: Term): Expr[Int] = {
          // -1 to match scala2 position
          // Expr((expr.asTerm.pos.endColumn + expr.asTerm.pos.startColumn - 1) / 2 - Position.ofMacroExpansion.startColumn)
          Expr(expr.pos.startColumn - Position.ofMacroExpansion.startColumn)
        }

        def handleArgs(argTps: List[TypeRepr], args: List[Term]): (List[Term], List[Term]) =
          args.zip(argTps).foldLeft(Nil -> Nil : (List[Term], List[Term])) { case ((diagrams, others), pair) =>
            pair match {
              case (Typed(Repeated(args, _), _), AppliedType(_, _)) =>
                (diagrams :++ args.map(parse), others)
              case (arg, ByNameType(_)) =>
                (diagrams, others :+ byNameExpr(arg))
              case (arg, tp) =>
                if (tp.widen.typeSymbol.fullName.startsWith("scala.Function")) (diagrams, others :+ byNameExpr(arg))
                else (diagrams :+ parse(arg), others)
            }
          }

        expr match {
          case apply: Apply if isXmlSugar(apply) => xmlSugarExpr(expr)

          case Apply(Select(New(_), _), _) => default(expr)

          case apply: Apply if isJavaStatic(apply) => default(expr)

          case Select(This(_), _) => default(expr)

          case x: Select if x.symbol.flags.is(Flags.Module) => default(expr)

          case x: Select if isJavaStatic(x) => default(expr)

          case sel @ Select(qual, name) =>
            parse(qual).asExpr match {
              case '{ $obj: DiagrammedExpr[t] } =>
                val anchor = getAnchorForSelect(sel)
                '{
                  val o = $obj
                  DiagrammedExpr.selectExpr[r](o, ${ selectField('{o.value}.asTerm, name).asExprOf[r] }, $anchor)
                }.asTerm
            }

          case Block(stats, expr) =>
            // call parse recursively using the expr argument if it is a block
            Block(stats, parse(expr))
          case Apply(sel @ Select(lhs, op), rhs :: Nil) =>
            val anchor = getAnchorForSelect(sel)
            op match {
              case "||" | "|" =>
                val left = parse(lhs).asExprOf[DiagrammedExpr[Boolean]]
                val right = parse(rhs).asExprOf[DiagrammedExpr[Boolean]]

                '{
                  val l = $left
                  if (l.value) l
                  else {
                    val r = $right
                    DiagrammedExpr.applyExpr[Boolean](l, r :: Nil, r.value, $anchor)
                  }
                }.asTerm
              case "&&" | "&" =>
                val left = parse(lhs).asExprOf[DiagrammedExpr[Boolean]]
                val right = parse(rhs).asExprOf[DiagrammedExpr[Boolean]]
                '{
                  val l = $left
                  if (!l.value) l
                  else {
                    val r = $right
                    DiagrammedExpr.applyExpr[Boolean](l, r :: Nil, r.value, $anchor)
                  }
                }.asTerm
              case _ =>
                val left = parse(lhs)

                val methTp = sel.tpe.widen.asInstanceOf[MethodType]
                val (diagrams, others) = handleArgs(methTp.paramTypes, rhs :: Nil)

                let(Symbol.spliceOwner, left) { l =>
                  let(Symbol.spliceOwner, diagrams) { rs =>
                    let(Symbol.spliceOwner, others) { os =>
                      l.asExpr match {
                        case '{ $left: DiagrammedExpr[t] } =>
                          val rights = rs.map(_.asExprOf[DiagrammedExpr[_]])
                          val res = Select.overloaded(Select.unique(l, "value"), op, Nil, rs.map(r => Select.unique(r, "value")) ++ os.map(o => Select.unique(o, "value"))).asExprOf[r]
                          '{ DiagrammedExpr.applyExpr[r]($left, ${Expr.ofList(rights)}, $res, $anchor) }.asTerm
                      }
                    }
                  }
                }
            }

          case Apply(sel @ Select(lhs, op), args) =>
            val left = parse(lhs)
            val anchor = getAnchorForSelect(sel)

            val methTp = sel.tpe.widen.asInstanceOf[MethodType]
            val (diagrams, others) = handleArgs(methTp.paramTypes, args)

            let(Symbol.spliceOwner, left) { l =>
              let(Symbol.spliceOwner, diagrams) { rs =>
                let(Symbol.spliceOwner, others) { os =>
                  l.asExpr match {
                    case '{ $left: DiagrammedExpr[t] } =>
                      val rights = rs.map(_.asExprOf[DiagrammedExpr[_]])
                      val res = Select.overloaded(Select.unique(l, "value"), op, Nil, rs.map(r => Select.unique(r, "value")) ++ os.map(o => Select.unique(o, "value"))).asExprOf[r]
                      '{ DiagrammedExpr.applyExpr[r]($left, ${Expr.ofList(rights)}, $res, $anchor) }.asTerm
                  }
                }
              }
            }

          case Apply(f @ Apply(sel @ Select(Apply(qual, lhs :: Nil), op @ ("===" | "!==")), rhs :: Nil), implicits)
          if isImplicitMethodType(f.tpe) =>
            val left = parse(lhs)
            val right = parse(rhs)
            val anchor = getAnchorForSelect(sel)

            let(Symbol.spliceOwner, left) { left =>
              let(Symbol.spliceOwner, right) { right =>
                val app = qual.appliedTo(Select.unique(left, "value")).select(sel.symbol)
                              .appliedTo(Select.unique(right, "value")).appliedToArgs(implicits)
                let(Symbol.spliceOwner, app) { result =>
                  val l = left.asExprOf[DiagrammedExpr[_]]
                  val r = right.asExprOf[DiagrammedExpr[_]]
                  val b = result.asExprOf[Boolean]
                  '{ DiagrammedExpr.applyExpr[Boolean]($l, $r :: Nil, $b, $anchor) }.asTerm
                }
              }
            }

          case Apply(fun @ TypeApply(sel @ Select(lhs, op), targs), args) =>
            val left = parse(lhs)
            val anchor = getAnchorForSelect(sel)

            val methTp = fun.tpe.widen.asInstanceOf[MethodType]
            val (diagrams, others) = handleArgs(methTp.paramTypes, args)

            let(Symbol.spliceOwner, left) { l =>
              let(Symbol.spliceOwner, diagrams) { rs =>
                let(Symbol.spliceOwner, others) { os =>
                  l.asExpr match {
                    case '{ $left: DiagrammedExpr[t] } =>
                      val rights = rs.map(_.asExprOf[DiagrammedExpr[_]])
                      val res = Select.overloaded(Select.unique(l, "value"), op, targs.map(_.tpe), rs.map(r => Select.unique(r, "value")) ++ os.map(o => Select.unique(o, "value"))).asExprOf[r]
                      '{ DiagrammedExpr.applyExpr[r]($left, ${Expr.ofList(rights)}, $res, $anchor) }.asTerm
                  }
                }
              }
            }

          case TypeApply(sel @ Select(lhs, op), targs) =>
            val left = parse(lhs)
            val anchor = getAnchorForSelect(sel)

            let(Symbol.spliceOwner, left) { l =>
              l.asExpr match {
                case '{ $left: DiagrammedExpr[t] } =>
                  val res = Select.unique(l, "value").select(sel.symbol).appliedToTypes(targs.map(_.tpe)).asExprOf[r]
                  '{ DiagrammedExpr.applyExpr[r]($left, Nil, $res, $anchor) }.asTerm
              }
            }

          case _ =>
            default(expr)
        }
    }
  }

  def transform(
    helper: Expr[(DiagrammedExpr[Boolean], Any, String, source.Position) => Assertion],
    condition: Expr[Boolean], pos: Expr[source.Position], clue: Expr[Any], sourceText: String
  )(using Quotes): Expr[Assertion] = {
    import quotes.reflect._
    val diagExpr = parse(condition.asTerm.underlyingArgument).asExprOf[DiagrammedExpr[Boolean]]
    '{ $helper($diagExpr, $clue, ${Expr(sourceText)}, $pos) }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy