diff --git a/build.mill b/build.mill index d73baea4..b3fac242 100644 --- a/build.mill +++ b/build.mill @@ -78,8 +78,9 @@ trait SjsonnetCrossModule extends CrossScalaModule with ScalafmtModule { "-feature", "-opt-inline-from:sjsonnet.*,sjsonnet.**", "-Xsource:3", - "-Xlint:_", - ) ++ (if (scalaVersion().startsWith("2.13")) Seq("-Wopt", "-Wconf:origin=scala.collection.compat.*:s") + "-Xlint:_" + ) ++ (if (scalaVersion().startsWith("2.13")) + Seq("-Wopt", "-Wconf:origin=scala.collection.compat.*:s") else Seq("-Xfatal-warnings", "-Ywarn-unused:-nowarn")) else Seq[String]("-Wconf:origin=scala.collection.compat.*:s", "-Xlint:all") ) diff --git a/sjsonnet/src/sjsonnet/Settings.scala b/sjsonnet/src/sjsonnet/Settings.scala index afc086a0..9afd92b7 100644 --- a/sjsonnet/src/sjsonnet/Settings.scala +++ b/sjsonnet/src/sjsonnet/Settings.scala @@ -13,7 +13,14 @@ final case class Settings( brokenAssertionLogic: Boolean = false, maxMaterializeDepth: Int = 1000, materializeRecursiveDepthLimit: Int = 128, - maxStack: Int = 500 + maxStack: Int = 500, + /** + * Enable aggressive static optimizations in the optimization phase, including: constant folding + * for arithmetic, comparison, bitwise, and shift operators; branch elimination for if-else with + * constant conditions; short-circuit elimination for And/Or with constant lhs. These reduce AST + * node count, benefiting long-running Jsonnet programs. + */ + aggressiveStaticOptimization: Boolean = false ) object Settings { diff --git a/sjsonnet/src/sjsonnet/StaticOptimizer.scala b/sjsonnet/src/sjsonnet/StaticOptimizer.scala index 25de8a47..e6efd570 100644 --- a/sjsonnet/src/sjsonnet/StaticOptimizer.scala +++ b/sjsonnet/src/sjsonnet/StaticOptimizer.scala @@ -9,6 +9,13 @@ import ScopedExprTransform.* * StaticOptimizer performs necessary transformations for the evaluator (assigning ValScope indices) * plus additional optimizations (post-order) and static checking (pre-order). * + * When `aggressiveStaticOptimization` is enabled, the optimizer additionally performs during the + * optimization phase: + * - Constant folding for arithmetic (+, -, *, /, %), comparison (<, >, <=, >=, ==, !=), bitwise + * (&, ^, |), shift (<<, >>), and unary (!, -, ~, +) operators. + * - Branch elimination for if-else with constant conditions. + * - Short-circuit elimination for And/Or with constant lhs operands. + * * @param variableResolver * a function that resolves variable names to expressions, only called if the variable is not * found in the scope. @@ -25,6 +32,8 @@ class StaticOptimizer( extends ScopedExprTransform { def optimize(e: Expr): Expr = transform(e) + private val aggressiveOptimization = ev.settings.aggressiveStaticOptimization + override def transform(_e: Expr): Expr = super.transform(check(_e)) match { case a: Apply => transformApply(a) @@ -96,7 +105,42 @@ class StaticOptimizer( Val.staticObject(pos, fields, internedStaticFieldSets, internedStrings) else m - case e => e + // Aggressive optimizations: constant folding, branch elimination, short-circuit elimination. + // These reduce AST node count at parse time, benefiting long-running Jsonnet programs. + case e => if (aggressiveOptimization) tryAggressiveOptimize(e) else e + } + + /** + * Aggressive static optimizations that benefit long-running programs by reducing AST size. + * Includes: branch elimination, short-circuit elimination, constant folding for arithmetic, + * comparison, bitwise, and shift operators. + */ + private def tryAggressiveOptimize(e: Expr): Expr = e match { + // Constant folding: BinaryOp with two constant operands (most common case first) + case e @ BinaryOp(pos, lhs: Val, op, rhs: Val) => tryFoldBinaryOp(pos, lhs, op, rhs, e) + + // Constant folding: UnaryOp with constant operand + case e @ UnaryOp(pos, op, v: Val) => tryFoldUnaryOp(pos, op, v, e) + + // Branch elimination: constant condition in if-else + case IfElse(_, _: Val.True, thenExpr, _) => thenExpr + case IfElse(pos, _: Val.False, _, elseExpr) => + if (elseExpr == null) Val.Null(pos) else elseExpr + + // Short-circuit elimination for And/Or with constant lhs. + // + // IMPORTANT: rhs MUST be guarded as `Val.Bool` — do NOT relax this to arbitrary Expr. + // The Evaluator's visitAnd/visitOr enforces that rhs evaluates to Bool, throwing + // "binary operator && does not operate on s" otherwise. If we fold `true && rhs` + // into just `rhs` without the Bool guard, we silently remove that runtime type check, + // causing programs like `true && "hello"` to return "hello" instead of erroring. + // See: Evaluator.visitAnd / Evaluator.visitOr for the authoritative runtime semantics. + case And(_, _: Val.True, rhs: Val.Bool) => rhs + case And(pos, _: Val.False, _) => Val.False(pos) + case Or(pos, _: Val.True, _) => Val.True(pos) + case Or(_, _: Val.False, rhs: Val.Bool) => rhs + + case _ => e } private object ValidSuper { @@ -258,4 +302,181 @@ class StaticOptimizer( } target } + + private def tryFoldUnaryOp(pos: Position, op: Int, v: Val, fallback: Expr): Expr = + try { + op match { + case Expr.UnaryOp.OP_! => + v match { + case _: Val.True => Val.False(pos) + case _: Val.False => Val.True(pos) + case _ => fallback + } + case Expr.UnaryOp.OP_- => + v match { + case Val.Num(_, n) => Val.Num(pos, -n) + case _ => fallback + } + case Expr.UnaryOp.OP_~ => + v match { + case Val.Num(_, n) => Val.Num(pos, (~n.toLong).toDouble) + case _ => fallback + } + case Expr.UnaryOp.OP_+ => + v match { + case Val.Num(_, n) => Val.Num(pos, n) + case _ => fallback + } + case _ => fallback + } + } catch { case _: Exception => fallback } + + private def tryFoldBinaryOp(pos: Position, lhs: Val, op: Int, rhs: Val, fallback: Expr): Expr = + try { + op match { + case BinaryOp.OP_+ => + (lhs, rhs) match { + case (Val.Num(_, l), Val.Num(_, r)) => Val.Num(pos, l + r) + case (Val.Str(_, l), Val.Str(_, r)) => Val.Str(pos, l + r) + case (l: Val.Arr, r: Val.Arr) => l.concat(pos, r) + case _ => fallback + } + case BinaryOp.OP_- => + (lhs, rhs) match { + case (Val.Num(_, l), Val.Num(_, r)) => Val.Num(pos, l - r) + case _ => fallback + } + case BinaryOp.OP_* => + (lhs, rhs) match { + case (Val.Num(_, l), Val.Num(_, r)) => Val.Num(pos, l * r) + case _ => fallback + } + case BinaryOp.OP_/ => + (lhs, rhs) match { + case (Val.Num(_, l), Val.Num(_, r)) if r != 0 => Val.Num(pos, l / r) + case _ => fallback + } + case BinaryOp.OP_% => + (lhs, rhs) match { + case (Val.Num(_, l), Val.Num(_, r)) => Val.Num(pos, l % r) + case _ => fallback + } + case BinaryOp.OP_< => + tryFoldComparison(pos, lhs, BinaryOp.OP_<, rhs, fallback) + case BinaryOp.OP_> => + tryFoldComparison(pos, lhs, BinaryOp.OP_>, rhs, fallback) + case BinaryOp.OP_<= => + tryFoldComparison(pos, lhs, BinaryOp.OP_<=, rhs, fallback) + case BinaryOp.OP_>= => + tryFoldComparison(pos, lhs, BinaryOp.OP_>=, rhs, fallback) + case BinaryOp.OP_== => + tryFoldEquality(pos, lhs, rhs, negate = false, fallback) + case BinaryOp.OP_!= => + tryFoldEquality(pos, lhs, rhs, negate = true, fallback) + case BinaryOp.OP_in => + (lhs, rhs) match { + case (Val.Str(_, l), o: Val.Obj) => Val.bool(pos, o.containsKey(l)) + case _ => fallback + } + case BinaryOp.OP_<< => + (lhs, rhs) match { + case (Val.Num(_, l), Val.Num(_, r)) => + val ll = lhs.asInstanceOf[Val.Num].asSafeLong + val rr = rhs.asInstanceOf[Val.Num].asSafeLong + if (rr < 0) fallback // negative shift → runtime error + else if (rr >= 1 && math.abs(ll) >= (1L << (63 - rr))) + fallback // overflow → runtime error + else Val.Num(pos, (ll << rr).toDouble) + case _ => fallback + } + case BinaryOp.OP_>> => + (lhs, rhs) match { + case (Val.Num(_, l), Val.Num(_, r)) => + val ll = lhs.asInstanceOf[Val.Num].asSafeLong + val rr = rhs.asInstanceOf[Val.Num].asSafeLong + if (rr < 0) fallback // negative shift → runtime error + else Val.Num(pos, (ll >> rr).toDouble) + case _ => fallback + } + case BinaryOp.OP_& => + (lhs, rhs) match { + case (Val.Num(_, _), Val.Num(_, _)) => + Val.Num( + pos, + (lhs.asInstanceOf[Val.Num].asSafeLong & rhs + .asInstanceOf[Val.Num] + .asSafeLong).toDouble + ) + case _ => fallback + } + case BinaryOp.OP_^ => + (lhs, rhs) match { + case (Val.Num(_, _), Val.Num(_, _)) => + Val.Num(pos, (lhs.asLong ^ rhs.asLong).toDouble) + case _ => fallback + } + case BinaryOp.OP_| => + (lhs, rhs) match { + case (Val.Num(_, _), Val.Num(_, _)) => + Val.Num(pos, (lhs.asLong | rhs.asLong).toDouble) + case _ => fallback + } + case _ => fallback + } + } catch { case _: Exception => fallback } + + private def tryFoldComparison( + pos: Position, + lhs: Val, + op: Int, + rhs: Val, + fallback: Expr): Expr = { + // Use IEEE 754 operators directly for Num, not java.lang.Double.compare, + // because compare(-0.0, 0.0) == -1 while IEEE 754 treats -0.0 == 0.0. + (lhs, rhs) match { + case (Val.Num(_, l), Val.Num(_, r)) if !l.isNaN && !r.isNaN => + val result = op match { + case BinaryOp.OP_< => l < r + case BinaryOp.OP_> => l > r + case BinaryOp.OP_<= => l <= r + case BinaryOp.OP_>= => l >= r + case _ => return fallback + } + Val.bool(pos, result) + case (Val.Str(_, l), Val.Str(_, r)) => + val cmp = Util.compareStringsByCodepoint(l, r) + val result = op match { + case BinaryOp.OP_< => cmp < 0 + case BinaryOp.OP_> => cmp > 0 + case BinaryOp.OP_<= => cmp <= 0 + case BinaryOp.OP_>= => cmp >= 0 + case _ => return fallback + } + Val.bool(pos, result) + case _ => fallback + } + } + + private def tryFoldEquality( + pos: Position, + lhs: Val, + rhs: Val, + negate: Boolean, + fallback: Expr): Expr = { + def isSimpleLiteral(v: Val): Boolean = v match { + case _: Val.Bool | _: Val.Null | _: Val.Str | _: Val.Num => true + case _ => false + } + if (!isSimpleLiteral(lhs) || !isSimpleLiteral(rhs)) return fallback + val result = (lhs, rhs) match { + case (_: Val.True, _: Val.True) | (_: Val.False, _: Val.False) | (_: Val.Null, _: Val.Null) => + true + case (Val.Num(_, l), Val.Num(_, r)) if !l.isNaN && !r.isNaN => + l == r + case (Val.Str(_, l), Val.Str(_, r)) => + l == r + case _ => false // different simple types are never equal + } + Val.bool(pos, if (negate) !result else result) + } } diff --git a/sjsonnet/test/src/sjsonnet/AggressiveStaticOptimizationTests.scala b/sjsonnet/test/src/sjsonnet/AggressiveStaticOptimizationTests.scala new file mode 100644 index 00000000..37b72e92 --- /dev/null +++ b/sjsonnet/test/src/sjsonnet/AggressiveStaticOptimizationTests.scala @@ -0,0 +1,314 @@ +package sjsonnet + +import utest._ +import TestUtils.{eval, evalErr} + +/** + * Tests for `aggressiveStaticOptimization = true`. + * + * Covers every optimization branch in [[StaticOptimizer.tryAggressiveOptimize]], which runs during + * the optimization phase (not parse time): + * - Constant folding: arithmetic (+, -, *, /, %), comparison (<, >, <=, >=, ==, !=), bitwise (&, + * ^, |), shift (<<, >>), unary (!, -, ~, +), string/array concatenation. + * - Branch elimination: if-else with constant condition. + * - Short-circuit elimination: And/Or with constant lhs. + * + * Each case is verified to produce the same result as without the optimization, confirming that the + * optimization is semantics-preserving. + */ +object AggressiveStaticOptimizationTests extends TestSuite { + + /** Shorthand: evaluate with aggressiveStaticOptimization enabled. */ + def evalOpt(s: String): ujson.Value = + eval(s, aggressiveStaticOptimization = true) + + /** Shorthand: evaluate and expect an error with aggressiveStaticOptimization enabled. */ + def evalErrOpt(s: String): String = + evalErr(s, aggressiveStaticOptimization = true) + + def tests: Tests = Tests { + + // ------------------------------------------------------------------------- + // Arithmetic constant folding + // ------------------------------------------------------------------------- + test("constantFolding") { + test("addNumbers") { + evalOpt("1 + 2") ==> ujson.Num(3) + evalOpt("1.5 + 2.5") ==> ujson.Num(4) + } + test("subtractNumbers") { + evalOpt("10 - 3") ==> ujson.Num(7) + evalOpt("0 - 5") ==> ujson.Num(-5) + } + test("multiplyNumbers") { + evalOpt("3 * 4") ==> ujson.Num(12) + evalOpt("2.5 * 2") ==> ujson.Num(5) + } + test("divideNumbers") { + evalOpt("10 / 4") ==> ujson.Num(2.5) + evalOpt("9 / 3") ==> ujson.Num(3) + } + test("moduloNumbers") { + evalOpt("10 % 3") ==> ujson.Num(1) + evalOpt("7 % 7") ==> ujson.Num(0) + } + test("addStrings") { + evalOpt(""" "hello" + " world" """) ==> ujson.Str("hello world") + evalOpt(""" "foo" + "bar" """) ==> ujson.Str("foobar") + } + test("addArrays") { + evalOpt("[1, 2] + [3, 4]") ==> ujson.Arr(1, 2, 3, 4) + evalOpt("[] + [1]") ==> ujson.Arr(1) + } + } + + // ------------------------------------------------------------------------- + // Unary operator constant folding + // ------------------------------------------------------------------------- + test("unaryConstantFolding") { + test("logicalNot") { + evalOpt("!true") ==> ujson.False + evalOpt("!false") ==> ujson.True + } + test("negateNumber") { + evalOpt("-5") ==> ujson.Num(-5) + evalOpt("-(-3)") ==> ujson.Num(3) + } + test("bitwiseNot") { + evalOpt("~0") ==> ujson.Num(-1) + evalOpt("~(-1)") ==> ujson.Num(0) + } + test("unaryPlus") { + evalOpt("+7") ==> ujson.Num(7) + } + } + + // ------------------------------------------------------------------------- + // Comparison constant folding + // ------------------------------------------------------------------------- + test("comparisonConstantFolding") { + test("lessThan") { + evalOpt("1 < 2") ==> ujson.True + evalOpt("2 < 1") ==> ujson.False + evalOpt("1 < 1") ==> ujson.False + } + test("greaterThan") { + evalOpt("2 > 1") ==> ujson.True + evalOpt("1 > 2") ==> ujson.False + evalOpt("1 > 1") ==> ujson.False + } + test("lessThanOrEqual") { + evalOpt("1 <= 1") ==> ujson.True + evalOpt("1 <= 2") ==> ujson.True + evalOpt("2 <= 1") ==> ujson.False + } + test("greaterThanOrEqual") { + evalOpt("1 >= 1") ==> ujson.True + evalOpt("2 >= 1") ==> ujson.True + evalOpt("1 >= 2") ==> ujson.False + } + test("equalNumbers") { + evalOpt("1 == 1") ==> ujson.True + evalOpt("1 == 2") ==> ujson.False + } + test("notEqualNumbers") { + evalOpt("1 != 2") ==> ujson.True + evalOpt("1 != 1") ==> ujson.False + } + test("equalStrings") { + evalOpt(""" "abc" == "abc" """) ==> ujson.True + evalOpt(""" "abc" == "def" """) ==> ujson.False + } + test("notEqualStrings") { + evalOpt(""" "abc" != "def" """) ==> ujson.True + evalOpt(""" "abc" != "abc" """) ==> ujson.False + } + test("equalBooleans") { + evalOpt("true == true") ==> ujson.True + evalOpt("false == false") ==> ujson.True + evalOpt("true == false") ==> ujson.False + } + test("equalNull") { + evalOpt("null == null") ==> ujson.True + evalOpt("null != null") ==> ujson.False + } + test("stringComparison") { + evalOpt(""" "abc" < "abd" """) ==> ujson.True + evalOpt(""" "b" > "a" """) ==> ujson.True + evalOpt(""" "abc" <= "abc" """) ==> ujson.True + evalOpt(""" "abc" >= "abc" """) ==> ujson.True + } + } + + // ------------------------------------------------------------------------- + // Bitwise and shift constant folding + // ------------------------------------------------------------------------- + test("bitwiseConstantFolding") { + test("bitwiseAnd") { + evalOpt("12 & 10") ==> ujson.Num(8) + evalOpt("0 & 255") ==> ujson.Num(0) + } + test("bitwiseXor") { + evalOpt("12 ^ 10") ==> ujson.Num(6) + evalOpt("0 ^ 0") ==> ujson.Num(0) + } + test("bitwiseOr") { + evalOpt("12 | 10") ==> ujson.Num(14) + evalOpt("0 | 0") ==> ujson.Num(0) + } + test("shiftLeft") { + evalOpt("1 << 3") ==> ujson.Num(8) + evalOpt("3 << 2") ==> ujson.Num(12) + } + test("shiftRight") { + evalOpt("8 >> 2") ==> ujson.Num(2) + evalOpt("16 >> 4") ==> ujson.Num(1) + } + } + + // ------------------------------------------------------------------------- + // Branch elimination: if-else with constant condition + // ------------------------------------------------------------------------- + test("branchElimination") { + test("trueConditionSelectsThenBranch") { + evalOpt("if true then 42 else 0") ==> ujson.Num(42) + evalOpt("if true then 'yes' else 'no'") ==> ujson.Str("yes") + } + test("falseConditionSelectsElseBranch") { + evalOpt("if false then 42 else 0") ==> ujson.Num(0) + evalOpt("if false then 'yes' else 'no'") ==> ujson.Str("no") + } + test("falseConditionWithNoElseYieldsNull") { + // `if false then expr` with no else branch should yield null + evalOpt("if false then 42") ==> ujson.Null + } + test("nestedBranchElimination") { + evalOpt("if true then (if false then 1 else 2) else 3") ==> ujson.Num(2) + } + } + + // ------------------------------------------------------------------------- + // Short-circuit elimination for And / Or + // ------------------------------------------------------------------------- + test("shortCircuitElimination") { + test("trueAndTrue") { + evalOpt("true && true") ==> ujson.True + } + test("trueAndFalse") { + evalOpt("true && false") ==> ujson.False + } + test("falseAndAnything") { + // false && rhs should short-circuit to false regardless of rhs + evalOpt("false && true") ==> ujson.False + evalOpt("false && false") ==> ujson.False + } + test("trueOrAnything") { + // true || rhs should short-circuit to true regardless of rhs + evalOpt("true || false") ==> ujson.True + evalOpt("true || true") ==> ujson.True + } + test("falseOrTrue") { + evalOpt("false || true") ==> ujson.True + } + test("falseOrFalse") { + evalOpt("false || false") ==> ujson.False + } + } + + // ------------------------------------------------------------------------- + // Semantics-preserving: results must match the non-optimized evaluator + // ------------------------------------------------------------------------- + test("semanticsMatch") { + val expressions = Seq( + "1 + 2", + "10 - 3", + "3 * 4", + "10 / 4", + "10 % 3", + "!true", + "!false", + "-5", + "~0", + "+7", + "1 < 2", + "2 > 1", + "1 <= 1", + "2 >= 1", + "1 == 1", + "1 != 2", + "12 & 10", + "12 ^ 10", + "12 | 10", + "1 << 3", + "8 >> 2", + "if true then 1 else 0", + "if false then 1 else 0", + "if false then 1", + "true && true", + "true && false", + "false && true", + "true || false", + "false || true", + "false || false" + ) + for (expr <- expressions) { + val withOpt = eval(expr, aggressiveStaticOptimization = true) + val withoutOpt = eval(expr, aggressiveStaticOptimization = false) + withOpt ==> withoutOpt + } + } + + // ------------------------------------------------------------------------- + // Edge cases: optimizations that must NOT fire (non-constant operands) + // ------------------------------------------------------------------------- + test("nonConstantOperandsNotFolded") { + // Variables are not statically known values; the optimizer must not fold these. + evalOpt("local x = 3; local y = 4; x + y") ==> ujson.Num(7) + evalOpt("local b = true; if b then 1 else 0") ==> ujson.Num(1) + evalOpt("local b = false; b || true") ==> ujson.True + } + + // ------------------------------------------------------------------------- + // Error cases: runtime errors must still be raised correctly + // ------------------------------------------------------------------------- + test("runtimeErrorsPreserved") { + test("divisionByZeroNotFolded") { + // Division by zero: the optimizer must NOT fold `1 / 0` into a value; + // it should fall back to the runtime error path. + val err = evalErrOpt("1 / 0") + assert(err.contains("sjsonnet.Error")) + } + test("negativeShiftNotFolded") { + // Negative shift amounts must not be constant-folded; runtime error expected. + val err = evalErrOpt("1 << -1") + assert(err.contains("sjsonnet.Error")) + } + test("andWithNonBoolRhsStillErrors") { + // `true && "hello"` must still error: the optimizer only short-circuits when + // rhs is a Val.Bool. If rhs is not a Bool, the BinaryOp is left intact and + // the runtime type-check fires. + val err = evalErrOpt(""" true && "hello" """) + assert(err.contains("binary operator &&")) + } + test("orWithNonBoolRhsStillErrors") { + val err = evalErrOpt(""" false || "hello" """) + assert(err.contains("binary operator ||")) + } + } + + // ------------------------------------------------------------------------- + // Interaction with other settings: aggressiveStaticOptimization + useNewEvaluator + // ------------------------------------------------------------------------- + test("withNewEvaluator") { + def evalBoth(s: String): ujson.Value = + eval(s, aggressiveStaticOptimization = true, useNewEvaluator = true) + + evalBoth("1 + 2") ==> ujson.Num(3) + evalBoth("if true then 'yes' else 'no'") ==> ujson.Str("yes") + evalBoth("true && false") ==> ujson.False + evalBoth("false || true") ==> ujson.True + evalBoth("~0") ==> ujson.Num(-1) + evalBoth("12 & 10") ==> ujson.Num(8) + } + } +} diff --git a/sjsonnet/test/src/sjsonnet/EvaluatorTests.scala b/sjsonnet/test/src/sjsonnet/EvaluatorTests.scala index 8a58d070..87666fd8 100644 --- a/sjsonnet/test/src/sjsonnet/EvaluatorTests.scala +++ b/sjsonnet/test/src/sjsonnet/EvaluatorTests.scala @@ -4,160 +4,377 @@ import utest._ import TestUtils.{eval, evalErr} object EvaluatorTests extends TestSuite { - def allTests(useNewEvaluator: Boolean): Tests = Tests { + def allTests(useNewEvaluator: Boolean, aggressiveOpt: Boolean): Tests = Tests { test("arithmetic") { - eval("1 + 2 + 3", useNewEvaluator = useNewEvaluator) ==> ujson.Num(6) - eval("1 + 2 * 3", useNewEvaluator = useNewEvaluator) ==> ujson.Num(7) - eval("-1 + 2 * 3", useNewEvaluator = useNewEvaluator) ==> ujson.Num(5) - eval("6 - 3 + 2", useNewEvaluator = useNewEvaluator) ==> ujson.Num(5) + eval( + "1 + 2 + 3", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(6) + eval( + "1 + 2 * 3", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(7) + eval( + "-1 + 2 * 3", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(5) + eval( + "6 - 3 + 2", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(5) } test("objects") { - eval("{x: 1}.x", useNewEvaluator = useNewEvaluator) ==> ujson.Num(1) + eval( + "{x: 1}.x", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(1) } test("arrays") { - eval("[1, [2, 3], 4][1][0]", useNewEvaluator = useNewEvaluator) ==> ujson.Num(2) - eval("([1, 2, 3] + [4, 5, 6])[3]", useNewEvaluator = useNewEvaluator) ==> ujson.Num(4) - evalErr("[][0]", useNewEvaluator = useNewEvaluator) ==> + eval( + "[1, [2, 3], 4][1][0]", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(2) + eval( + "([1, 2, 3] + [4, 5, 6])[3]", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(4) + evalErr( + "[][0]", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> """sjsonnet.Error: array bounds error: array is empty |at [].(:1:3)""".stripMargin - eval("std.slice(std.range(1,4), 0, null, 2)", useNewEvaluator = useNewEvaluator) ==> ujson + eval( + "std.slice(std.range(1,4), 0, null, 2)", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson .Arr(1, 3) - eval("std.slice(std.range(1,4), null, null, 2)", useNewEvaluator = useNewEvaluator) ==> ujson + eval( + "std.slice(std.range(1,4), null, null, 2)", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson .Arr(1, 3) eval( "std.slice(std.range(1,4), null, null, null)", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Arr(1, 2, 3, 4) eval(""" |std.slice("jsonnet", -3, null, null) |""".stripMargin) ==> ujson.Str("net") - eval("std.range(1,4)[0::2]", useNewEvaluator = useNewEvaluator) ==> ujson.Arr(1, 3) - eval("std.range(1,4)[0:null:2]", useNewEvaluator = useNewEvaluator) ==> ujson.Arr(1, 3) - eval("std.range(1,4)[null:null:2]", useNewEvaluator = useNewEvaluator) ==> ujson.Arr(1, 3) - eval("std.range(1,4)[null:null:null]", useNewEvaluator = useNewEvaluator) ==> ujson.Arr( + eval( + "std.range(1,4)[0::2]", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Arr(1, 3) + eval( + "std.range(1,4)[0:null:2]", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Arr(1, 3) + eval( + "std.range(1,4)[null:null:2]", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Arr(1, 3) + eval( + "std.range(1,4)[null:null:null]", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Arr( 1, 2, 3, 4 ) - eval("std.range(1,4)[::2]", useNewEvaluator = useNewEvaluator) ==> ujson.Arr(1, 3) + eval( + "std.range(1,4)[::2]", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Arr(1, 3) } test("functions") { - eval("(function(x) x)(1)", useNewEvaluator = useNewEvaluator) ==> ujson.Num(1) - eval("function() 1", useNewEvaluator = useNewEvaluator) ==> ujson.Num(1) - eval("function(a=1) a", useNewEvaluator = useNewEvaluator) ==> ujson.Num(1) - eval("(function(x, y = x + 1) y)(x = 10)", useNewEvaluator = useNewEvaluator) ==> ujson.Num( + eval( + "(function(x) x)(1)", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(1) + eval( + "function() 1", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(1) + eval( + "function(a=1) a", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(1) + eval( + "(function(x, y = x + 1) y)(x = 10)", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num( 11 ) - eval("local f(x) = function() true; f(42)", useNewEvaluator = useNewEvaluator) ==> ujson.True + eval( + "local f(x) = function() true; f(42)", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.True eval( "local f(x) = function() true; f(42) == true", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.False eval( "local f(x) = function() true; f(42)() == true", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.True assert( - evalErr("{foo: function() true}", useNewEvaluator = useNewEvaluator).startsWith( + evalErr( + "{foo: function() true}", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ).startsWith( "sjsonnet.Error: Couldn't manifest function with params" ) ) - eval("{foo: (function() true)()}", useNewEvaluator = useNewEvaluator) ==> ujson.Obj { + eval( + "{foo: (function() true)()}", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Obj { "foo" -> ujson.True } } test("members") { - eval("{local x = 1, x: x}['x']", useNewEvaluator = useNewEvaluator) ==> ujson.Num(1) - eval("{local x(y) = y + '1', x: x('2')}['x']", useNewEvaluator = useNewEvaluator) ==> ujson + eval( + "{local x = 1, x: x}['x']", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(1) + eval( + "{local x(y) = y + '1', x: x('2')}['x']", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson .Str("21") - eval("{local x(y) = y + '1', x: x(y='2')}['x']", useNewEvaluator = useNewEvaluator) ==> ujson + eval( + "{local x(y) = y + '1', x: x(y='2')}['x']", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson .Str("21") - eval("{local x(y='2') = y + '1', x: x()}['x']", useNewEvaluator = useNewEvaluator) ==> ujson + eval( + "{local x(y='2') = y + '1', x: x()}['x']", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson .Str("21") eval( """{[{local x = $.y + "lol", y: x, z: "1"}.z]: 2}["1"]""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(2) - eval("{local x = 1, y: { z: x }}.y.z", useNewEvaluator = useNewEvaluator) ==> ujson.Num(1) + eval( + "{local x = 1, y: { z: x }}.y.z", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(1) } test("extends") { - eval("(function(a) a.x + a.y)({x: 1}{y: 2})", useNewEvaluator = useNewEvaluator) ==> ujson + eval( + "(function(a) a.x + a.y)({x: 1}{y: 2})", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson .Num(3) eval( "(function(a) a.x + a.y)({x: 1}{x: 2, y: 3})", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(5) - eval("({x: 1}{x+: 2}).x", useNewEvaluator = useNewEvaluator) ==> ujson.Num(3) - eval("({x+: 1}{x: 2}).x", useNewEvaluator = useNewEvaluator) ==> ujson.Num(2) - eval("({x+: 1}{x+: 2}).x", useNewEvaluator = useNewEvaluator) ==> ujson.Num(3) - eval("({x+: 1} + {x+: 2}).x", useNewEvaluator = useNewEvaluator) ==> ujson.Num(3) + eval( + "({x: 1}{x+: 2}).x", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(3) + eval( + "({x+: 1}{x: 2}).x", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(2) + eval( + "({x+: 1}{x+: 2}).x", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(3) + eval( + "({x+: 1} + {x+: 2}).x", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(3) eval( "(function(a, b) a + b)({x+: 1}, {x+: 2}).x", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(3) - eval("""({a: [1]} + {a+: "1"}).a""", useNewEvaluator = useNewEvaluator) ==> ujson.Str("[1]1") - eval("""({a: 1} + {a+: "1"}).a""", useNewEvaluator = useNewEvaluator) ==> ujson.Str("11") - eval("""({a: "1"} + {a+: 1}).a""", useNewEvaluator = useNewEvaluator) ==> ujson.Str("11") + eval( + """({a: [1]} + {a+: "1"}).a""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str("[1]1") + eval( + """({a: 1} + {a+: "1"}).a""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str("11") + eval( + """({a: "1"} + {a+: 1}).a""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str("11") } test("ifElse") { - eval("if true then 1 else 0", useNewEvaluator = useNewEvaluator) ==> ujson.Num(1) - eval("if 2 > 1 then 1 else 0", useNewEvaluator = useNewEvaluator) ==> ujson.Num(1) - eval("if false then 1 else 0", useNewEvaluator = useNewEvaluator) ==> ujson.Num(0) - eval("if 1 > 2 then 1 else 0", useNewEvaluator = useNewEvaluator) ==> ujson.Num(0) + eval( + "if true then 1 else 0", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(1) + eval( + "if 2 > 1 then 1 else 0", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(1) + eval( + "if false then 1 else 0", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(0) + eval( + "if 1 > 2 then 1 else 0", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(0) } test("self") { - eval("{x: 1, y: $.x + 10}.y", useNewEvaluator = useNewEvaluator) ==> ujson.Num(11) - eval("{x: 1, y: self.x}.y", useNewEvaluator = useNewEvaluator) ==> ujson.Num(1) - eval("{x: 1, y: {x: 2, z: $.x + 10}}.y.z", useNewEvaluator = useNewEvaluator) ==> ujson.Num( + eval( + "{x: 1, y: $.x + 10}.y", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(11) + eval( + "{x: 1, y: self.x}.y", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(1) + eval( + "{x: 1, y: {x: 2, z: $.x + 10}}.y.z", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num( 11 ) - eval("{x: 1, y: {x: 2, z: self.x + 10}}.y.z", useNewEvaluator = useNewEvaluator) ==> ujson + eval( + "{x: 1, y: {x: 2, z: self.x + 10}}.y.z", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson .Num(12) - eval("{x: 1, y: {x: 0, y: self.x}.y}.y", useNewEvaluator = useNewEvaluator) ==> ujson.Num(0) - eval("""{x: 1, y: "x" in self}.y""", useNewEvaluator = useNewEvaluator) ==> ujson.True - eval("""{x: 1, y: "z" in self}.y""", useNewEvaluator = useNewEvaluator) ==> ujson.False - eval("""{y: "y" in self}.y""", useNewEvaluator = useNewEvaluator) ==> ujson.True + eval( + "{x: 1, y: {x: 0, y: self.x}.y}.y", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(0) + eval( + """{x: 1, y: "x" in self}.y""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.True + eval( + """{x: 1, y: "z" in self}.y""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.False + eval( + """{y: "y" in self}.y""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.True } test("topLevel") { - eval("local p(n='A') = {w: 'H'+n}; {p: p()}.p.w", useNewEvaluator = useNewEvaluator) ==> ujson + eval( + "local p(n='A') = {w: 'H'+n}; {p: p()}.p.w", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson .Str("HA") } test("lazy") { - eval("[{x: $.y, y: $.x}.x, 2][1]", useNewEvaluator = useNewEvaluator) ==> ujson.Num(2) + eval( + "[{x: $.y, y: $.x}.x, 2][1]", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(2) eval( "{x: $.y, y: $.x, local z(z0) = [3], w: z($.x)}.w[0]", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(3) eval( "(function(a=[1, b[1]], b=[a[0], 2]) [a, b])()[0][1]", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(2) } test("comprehensions") { - eval("[x + 1 for x in [1, 2, 3]][2]", useNewEvaluator = useNewEvaluator) ==> ujson.Num(4) - eval("[x + 1, for x in [1, 2, 3]][2]", useNewEvaluator = useNewEvaluator) ==> ujson.Num(4) + eval( + "[x + 1 for x in [1, 2, 3]][2]", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(4) + eval( + "[x + 1, for x in [1, 2, 3]][2]", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(4) eval( "[x + y for x in [1, 2, 3] for y in [4, 5, 6] if x + y != 7][3]", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(8) eval( """{[""+x]: x * x for x in [1, 2, 3]}["3"]""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(9) eval( """{local y = $["2"], [x]: if x == "1" then y else 0, for x in ["1", "2"]}["1"]""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(0) // References between locals in an object comprehension: eval( """{local a = 1, local b = a + 1, [k]: b + 1 for k in ["x"]}""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Obj("x" -> ujson.Num(3)) // Locals which reference variables from the comprehension: eval( """{local x2 = k*2, [std.toString(k)]: x2 for k in [1]}""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Obj("1" -> ujson.Num(2)) // Regression test for https://github.com/databricks/sjsonnet/issues/357 // self references in object comprehension locals are properly rebound during inheritance: @@ -180,7 +397,8 @@ object EvaluatorTests extends TestSuite { |} |+ lib.foo() |""".stripMargin, - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Obj("foo" -> ujson.Obj("foo" -> "foo")) // Regression test for a related bug involving local references to `super`: eval( @@ -196,7 +414,8 @@ object EvaluatorTests extends TestSuite { |{ x: 2 } |+ lib.foo() |""".stripMargin, - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Obj("x" -> ujson.Num(3)) // Yet another related bug involving super references _not_ in locals: eval( @@ -211,10 +430,15 @@ object EvaluatorTests extends TestSuite { |{ x: 2 } |+ lib.foo() |""".stripMargin, - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Obj("x" -> ujson.Num(3)) // Regression test for a bug in handling of non-string field names: - evalErr("{[k]: k for k in [1]}", useNewEvaluator = useNewEvaluator) ==> + evalErr( + "{[k]: k for k in [1]}", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> """sjsonnet.Error: [object comprehension] Field name must be string or null, not number |at [].(:1:1)""".stripMargin // Basic function support: @@ -226,7 +450,8 @@ object EvaluatorTests extends TestSuite { |}; |funcs.f1(10) |""".stripMargin, - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(20) // Functions which use locals from the comprehension: eval( @@ -238,181 +463,277 @@ object EvaluatorTests extends TestSuite { |}; |funcs.f1(10) |""".stripMargin, - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(20) } test("super") { test("implicit") { - eval("({ x: 1, y: self.x } + { x: 2 }).y", useNewEvaluator = useNewEvaluator) ==> ujson.Num( + eval( + "({ x: 1, y: self.x } + { x: 2 }).y", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num( 2 ) eval( "({ local x = $.y, y: 1, z: x} + { y: 2 }).z", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(2) eval( "({ local x = self.y, y: 1, z: x} + { y: 2 }).z", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(2) eval( "local A = {x: 1, local outer = self, y: A{z: outer}}; A.y.z.x", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(1) eval( "{local x = self, y: 1, z: {a: x, y: 2}}.z.a.y", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(1) eval( "local A = {x: 1, local outer = self, y: A{x: outer.x}}; A.y.x", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(1) eval( "local A = {x: 1, local outer = self, y: A{x: outer.x + 1}}; A.y.y.x", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(3) - eval("""("a" in ({a: 1}{b: 2}))""", useNewEvaluator = useNewEvaluator) ==> ujson.True + eval( + """("a" in ({a: 1}{b: 2}))""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.True } test("explicit") { - eval("{ x: 1, y: self.x } + { x: 2, y: super.y + 1}", useNewEvaluator = useNewEvaluator) ==> + eval( + "{ x: 1, y: self.x } + { x: 2, y: super.y + 1}", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.read("""{ "x": 2, "y": 3 }""") eval( "{ x: 1 } + { x: 2, y: super.x } + { x: 3, z: super.x }", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.read("""{ "x": 3, "y": 1, "z": 2 }""") eval( """({a: 1} + {b: 2} + {c: ["a" in super, "b" in super]}).c""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Arr(true, true) - eval("""{a: ["a" in super, "b" in super]}.a""", useNewEvaluator = useNewEvaluator) ==> ujson + eval( + """{a: ["a" in super, "b" in super]}.a""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson .Arr(false, false) - eval("""(({a: 1}{b: 2}){c: super.a}).c""", useNewEvaluator = useNewEvaluator) ==> ujson.Num( + eval( + """(({a: 1}{b: 2}){c: super.a}).c""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num( 1 ) - eval("""(({a: 1}{b: 2}){c: super.b}).c""", useNewEvaluator = useNewEvaluator) ==> ujson.Num( + eval( + """(({a: 1}{b: 2}){c: super.b}).c""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num( 2 ) eval( """(({a: 1}{b: 2, f: super.a}){c: super.f}).c""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(1) val ex = assertThrows[Exception] { - eval("""(({a: 1}{b: 2, f: super.b}){c: super.f}).c""", useNewEvaluator = useNewEvaluator) + eval( + """(({a: 1}{b: 2, f: super.b}){c: super.f}).c""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) } assert(ex.getMessage.contains("Field does not exist: b")) eval( """(({a: 1}{b: 2}) + ({c: super.b}{d: super.a})).c""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(2) eval( """(({a: 1}{b: 2}) + ({c: super.b}{d: super.a})).d""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(1) eval( """local x = {a: 1}; local y = {b: super.a}; x + y""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.read("""{"a": 1, "b": 1}""") eval( """local x = { a: 1, b: { c: 2 }}; x { a: super.a * 10, b:: { c: super.b.c * 10 } }""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Obj("a" -> ujson.Num(10)) evalErr( """local x = { a: 1, b: { c: 2 }}; x { a: super.a * 10, b:: { c: super.b.c * 10 } }.b""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> """sjsonnet.Error: Attempt to use `super` when there is no super class |at [].(:1:7)""".stripMargin } } test("hidden") { - eval("{i::1 }", useNewEvaluator = useNewEvaluator) ==> + eval( + "{i::1 }", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.read("""{}""") - eval("{i:: 1} + {i: 2}", useNewEvaluator = useNewEvaluator) ==> + eval( + "{i:: 1} + {i: 2}", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.read("""{}""") - eval("{i: 1} + {i:: 2}", useNewEvaluator = useNewEvaluator) ==> + eval( + "{i: 1} + {i:: 2}", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.read("""{}""") - eval("{i:: 1} + {i:: 2}", useNewEvaluator = useNewEvaluator) ==> + eval( + "{i:: 1} + {i:: 2}", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.read("""{}""") - eval("{i:::1} + {i::2}", useNewEvaluator = useNewEvaluator) ==> + eval( + "{i:::1} + {i::2}", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.read("""{}""") - eval("{i::1} + {i:::2}", useNewEvaluator = useNewEvaluator) ==> + eval( + "{i::1} + {i:::2}", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.read("""{"i": 2}""") - eval("{i:1} + {i:::2}", useNewEvaluator = useNewEvaluator) ==> + eval( + "{i:1} + {i:::2}", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.read("""{"i": 2}""") - eval("local M = {x+: self.i, i :: 1}; { x: 1 } + M", useNewEvaluator = useNewEvaluator) ==> + eval( + "local M = {x+: self.i, i :: 1}; { x: 1 } + M", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.read("""{ "x": 2 }""") - eval("""("%(hello)s" % {hello::"world"})""", useNewEvaluator = useNewEvaluator) ==> ujson.Str( + eval( + """("%(hello)s" % {hello::"world"})""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str( "world" ) eval( """("%(hello)s" % {hello::"world", bad:: error "lol"})""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Str("world") } test("evaluator2") { eval( """{local x = 1, [x]: x, for x in ["foo"]}.foo""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(1) eval( """{[x]: x, local x = 1, for x in ["foo"]}.foo""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(1) eval( """local foo = ["foo"]; {local foo = 1, [x]: x, for x in foo}.foo""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Str("foo") eval( """local foo = ["foo"]; {[x]: x, local foo = 2, for x in foo}.foo""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Str("foo") eval( """{ [x + ""]: if x == 1 then 1 else x + $["1"] for x in [1, 2, 3] }""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.read("""{ "1": 1, "2": 3, "3": 4 }""") eval( """local x = "baz"; { local x = "bar", [x]: x for x in ["foo"] }""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.read("""{ "foo": "bar" }""") eval( """{ [x + ""]: x + foo, local foo = 3 for x in [1, 2, 3] }""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.read("""{ "1": 4, "2": 5, "3": 6 }""") eval( """{local y = x, ["foo"]: y, for x in ["foo"]}.foo""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Str("foo") } test("shadowing") { - eval("local x = 1; local x = 2; x", useNewEvaluator = useNewEvaluator) ==> ujson.Num(2) - eval("local x = 1; x + local x = 2; x", useNewEvaluator = useNewEvaluator) ==> ujson.Num(3) + eval( + "local x = 1; local x = 2; x", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(2) + eval( + "local x = 1; x + local x = 2; x", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(3) eval( """local str1 = ||| | text @@ -424,18 +745,40 @@ object EvaluatorTests extends TestSuite { | |(str1 == "\\n\n") |""".stripMargin, - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.True } test("stdLib") { - eval("std.pow(2, 3)", useNewEvaluator = useNewEvaluator) ==> ujson.Num(8) - eval("std.pow(x=2, n=3)", useNewEvaluator = useNewEvaluator) ==> ujson.Num(8) - eval("std.pow(n=3, x=2)", useNewEvaluator = useNewEvaluator) ==> ujson.Num(8) - eval("({a:: 1} + {a+:::2}).a", useNewEvaluator = useNewEvaluator) ==> ujson.Num(3) - eval("(std.prune({a:: 1}) + {a+:::2}).a", useNewEvaluator = useNewEvaluator) ==> ujson.Num(2) + eval( + "std.pow(2, 3)", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(8) + eval( + "std.pow(x=2, n=3)", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(8) + eval( + "std.pow(n=3, x=2)", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(8) + eval( + "({a:: 1} + {a+:::2}).a", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(3) + eval( + "(std.prune({a:: 1}) + {a+:::2}).a", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Num(2) eval( "std.toString(std.mapWithIndex(function(idx, elem) elem, [2,1,0]))", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Str("[2, 1, 0]") } test("unboundParam") { @@ -451,7 +794,8 @@ object EvaluatorTests extends TestSuite { |params.y | """.stripMargin, - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) } @@ -469,7 +813,8 @@ object EvaluatorTests extends TestSuite { | person2: Person('Bob', hello=123), |} """.stripMargin, - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) } @@ -477,16 +822,32 @@ object EvaluatorTests extends TestSuite { } test("unknownVariable") { - evalErr("x", useNewEvaluator = useNewEvaluator) ==> + evalErr( + "x", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> """sjsonnet.StaticError: Unknown variable: x |at [].(:1:1)""".stripMargin - evalErr("self.x", useNewEvaluator = useNewEvaluator) ==> + evalErr( + "self.x", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> """sjsonnet.StaticError: Can't use self outside of an object |at [].(:1:1)""".stripMargin - evalErr("$.x", useNewEvaluator = useNewEvaluator) ==> + evalErr( + "$.x", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> """sjsonnet.StaticError: Can't use $ outside of an object |at [].(:1:1)""".stripMargin - evalErr("super.x", useNewEvaluator = useNewEvaluator) ==> + evalErr( + "super.x", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> """sjsonnet.StaticError: Can't use super outside of an object |at [].(:1:1)""".stripMargin } @@ -501,7 +862,8 @@ object EvaluatorTests extends TestSuite { | person2: Person(name='Bob'), |}.person2.welcome """.stripMargin, - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) res ==> ujson.Str("Hello Bob!") @@ -510,12 +872,17 @@ object EvaluatorTests extends TestSuite { test("equalDollar") { eval( "local f(x) = x; {hello: 123, world: f(x=$.hello)}", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Obj("hello" -> 123, "world" -> 123) } test("stdSubstr") { - eval("std.substr('cookie', 6, 2)", useNewEvaluator = useNewEvaluator) ==> ujson.Str("") + eval( + "std.substr('cookie', 6, 2)", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str("") } test("manifestIni") { eval( @@ -523,124 +890,280 @@ object EvaluatorTests extends TestSuite { | main: { a: "1", b: 2, c: true, d: null, e: [1, {"2": 2}, [3]], f: {"hello": "world"} }, | sections: {} |})""".stripMargin, - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Str( "a = 1\nb = 2\nc = true\nd = null\ne = 1\ne = {\"2\": 2}\ne = [3]\nf = {\"hello\": \"world\"}\n" ) } test("format") { - eval("\"%s\" % \"world\"", useNewEvaluator = useNewEvaluator) ==> ujson.Str("world") - eval("\"%s\" % [\"world\"]", useNewEvaluator = useNewEvaluator) ==> ujson.Str("world") - eval("\"%s %s\" % [\"hello\", \"world\"]", useNewEvaluator = useNewEvaluator) ==> ujson.Str( + eval( + "\"%s\" % \"world\"", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str("world") + eval( + "\"%s\" % [\"world\"]", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str("world") + eval( + "\"%s %s\" % [\"hello\", \"world\"]", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str( "hello world" ) - eval("\"%(hello)s\" % {hello: \"world\"}", useNewEvaluator = useNewEvaluator) ==> ujson.Str( + eval( + "\"%(hello)s\" % {hello: \"world\"}", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str( "world" ) - eval("\"%()s %()s!\" % [\"Hello\", \"World\"]", useNewEvaluator = useNewEvaluator) ==> ujson + eval( + "\"%()s %()s!\" % [\"Hello\", \"World\"]", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson .Str("Hello World!") } test("binaryOps") { - val ex = assertThrows[Exception](eval("1 && 2", useNewEvaluator = useNewEvaluator)) + val ex = assertThrows[Exception]( + eval( + "1 && 2", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) + ) assert(ex.getMessage.contains("binary operator && does not operate on numbers.")) - val ex2 = assertThrows[Exception](eval("1 || 2", useNewEvaluator = useNewEvaluator)) + val ex2 = assertThrows[Exception]( + eval( + "1 || 2", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) + ) assert(ex2.getMessage.contains("binary operator || does not operate on numbers.")) } test("stdToString") { - eval("""std.toString({k: "v"})""", useNewEvaluator = useNewEvaluator) ==> ujson.Str( + eval( + """std.toString({k: "v"})""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str( """{"k": "v"}""" ) } test("floatFormatRegression") { - eval("'%.4f' % 0.01", useNewEvaluator = useNewEvaluator) ==> ujson.Str("0.0100") - eval("'%05d' % 2", useNewEvaluator = useNewEvaluator) ==> ujson.Str("00002") - eval("'%000d' % 2", useNewEvaluator = useNewEvaluator) ==> ujson.Str("2") - eval("'%000d' % 2.123", useNewEvaluator = useNewEvaluator) ==> ujson.Str("2") - eval("'%5d' % 2", useNewEvaluator = useNewEvaluator) ==> ujson.Str(" 2") - eval("'%5f' % 2", useNewEvaluator = useNewEvaluator) ==> ujson.Str("2.000000") - - eval("'%10d' % 2.123", useNewEvaluator = useNewEvaluator) ==> ujson.Str(" 2") - eval("'%+5.5f' % 123.456", useNewEvaluator = useNewEvaluator) ==> ujson.Str("+123.45600") - eval("'%+5.5f' % -123.456", useNewEvaluator = useNewEvaluator) ==> ujson.Str("-123.45600") - eval("'% 5.5f' % -123.456", useNewEvaluator = useNewEvaluator) ==> ujson.Str("-123.45600") - eval("'%--+5.5f' % -123.456", useNewEvaluator = useNewEvaluator) ==> ujson.Str("-123.45600") - eval("'%#-0- + 5.5f' % -123.456", useNewEvaluator = useNewEvaluator) ==> ujson.Str( + eval( + "'%.4f' % 0.01", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str("0.0100") + eval( + "'%05d' % 2", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str("00002") + eval( + "'%000d' % 2", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str("2") + eval( + "'%000d' % 2.123", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str("2") + eval( + "'%5d' % 2", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str(" 2") + eval( + "'%5f' % 2", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str("2.000000") + + eval( + "'%10d' % 2.123", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str(" 2") + eval( + "'%+5.5f' % 123.456", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str("+123.45600") + eval( + "'%+5.5f' % -123.456", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str("-123.45600") + eval( + "'% 5.5f' % -123.456", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str("-123.45600") + eval( + "'%--+5.5f' % -123.456", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str("-123.45600") + eval( + "'%#-0- + 5.5f' % -123.456", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str( "-123.45600" ) } test("formatIntegerOverflow") { // %d, %o, %x should not overflow to Long.MAX_VALUE for large doubles - eval("'%d' % 1e19", useNewEvaluator = useNewEvaluator) ==> ujson.Str("10000000000000000000") - eval("'%d' % 1e20", useNewEvaluator = useNewEvaluator) ==> ujson.Str( + eval( + "'%d' % 1e19", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str("10000000000000000000") + eval( + "'%d' % 1e20", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str( "100000000000000000000" ) - eval("'%d' % -1e19", useNewEvaluator = useNewEvaluator) ==> ujson.Str( + eval( + "'%d' % -1e19", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str( "-10000000000000000000" ) - eval("'%o' % 1e19", useNewEvaluator = useNewEvaluator) ==> ujson.Str( + eval( + "'%o' % 1e19", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str( "1053071060221172000000" ) - eval("'%x' % 1e19", useNewEvaluator = useNewEvaluator) ==> ujson.Str("8ac7230489e80000") + eval( + "'%x' % 1e19", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str("8ac7230489e80000") // Sign should be determined from the truncated integer, not the original double: // -0.3 truncates to 0, so no minus sign should appear. - eval("'%5.3d' % -0.3", useNewEvaluator = useNewEvaluator) ==> ujson.Str(" 000") - eval("'%d' % -0.9", useNewEvaluator = useNewEvaluator) ==> ujson.Str("0") + eval( + "'%5.3d' % -0.3", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str(" 000") + eval( + "'%d' % -0.9", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Str("0") } test("strict") { - eval("({ a: 1 } { b: 2 }).a", strict = false, useNewEvaluator = useNewEvaluator) ==> ujson + eval( + "({ a: 1 } { b: 2 }).a", + strict = false, + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson .Num(1) - evalErr("({ a: 1 } { b: 2 }).a", strict = true, useNewEvaluator = useNewEvaluator) ==> + evalErr( + "({ a: 1 } { b: 2 }).a", + strict = true, + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> """sjsonnet.StaticError: Adjacent object literals not allowed in strict mode - Use '+' to concatenate objects |at [].(:1:11)""".stripMargin eval( "local x = { c: 3 }; (x { a: 1 } { b: 2 }).a", strict = false, - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(1) eval( "local x = { c: 3 }; (x { a: 1 }).a", strict = true, - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(1) evalErr( "local x = { c: 3 }; ({ a: 1 } { b: 2 }).a", strict = true, - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> """sjsonnet.StaticError: Adjacent object literals not allowed in strict mode - Use '+' to concatenate objects |at [].(:1:31)""".stripMargin } test("objectDeclaration") { - eval("{ ['foo']: x for x in []}", false, useNewEvaluator = useNewEvaluator) ==> ujson.Obj() - eval("{ ['foo']: x for x in [1]}", false, useNewEvaluator = useNewEvaluator) ==> ujson.Obj( + eval( + "{ ['foo']: x for x in []}", + false, + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Obj() + eval( + "{ ['foo']: x for x in [1]}", + false, + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Obj( "foo" -> 1 ) - eval("{ ['foo']+: x for x in []}", false, useNewEvaluator = useNewEvaluator) ==> ujson.Obj() - eval("{ ['foo']+: x for x in [1]}", false, useNewEvaluator = useNewEvaluator) ==> ujson.Obj( + eval( + "{ ['foo']+: x for x in []}", + false, + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Obj() + eval( + "{ ['foo']+: x for x in [1]}", + false, + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Obj( "foo" -> 1 ) eval( "{ ['foo']+: [x] for x in [1]} + { ['foo']+: [x] for x in [2]}", false, - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Obj("foo" -> ujson.Arr(1, 2)) } test("givenNoDuplicateFieldsInListComprehension1_expectSuccess") { - eval("""{ ["bar"]: x for x in [-876.89]}""", useNewEvaluator = useNewEvaluator) ==> ujson.Obj( + eval( + """{ ["bar"]: x for x in [-876.89]}""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Obj( "bar" -> -876.89 ) } test("givenNoDuplicateFieldsInListComprehension2_expectSuccess") { - eval("""{ ["bar_" + x]: x for x in [5,12]}""", useNewEvaluator = useNewEvaluator) ==> ujson + eval( + """{ ["bar_" + x]: x for x in [5,12]}""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson .Obj("bar_5" -> 5, "bar_12" -> 12) } test("givenDuplicateFieldsInListComprehension_expectFailure") { evalErr( """{ [x]: x for x in ["A", "A"]}""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> """sjsonnet.Error: [object comprehension] Duplicate key A in evaluated object comprehension. |at [].(:1:1)""".stripMargin @@ -650,14 +1173,23 @@ object EvaluatorTests extends TestSuite { """local y = { a: "A" }; |local z = { a: "A" }; |{ [x.a]: x for x in [y, z]}""".stripMargin, - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> """sjsonnet.Error: [object comprehension] Duplicate key A in evaluated object comprehension. |at [].(:1:7)""".stripMargin } test("functionEqualsNull") { - eval("""local f(x)=null; f == null""", useNewEvaluator = useNewEvaluator) ==> ujson.False - eval("""local f=null; f == null""", useNewEvaluator = useNewEvaluator) ==> ujson.True + eval( + """local f(x)=null; f == null""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.False + eval( + """local f=null; f == null""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.True } test("dynamicDuplicateFields") { @@ -666,34 +1198,52 @@ object EvaluatorTests extends TestSuite { // duplicate field names is dynamically computed via a field name expression. // Cases where StaticOptimizer replaces the dynamic field names with fixed ones: - test - (evalErr("""{ ["k"]: 1, ["k"]: 2 }""", useNewEvaluator = useNewEvaluator) ==> + test - (evalErr( + """{ ["k"]: 1, ["k"]: 2 }""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> """sjsonnet.Error: Duplicate key k in evaluated object. |at [].(:1:1)""".stripMargin) - test - (evalErr("""{ k: 1, ["k"]: 2 }""", useNewEvaluator = useNewEvaluator) ==> + test - (evalErr( + """{ k: 1, ["k"]: 2 }""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> """sjsonnet.Error: Duplicate key k in evaluated object. |at [].(:1:1)""".stripMargin) - test - (evalErr("""{ ["k"]: 1, k: 2 }""", useNewEvaluator = useNewEvaluator) ==> + test - (evalErr( + """{ ["k"]: 1, k: 2 }""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> """sjsonnet.Error: Duplicate key k in evaluated object. |at [].(:1:1)""".stripMargin) // Test that lazy evaluation is preserved - duplicate fields should only error when accessed test - (eval( """{x: { ["k"]: 1, ["k"]: 2 }, y:1 }.y""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(1)) // But accessing the problematic field should still error test - (evalErr( """{x: { ["k"]: 1, ["k"]: 2 }, y:1 }.x""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> """sjsonnet.Error: Duplicate key k in evaluated object. |at [].(:1:34)""".stripMargin) // Non-StaticOptimizable case: - test - (evalErr("""{ k: 1, ["k" + ""]: 2 }""", useNewEvaluator = useNewEvaluator) ==> + test - (evalErr( + """{ k: 1, ["k" + ""]: 2 }""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> """sjsonnet.Error: Duplicate key k in evaluated object. |at [].(:1:1)""".stripMargin) } @@ -702,9 +1252,14 @@ object EvaluatorTests extends TestSuite { for (keyword <- Parser.keywords) { eval( s"""local ${keyword}Foo = 123; ${keyword}Foo""", - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(123) - eval(s"""{${keyword}Foo: 123}""", useNewEvaluator = useNewEvaluator) ==> ujson.Obj( + eval( + s"""{${keyword}Foo: 123}""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) ==> ujson.Obj( s"${keyword}Foo" -> ujson.Num(123) ) } @@ -712,10 +1267,18 @@ object EvaluatorTests extends TestSuite { test("errorNonString") { assert( - evalErr("""error {a: "b"}""", useNewEvaluator = useNewEvaluator).contains("""{"a": "b"}""") + evalErr( + """error {a: "b"}""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ).contains("""{"a": "b"}""") ) assert( - evalErr("""assert 1 == 2 : { a: "b"}; 1""", useNewEvaluator = useNewEvaluator).contains( + evalErr( + """assert 1 == 2 : { a: "b"}; 1""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ).contains( """{"a": "b"}""" ) ) @@ -723,33 +1286,57 @@ object EvaluatorTests extends TestSuite { test("assertInheritance") { test - assert( - evalErr("""{ } + {assert false}""", useNewEvaluator = useNewEvaluator).contains( + evalErr( + """{ } + {assert false}""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ).contains( "sjsonnet.Error: Assertion failed" ) ) test - assert( - evalErr("""{assert false} + {}""", useNewEvaluator = useNewEvaluator).contains( + evalErr( + """{assert false} + {}""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ).contains( "sjsonnet.Error: Assertion failed" ) ) test - assert( - evalErr("""{assert false} + {} + {}""", useNewEvaluator = useNewEvaluator).contains( + evalErr( + """{assert false} + {} + {}""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ).contains( "sjsonnet.Error: Assertion failed" ) ) test - assert( - evalErr("""{} + {assert false} + {}""", useNewEvaluator = useNewEvaluator).contains( + evalErr( + """{} + {assert false} + {}""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ).contains( "sjsonnet.Error: Assertion failed" ) ) test - assert( - evalErr("""{} + {} + {assert false}""", useNewEvaluator = useNewEvaluator).contains( + evalErr( + """{} + {} + {assert false}""", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ).contains( "sjsonnet.Error: Assertion failed" ) ) // Both own and inherited assertions are evaluated: test - assert( - evalErr("{assert false} + {assert true}", useNewEvaluator = useNewEvaluator).contains( + evalErr( + "{assert false} + {assert true}", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ).contains( "sjsonnet.Error: Assertion failed" ) ) @@ -757,12 +1344,20 @@ object EvaluatorTests extends TestSuite { // assertions plus any inherited assertions, even if the accessed member happens // to be a constant. test - assert( - evalErr("({assert false} + {x: 2}).x", useNewEvaluator = useNewEvaluator).contains( + evalErr( + "({assert false} + {x: 2}).x", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ).contains( "sjsonnet.Error: Assertion failed" ) ) test - assert( - evalErr("({assert false} + {f(x): x}).f(1)", useNewEvaluator = useNewEvaluator).contains( + evalErr( + "({assert false} + {f(x): x}).f(1)", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ).contains( "sjsonnet.Error: Assertion failed" ) ) @@ -774,7 +1369,8 @@ object EvaluatorTests extends TestSuite { assert( evalErr( problematicStrictInheritedAssertionsSnippet, - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ).contains("sjsonnet.Error: Assertion failed") ) } @@ -784,7 +1380,8 @@ object EvaluatorTests extends TestSuite { evalErr( """{ } + {assert false}""", useNewEvaluator = useNewEvaluator, - brokenAssertionLogic = true + brokenAssertionLogic = true, + aggressiveStaticOptimization = aggressiveOpt ).contains( "sjsonnet.Error: Assertion failed" ) @@ -793,7 +1390,8 @@ object EvaluatorTests extends TestSuite { evalErr( """{assert false} + {}""", useNewEvaluator = useNewEvaluator, - brokenAssertionLogic = true + brokenAssertionLogic = true, + aggressiveStaticOptimization = aggressiveOpt ).contains( "sjsonnet.Error: Assertion failed" ) @@ -802,7 +1400,8 @@ object EvaluatorTests extends TestSuite { evalErr( """{assert false} + {} + {}""", useNewEvaluator = useNewEvaluator, - brokenAssertionLogic = true + brokenAssertionLogic = true, + aggressiveStaticOptimization = aggressiveOpt ).contains( "sjsonnet.Error: Assertion failed" ) @@ -811,7 +1410,8 @@ object EvaluatorTests extends TestSuite { evalErr( """{} + {assert false} + {}""", useNewEvaluator = useNewEvaluator, - brokenAssertionLogic = true + brokenAssertionLogic = true, + aggressiveStaticOptimization = aggressiveOpt ).contains( "sjsonnet.Error: Assertion failed" ) @@ -820,7 +1420,8 @@ object EvaluatorTests extends TestSuite { evalErr( """{} + {} + {assert false}""", useNewEvaluator = useNewEvaluator, - brokenAssertionLogic = true + brokenAssertionLogic = true, + aggressiveStaticOptimization = aggressiveOpt ).contains( "sjsonnet.Error: Assertion failed" ) @@ -829,7 +1430,8 @@ object EvaluatorTests extends TestSuite { evalErr( "({assert false} + {f(x): x}).f(1)", useNewEvaluator = useNewEvaluator, - brokenAssertionLogic = true + brokenAssertionLogic = true, + aggressiveStaticOptimization = aggressiveOpt ).contains( "sjsonnet.Error: Assertion failed" ) @@ -843,7 +1445,8 @@ object EvaluatorTests extends TestSuite { evalErr( problematicStrictInheritedAssertionsSnippet, useNewEvaluator = useNewEvaluator, - brokenAssertionLogic = true + brokenAssertionLogic = true, + aggressiveStaticOptimization = aggressiveOpt ).contains("sjsonnet.Error: Assertion failed") ) } @@ -853,12 +1456,14 @@ object EvaluatorTests extends TestSuite { eval( "{assert false} + {assert true}", useNewEvaluator = useNewEvaluator, - brokenAssertionLogic = true + brokenAssertionLogic = true, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Obj() eval( "({assert false} + {x: 2}).x", useNewEvaluator = useNewEvaluator, - brokenAssertionLogic = true + brokenAssertionLogic = true, + aggressiveStaticOptimization = aggressiveOpt ) ==> ujson.Num(2) } } @@ -869,7 +1474,11 @@ object EvaluatorTests extends TestSuite { // Too many args test("tooManyArgs") { val ex = assertThrows[Exception] { - eval("std.length([1], [2])", useNewEvaluator = useNewEvaluator) + eval( + "std.length([1], [2])", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) } assert(ex.getMessage.contains("Too many args, has")) } @@ -877,7 +1486,11 @@ object EvaluatorTests extends TestSuite { // Parameter not bound in call (missing required arg) test("missingRequiredArg") { val ex = assertThrows[Exception] { - eval("std.substr('hello')", useNewEvaluator = useNewEvaluator) + eval( + "std.substr('hello')", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) } assert(ex.getMessage.contains("parameter")) assert(ex.getMessage.contains("not bound in call")) @@ -886,7 +1499,11 @@ object EvaluatorTests extends TestSuite { // Function has no parameter (invalid named arg) test("invalidNamedArg") { val ex = assertThrows[Exception] { - eval("std.length(x=[1], noSuchParam=2)", useNewEvaluator = useNewEvaluator) + eval( + "std.length(x=[1], noSuchParam=2)", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) } assert(ex.getMessage.contains("has no parameter noSuchParam")) } @@ -894,7 +1511,11 @@ object EvaluatorTests extends TestSuite { // Binding parameter a second time test("duplicateArg") { val ex = assertThrows[Exception] { - eval("std.pow(2, x=3)", useNewEvaluator = useNewEvaluator) + eval( + "std.pow(2, x=3)", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) } assert(ex.getMessage.contains("binding parameter a second time: x in function pow")) } @@ -906,7 +1527,8 @@ object EvaluatorTests extends TestSuite { """local myFunc(x, y) = x + y; |myFunc("a") """.stripMargin, - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) } assert(ex.getMessage.contains("parameter y not bound in call")) @@ -919,7 +1541,8 @@ object EvaluatorTests extends TestSuite { """local add(x, y) = x + y; |add(1, 2, 3) """.stripMargin, - useNewEvaluator = useNewEvaluator + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt ) } assert(ex.getMessage.contains("Too many args, has 2 parameter(s)")) @@ -928,7 +1551,11 @@ object EvaluatorTests extends TestSuite { // Anonymous function should NOT include function name in error message test("anonymousFunctionNoFunctionName") { val ex = assertThrows[Exception] { - eval("(function(x, y) x + y)('a')", useNewEvaluator = useNewEvaluator) + eval( + "(function(x, y) x + y)('a')", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) } assert(ex.getMessage.contains("parameter y not bound in call")) } @@ -936,7 +1563,11 @@ object EvaluatorTests extends TestSuite { // Anonymous function: too many args should NOT include function name test("anonymousFunctionTooManyArgs") { val ex = assertThrows[Exception] { - eval("(function(x) x)(1, 2)", useNewEvaluator = useNewEvaluator) + eval( + "(function(x) x)(1, 2)", + useNewEvaluator = useNewEvaluator, + aggressiveStaticOptimization = aggressiveOpt + ) } assert(ex.getMessage.contains("Too many args, has 1 parameter(s)")) } @@ -976,5 +1607,9 @@ object EvaluatorTests extends TestSuite { } } - def tests: Tests = allTests(false).prefix("Evaluator") ++ allTests(true).prefix("NewEvaluator") + def tests: Tests = + allTests(useNewEvaluator = false, aggressiveOpt = false).prefix("Evaluator") ++ + allTests(useNewEvaluator = true, aggressiveOpt = false).prefix("NewEvaluator") ++ + allTests(useNewEvaluator = false, aggressiveOpt = true).prefix("AggressiveOpt") ++ + allTests(useNewEvaluator = true, aggressiveOpt = true).prefix("NewEvaluator+AggressiveOpt") } diff --git a/sjsonnet/test/src/sjsonnet/TestUtils.scala b/sjsonnet/test/src/sjsonnet/TestUtils.scala index 4122c018..a6dcbb65 100644 --- a/sjsonnet/test/src/sjsonnet/TestUtils.scala +++ b/sjsonnet/test/src/sjsonnet/TestUtils.scala @@ -10,6 +10,7 @@ object TestUtils { useNewEvaluator: Boolean = false, brokenAssertionLogic: Boolean = false, maxStack: Int = 500, + aggressiveStaticOptimization: Boolean = false, std: sjsonnet.stdlib.StdLibModule = sjsonnet.stdlib.StdLibModule.Default) : Either[String, Value] = { new Interpreter( @@ -24,7 +25,8 @@ object TestUtils { throwErrorForInvalidSets = true, useNewEvaluator = useNewEvaluator, brokenAssertionLogic = brokenAssertionLogic, - maxStack = maxStack + maxStack = maxStack, + aggressiveStaticOptimization = aggressiveStaticOptimization ), std = std.module ).interpret(s, DummyPath("(memory)")) @@ -37,6 +39,7 @@ object TestUtils { useNewEvaluator: Boolean = false, brokenAssertionLogic: Boolean = false, maxStack: Int = 500, + aggressiveStaticOptimization: Boolean = false, std: sjsonnet.stdlib.StdLibModule = sjsonnet.stdlib.StdLibModule.Default): Value = { eval0( s, @@ -45,6 +48,7 @@ object TestUtils { useNewEvaluator, brokenAssertionLogic, maxStack, + aggressiveStaticOptimization, std ) match { case Right(x) => x @@ -59,6 +63,7 @@ object TestUtils { useNewEvaluator: Boolean = false, brokenAssertionLogic: Boolean = false, maxStack: Int = 500, + aggressiveStaticOptimization: Boolean = false, std: sjsonnet.stdlib.StdLibModule = sjsonnet.stdlib.StdLibModule.Default): String = { eval0( s, @@ -67,6 +72,7 @@ object TestUtils { useNewEvaluator, brokenAssertionLogic, maxStack, + aggressiveStaticOptimization, std ) match { case Left(err) =>