diff --git a/bench/src/main/scala/sjsonnet/MainBenchmark.scala b/bench/src/main/scala/sjsonnet/MainBenchmark.scala index 54722a1a..bd3ab6d9 100644 --- a/bench/src/main/scala/sjsonnet/MainBenchmark.scala +++ b/bench/src/main/scala/sjsonnet/MainBenchmark.scala @@ -22,7 +22,7 @@ object MainBenchmark { val parseCache = new DefaultParseCache val interp = new Interpreter( Map.empty[String, String], - Map.empty[String, ujson.Value], + Map.empty[String, String], OsPath(wd), importer = SjsonnetMain.resolveImport(config.jpaths.map(os.Path(_, wd)).map(OsPath(_)), None), parseCache = parseCache diff --git a/bench/src/main/scala/sjsonnet/MaterializerBenchmark.scala b/bench/src/main/scala/sjsonnet/MaterializerBenchmark.scala index b23c86fe..9152f6f0 100644 --- a/bench/src/main/scala/sjsonnet/MaterializerBenchmark.scala +++ b/bench/src/main/scala/sjsonnet/MaterializerBenchmark.scala @@ -29,7 +29,7 @@ class MaterializerBenchmark { var currentPos: Position = null this.interp = new Interpreter( Map.empty[String, String], - Map.empty[String, ujson.Value], + Map.empty[String, String], OsPath(wd), importer = SjsonnetMain.resolveImport(config.jpaths.map(os.Path(_, wd)).map(OsPath(_)), None), parseCache = new DefaultParseCache diff --git a/bench/src/main/scala/sjsonnet/RunProfiler.scala b/bench/src/main/scala/sjsonnet/RunProfiler.scala index 9b5d8ecd..85177b7a 100644 --- a/bench/src/main/scala/sjsonnet/RunProfiler.scala +++ b/bench/src/main/scala/sjsonnet/RunProfiler.scala @@ -11,7 +11,7 @@ object RunProfiler extends App { val parseCache = new DefaultParseCache val interp = new Interpreter( Map.empty[String, String], - Map.empty[String, ujson.Value], + Map.empty[String, String], OsPath(wd), importer = SjsonnetMain.resolveImport(config.jpaths.map(os.Path(_, wd)).map(OsPath(_)), None), parseCache = parseCache diff --git a/sjsonnet/src-js/sjsonnet/SjsonnetMain.scala b/sjsonnet/src-js/sjsonnet/SjsonnetMain.scala index 8ee10165..f88ab814 100644 --- a/sjsonnet/src-js/sjsonnet/SjsonnetMain.scala +++ b/sjsonnet/src-js/sjsonnet/SjsonnetMain.scala @@ -16,7 +16,7 @@ object SjsonnetMain { preserveOrder: Boolean = false): js.Any = { val interp = new Interpreter( ujson.WebJson.transform(extVars, ujson.Value).obj.toMap.map{case (k, ujson.Str(v)) => (k, v)}, - ujson.WebJson.transform(tlaVars, ujson.Value).obj.toMap, + ujson.WebJson.transform(tlaVars, ujson.Value).obj.toMap.map{case (k, ujson.Str(v)) => (k, v)}, JsVirtualPath(wd0), new Importer { def resolve(docBase: Path, importName: String): Option[Path] = diff --git a/sjsonnet/src-jvm-native/sjsonnet/SjsonnetMain.scala b/sjsonnet/src-jvm-native/sjsonnet/SjsonnetMain.scala index b200dac4..2b5b0d36 100644 --- a/sjsonnet/src-jvm-native/sjsonnet/SjsonnetMain.scala +++ b/sjsonnet/src-jvm-native/sjsonnet/SjsonnetMain.scala @@ -143,6 +143,27 @@ object SjsonnetMain { def isScalar(v: ujson.Value) = !v.isInstanceOf[ujson.Arr] && !v.isInstanceOf[ujson.Obj] + def parseBindings(strs: Seq[String], + strFiles: Seq[String], + codes: Seq[String], + codeFiles: Seq[String], + wd: os.Path) = { + + def split(s: String) = s.split("=", 2) match{ + case Array(x) => (x, System.getenv(x)) + case Array(x, v) => (x, v) + } + + def splitMap(s: Seq[String], f: String => String) = s.map(split).map{case (x, v) => (x, f(v))} + def readPath(v: String) = os.read(os.Path(v, wd)) + + Map() ++ + splitMap(strs, v => ujson.write(v)) ++ + splitMap(strFiles, v => ujson.write(readPath(v))) ++ + splitMap(codes, identity) ++ + splitMap(codeFiles, readPath) + } + /** * @return Right(str) if there's some string that needs to be printed to stdout or * --output-file, Left(err) if there is an error to be reported @@ -159,45 +180,21 @@ object SjsonnetMain { val (jsonnetCode, path) = if (config.exec.value) (file, wd / "") else (os.read(os.Path(file)), os.Path(file)) - var varBinding = Map.empty[String, String] - config.extStr.map(_.split("=", 2)).foreach{ - case Array(x) => varBinding = varBinding ++ Seq(x -> ujson.write(System.getenv(x))) - case Array(x, v) => varBinding = varBinding ++ Seq(x -> ujson.write(v)) - } - config.extStrFile.map(_.split("=", 2)).foreach { - case Array(x, v) => - varBinding = varBinding ++ Seq(x -> ujson.write(os.read(os.Path(v, wd)))) - } - config.extCode.map(_.split("=", 2)).foreach { - case Array(x) => varBinding = varBinding ++ Seq(x -> System.getenv(x)) - case Array(x, v) => varBinding = varBinding ++ Seq(x -> v) - } - config.extCodeFile.map(_.split("=", 2)).foreach { - case Array(x, v) => - varBinding = varBinding ++ Seq(x -> os.read(os.Path(v, wd))) - } + val extBinding = parseBindings( + config.extStr, config.extStrFile, + config.extCode, config.extCodeFile, + wd + ) - var tlaBinding = Map.empty[String, ujson.Value] + val tlaBinding = parseBindings( + config.tlaStr, config.tlaStrFile, + config.tlaCode, config.tlaCodeFile, + wd + ) - config.tlaStr.map(_.split("=", 2)).foreach{ - case Array(x) => tlaBinding = tlaBinding ++ Seq(x -> ujson.Str(System.getenv(x))) - case Array(x, v) => tlaBinding = tlaBinding ++ Seq(x -> ujson.Str(v)) - } - config.tlaStrFile.map(_.split("=", 2)).foreach { - case Array(x, v) => - tlaBinding = tlaBinding ++ Seq(x -> ujson.Str(os.read(os.Path(v, wd)))) - } - config.tlaCode.map(_.split("=", 2)).foreach { - case Array(x) => tlaBinding = tlaBinding ++ Seq(x -> ujson.read(System.getenv(x))) - case Array(x, v) => tlaBinding = tlaBinding ++ Seq(x -> ujson.read(v)) - } - config.tlaCodeFile.map(_.split("=", 2)).foreach { - case Array(x, v) => - tlaBinding = tlaBinding ++ Seq(x -> ujson.read(os.read(os.Path(v, wd)))) - } var currentPos: Position = null val interp = new Interpreter( - varBinding, + extBinding, tlaBinding, OsPath(wd), importer = importer match{ diff --git a/sjsonnet/src/sjsonnet/Interpreter.scala b/sjsonnet/src/sjsonnet/Interpreter.scala index 06a066d1..b415df08 100644 --- a/sjsonnet/src/sjsonnet/Interpreter.scala +++ b/sjsonnet/src/sjsonnet/Interpreter.scala @@ -4,7 +4,6 @@ import java.io.{PrintWriter, StringWriter} import sjsonnet.Expr.Params -import scala.collection.mutable import scala.util.control.NonFatal /** @@ -12,7 +11,7 @@ import scala.util.control.NonFatal * evaluation to materialization, into a convenient wrapper class. */ class Interpreter(extVars: Map[String, String], - tlaVars: Map[String, ujson.Value], + tlaVars: Map[String, String], wd: Path, importer: Importer, val parseCache: ParseCache, @@ -33,12 +32,15 @@ class Interpreter(extVars: Map[String, String], settings: Settings, warn: Error => Unit): Evaluator = new Evaluator(resolver, extVars, wd, settings, warn) + + def parseVar(k: String, v: String) = { + resolver.parse(wd / s"<$k>", v)(evaluator).fold(throw _, _._1) + } + lazy val evaluator: Evaluator = createEvaluator( resolver, // parse extVars lazily, because they can refer to each other and be recursive - extVars - .mapValues{ v => resolver.parse(wd / s"", v)(evaluator).fold(throw _, _._1) } - .lift, + k => extVars.get(k).map(v => parseVar(s"ext-var $k", v)), wd, settings, warn @@ -73,7 +75,7 @@ class Interpreter(extVars: Map[String, String], } } - def evaluate[T](txt: String, path: Path): Either[Error, Val] = { + def evaluate(txt: String, path: Path): Either[Error, Val] = { resolver.cache(path) = txt for{ res <- resolver.parse(path, txt)(evaluator) @@ -84,15 +86,18 @@ class Interpreter(extVars: Map[String, String], val defaults2 = f.params.defaultExprs.clone() var i = 0 while(i < defaults2.length) { - tlaVars.get(f.params.names(i)) match { - case Some(v) => defaults2(i) = Materializer.toExpr(v)(evaluator) + val k = f.params.names(i) + tlaVars.get(k) match { + case Some(v) => defaults2(i) = parseVar(s"tla-var $k", v) case None => } i += 1 } new Val.Func(f.pos, f.defSiteValScope, Params(f.params.names, defaults2)) { def evalRhs(vs: ValScope, es: EvalScope, fs: FileScope, pos: Position) = f.evalRhs(vs, es, fs, pos) - override def evalDefault(expr: Expr, vs: ValScope, es: EvalScope) = f.evalDefault(expr, vs, es) + override def evalDefault(expr: Expr, vs: ValScope, es: EvalScope) = { + evaluator.visitExpr(expr)(ValScope.empty) + } } case x => x } diff --git a/sjsonnet/test/src-jvm-native/sjsonnet/FileTests.scala b/sjsonnet/test/src-jvm-native/sjsonnet/FileTests.scala index df4bb487..6d5ca2a8 100644 --- a/sjsonnet/test/src-jvm-native/sjsonnet/FileTests.scala +++ b/sjsonnet/test/src-jvm-native/sjsonnet/FileTests.scala @@ -7,7 +7,7 @@ object FileTests extends TestSuite{ def eval(p: os.Path) = { val interp = new Interpreter( Map("var1" -> "\"test\"", "var2" -> """local f(a, b) = {[a]: b, "y": 2}; f("x", 1)"""), - Map("var1" -> "test", "var2" -> ujson.Obj("x" -> 1, "y" -> 2)), + Map("var1" -> "\"test\"", "var2" -> """{"x": 1, "y": 2}"""), OsPath(testSuiteRoot), importer = sjsonnet.SjsonnetMain.resolveImport(Array(OsPath(testSuiteRoot))), parseCache = new DefaultParseCache diff --git a/sjsonnet/test/src/sjsonnet/Std0150FunctionsTests.scala b/sjsonnet/test/src/sjsonnet/Std0150FunctionsTests.scala index 61fcae15..ceb95394 100644 --- a/sjsonnet/test/src/sjsonnet/Std0150FunctionsTests.scala +++ b/sjsonnet/test/src/sjsonnet/Std0150FunctionsTests.scala @@ -103,6 +103,36 @@ object Std0150FunctionsTests extends TestSuite { check("""std.extVar("stdExtVarRecursive")""", 115) } + + test("tlaVars"){ + val interpreter = new Interpreter( + Map(), + Map( + "num" -> "1", + "str" -> "\"hello\"", + "bool" -> "true", + "jsonArrNums" -> """[1, 2, 3]""", + "jsonObjBools" -> """{"hello": false}""", + "code" -> """local f(a, b) = {[a]: b, "y": 2}; f("x", 1)""", + "std" -> """std.length("hello")""", + ), + DummyPath(), + Importer.empty, + parseCache = new DefaultParseCache, + ) + + def check(s: String, expected: ujson.Value) = + interpreter.interpret(s, DummyPath("(memory)")) ==> Right(expected) + + check("""function(num) num""", 1) + check("""function(str) str""", "hello") + check("""function(bool) bool""", ujson.True) + check("""function(jsonArrNums) jsonArrNums""", ujson.Arr(1, 2, 3)) + check("""function(jsonObjBools) jsonObjBools""", ujson.Obj("hello" -> false)) + check("""function(code) code""", ujson.Obj("x" -> 1, "y" -> 2)) + check("""function(std) std""", 5) + } + test("fold"){ eval("""std.foldr(function (acc, it) acc + " " + it, "jsonnet", "this is")""") ==> ujson.Str("t e n n o s j this is")