diff --git a/sjsonnet/src/sjsonnet/Materializer.scala b/sjsonnet/src/sjsonnet/Materializer.scala index cfffc6ff..5e4b2633 100644 --- a/sjsonnet/src/sjsonnet/Materializer.scala +++ b/sjsonnet/src/sjsonnet/Materializer.scala @@ -62,7 +62,8 @@ abstract class Materializer { "Couldn't manifest function with params [" + s.params.names.mkString(",") + "]", v.pos ) - case vv: Val => + case mat: Materializer.Materializable => storePos(v.pos); mat.materialize(visitor) + case vv: Val => Error.fail("Unknown value type " + vv.prettyName, vv.pos) case null => Error.fail("Unknown value type " + v) @@ -147,4 +148,12 @@ object Materializer extends Materializer { final val emptyStringArray = new Array[String](0) final val emptyLazyArray = new Array[Lazy](0) + + /** + * Trait for providing custom materialization logic to the Materializer. + * @since 1.0.0 + */ + trait Materializable { + def materialize[T](visitor: Visitor[T, T])(implicit evaluator: EvalScope): T + } } diff --git a/sjsonnet/test/src/sjsonnet/CustomValTests.scala b/sjsonnet/test/src/sjsonnet/CustomValTests.scala new file mode 100644 index 00000000..cf89b5c6 --- /dev/null +++ b/sjsonnet/test/src/sjsonnet/CustomValTests.scala @@ -0,0 +1,69 @@ +package sjsonnet + +import upickle.core.Visitor +import utest._ + +object CustomValTests extends TestSuite { + private final case class ImportantString(pos: Position, str: String, importance: Int) + extends Val.Literal + with Materializer.Materializable { + override def prettyName: String = "Important string" + def materialize[T](visitor: Visitor[T, T])(implicit evaluator: EvalScope): T = { + visitor.visitString(str + "!".repeat(importance), -1) + } + } + + private final class IncreaseImportance + extends Val.Builtin1("increaseImportance", "important_string") { + def evalRhs(arg1: Lazy, ev: EvalScope, pos: Position): Val = { + arg1.force match { + case importantString: ImportantString => + importantString.copy(importance = importantString.importance + 1) + case other => other + } + } + } + + private def variableResolve(name: String): Option[Expr] = { + if (name == "message") { + Some(ImportantString(new Position(null, 0), "message", 2)) + } else if (name == "increaseImportance") { + Some(new IncreaseImportance().asFunc) + } else { + None + } + } + + private val interpreter = new Interpreter( + Map(), + Map(), + DummyPath(), + Importer.empty, + parseCache = new DefaultParseCache, + variableResolver = variableResolve + ) + + def check(s: String)(f: Function[Any, Boolean]): Unit = { + val result = interpreter.interpret(s, DummyPath("(memory)")) + assertMatch(result) { + case Right(v) if f(v) => () + case Left(e) => throw new Exception(s"check failed: $s, $e") + } + } + + def tests: Tests = Tests { + test("test custom Val materialization") { + check(s"""important""") { + case ujson.Str(v) => v == "message!!" + case _ => false + } + } + + test("test custom Val usage") { + check(s"""increaseImportance(message)""") { + case ujson.Str(v) => v == "message!!!" + case _ => false + } + } + } +}