|
| 1 | + |
| 2 | +import scala.quoted._ |
| 3 | +import scala.tasty.Reflection |
| 4 | +import scala.language.implicitConversions |
| 5 | +import scala.quoted.Exprs.LiftedExpr |
| 6 | +import scala.quoted.Toolbox.Default._ |
| 7 | + |
| 8 | +object Macro { |
| 9 | + |
| 10 | + class StringContextOps(strCtx: => StringContext) { |
| 11 | + inline def s2(args: Any*): String = ~SIntepolator('(strCtx), '(args)) |
| 12 | + inline def raw2(args: Any*): String = ~RawIntepolator('(strCtx), '(args)) |
| 13 | + inline def foo(args: Any*): String = ~FooIntepolator('(strCtx), '(args)) |
| 14 | + } |
| 15 | + implicit inline def SCOps(strCtx: => StringContext): StringContextOps = new StringContextOps(strCtx) |
| 16 | +} |
| 17 | + |
| 18 | +object SIntepolator extends MacroStringInterpolator[String] { |
| 19 | + protected def interpolate(strCtx: StringContext, args: List[Expr[Any]])(implicit reflect: Reflection): Expr[String] = |
| 20 | + '((~strCtx.toExpr).s(~args.toExprOfList: _*)) |
| 21 | +} |
| 22 | + |
| 23 | +object RawIntepolator extends MacroStringInterpolator[String] { |
| 24 | + protected def interpolate(strCtx: StringContext, args: List[Expr[Any]])(implicit reflect: Reflection): Expr[String] = |
| 25 | + '((~strCtx.toExpr).raw(~args.toExprOfList: _*)) |
| 26 | +} |
| 27 | + |
| 28 | +object FooIntepolator extends MacroStringInterpolator[String] { |
| 29 | + protected def interpolate(strCtx: StringContext, args: List[Expr[Any]])(implicit reflect: Reflection): Expr[String] = |
| 30 | + '((~strCtx.toExpr).s(~args.map(_ => '("foo")).toExprOfList: _*)) |
| 31 | +} |
| 32 | + |
| 33 | +// TODO put this class in the stdlib or separate project? |
| 34 | +abstract class MacroStringInterpolator[T] { |
| 35 | + |
| 36 | + final def apply(strCtxExpr: Expr[StringContext], argsExpr: Expr[Seq[Any]])(implicit reflect: Reflection): Expr[T] = { |
| 37 | + try interpolate(strCtxExpr, argsExpr) |
| 38 | + catch { |
| 39 | + case ex: NotStaticlyKnownError => |
| 40 | + // TODO use ex.expr to recover the position |
| 41 | + throw new QuoteError(ex.getMessage) |
| 42 | + case ex: StringContextError => |
| 43 | + // TODO use ex.idx to recover the position |
| 44 | + throw new QuoteError(ex.getMessage) |
| 45 | + case ex: ArgumentError => |
| 46 | + // TODO use ex.idx to recover the position |
| 47 | + throw new QuoteError(ex.getMessage) |
| 48 | + } |
| 49 | + } |
| 50 | + |
| 51 | + protected def interpolate(strCtxExpr: Expr[StringContext], argsExpr: Expr[Seq[Any]])(implicit reflect: Reflection): Expr[T] = |
| 52 | + interpolate(getStaticStringContext(strCtxExpr), getArgsList(argsExpr)) |
| 53 | + |
| 54 | + protected def interpolate(strCtx: StringContext, argExprs: List[Expr[Any]])(implicit reflect: Reflection): Expr[T] |
| 55 | + |
| 56 | + protected def getStaticStringContext(strCtxExpr: Expr[StringContext])(implicit reflect: Reflection): StringContext = { |
| 57 | + import reflect._ |
| 58 | + strCtxExpr.unseal.underlyingArgument match { |
| 59 | + case Term.Select(Term.Typed(Term.Apply(_, List(Term.Apply(_, List(Term.Typed(Term.Repeated(strCtxArgTrees), TypeTree.Inferred()))))), _), _) => |
| 60 | + val strCtxArgs = strCtxArgTrees.map { |
| 61 | + case Term.Literal(Constant.String(str)) => str |
| 62 | + case tree => throw new NotStaticlyKnownError("Expected statically known StringContext", tree.seal[Any]) |
| 63 | + } |
| 64 | + StringContext(strCtxArgs: _*) |
| 65 | + case tree => |
| 66 | + throw new NotStaticlyKnownError("Expected statically known StringContext", tree.seal[Any]) |
| 67 | + } |
| 68 | + } |
| 69 | + |
| 70 | + protected def getArgsList(argsExpr: Expr[Seq[Any]])(implicit reflect: Reflection): List[Expr[Any]] = { |
| 71 | + import reflect._ |
| 72 | + argsExpr.unseal.underlyingArgument match { |
| 73 | + case Term.Typed(Term.Repeated(args), _) => args.map(_.seal[Any]) |
| 74 | + case tree => throw new NotStaticlyKnownError("Expected statically known argument list", tree.seal[Any]) |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + protected implicit def StringContextIsLiftable: Liftable[StringContext] = new Liftable[StringContext] { |
| 79 | + def toExpr(strCtx: StringContext): Expr[StringContext] = { |
| 80 | + // TODO define in stdlib? |
| 81 | + implicit def ListIsLiftable: Liftable[List[String]] = new Liftable[List[String]] { |
| 82 | + override def toExpr(list: List[String]): Expr[List[String]] = list match { |
| 83 | + case x :: xs => '(~x.toExpr :: ~toExpr(xs)) |
| 84 | + case Nil => '(Nil) |
| 85 | + } |
| 86 | + } |
| 87 | + '(StringContext(~strCtx.parts.toList.toExpr: _*)) |
| 88 | + } |
| 89 | + } |
| 90 | + |
| 91 | + protected class NotStaticlyKnownError(msg: String, expr: Expr[Any]) extends Exception(msg) |
| 92 | + protected class StringContextError(msg: String, idx: Int, start: Int = -1, end: Int = -1) extends Exception(msg) |
| 93 | + protected class ArgumentError(msg: String, idx: Int) extends Exception(msg) |
| 94 | + |
| 95 | +} |
0 commit comments