From 4eb8b30451d3e79b62fc970de3ade551797e2731 Mon Sep 17 00:00:00 2001 From: Stephen Amar Date: Fri, 3 Jan 2025 17:08:35 -0800 Subject: [PATCH] tailstrict --- build.sc | 2 +- sjsonnet/src/sjsonnet/Evaluator.scala | 79 ++++++++++++++----- sjsonnet/src/sjsonnet/Expr.scala | 10 +-- sjsonnet/src/sjsonnet/ExprTransform.scala | 20 ++--- sjsonnet/src/sjsonnet/Interpreter.scala | 2 +- sjsonnet/src/sjsonnet/Parser.scala | 4 +- sjsonnet/src/sjsonnet/StaticOptimizer.scala | 20 ++--- sjsonnet/src/sjsonnet/Val.scala | 75 ++++++++++++------ .../recursive_function_native.jsonnet | 38 +++++++++ .../test/src-jvm/sjsonnet/FileTests.scala | 2 +- .../test/src-native/sjsonnet/FileTests.scala | 2 +- sjsonnet/test/src/sjsonnet/FormatTests.scala | 1 + 12 files changed, 182 insertions(+), 73 deletions(-) create mode 100644 sjsonnet/test/resources/test_suite/recursive_function_native.jsonnet diff --git a/build.sc b/build.sc index 041b1876..56fcfcc1 100644 --- a/build.sc +++ b/build.sc @@ -114,7 +114,7 @@ object sjsonnet extends Module { def scalacOptions = Seq("-opt:l:inline", "-opt-inline-from:sjsonnet.**") object test extends ScalaTests with CrossTests { - def forkOptions = Seq("-Xss100m") + def forkArgs = Seq("-Xss100m") def sources = T.sources( this.millSourcePath / "src", this.millSourcePath / "src-jvm", diff --git a/sjsonnet/src/sjsonnet/Evaluator.scala b/sjsonnet/src/sjsonnet/Evaluator.scala index 48213291..0e1e6d30 100644 --- a/sjsonnet/src/sjsonnet/Evaluator.scala +++ b/sjsonnet/src/sjsonnet/Evaluator.scala @@ -28,7 +28,12 @@ class Evaluator(resolver: CachedResolver, def materialize(v: Val): Value = Materializer.apply(v) val cachedImports = collection.mutable.HashMap.empty[Path, Val] - def visitExpr(e: Expr)(implicit scope: ValScope): Val = try { + var isInTailstrictMode = false + + override def tailstrict: Boolean = isInTailstrictMode + + + override def visitExpr(e: Expr)(implicit scope: ValScope): Val = try { e match { case e: ValidId => visitValidId(e) case e: BinaryOp => visitBinaryOp(e) @@ -184,14 +189,24 @@ class Evaluator(resolver: CachedResolver, private def visitApply(e: Apply)(implicit scope: ValScope) = { val lhs = visitExpr(e.value) - val args = e.args - val argsL = new Array[Lazy](args.length) - var idx = 0 - while (idx < args.length) { - argsL(idx) = visitAsLazy(args(idx)) - idx += 1 + + if (isInTailstrictMode) { + lhs.cast[Val.Func].apply(e.args.map(visitExpr(_)), e.namedNames, e.pos) + } else if (e.tailstrict) { + isInTailstrictMode = true + val res = lhs.cast[Val.Func].apply(e.args.map(visitExpr(_)), e.namedNames, e.pos) + isInTailstrictMode = false + res + } else { + val args = e.args + val argsL = new Array[Lazy](args.length) + var idx = 0 + while (idx < args.length) { + argsL(idx) = visitAsLazy(args(idx)) + idx += 1 + } + lhs.cast[Val.Func].apply(argsL, e.namedNames, e.pos) } - lhs.cast[Val.Func].apply(argsL, e.namedNames, e.pos) } private def visitApply0(e: Apply0)(implicit scope: ValScope): Val = { @@ -201,23 +216,50 @@ class Evaluator(resolver: CachedResolver, private def visitApply1(e: Apply1)(implicit scope: ValScope): Val = { val lhs = visitExpr(e.value) - val l1 = visitAsLazy(e.a1) - lhs.cast[Val.Func].apply1(l1, e.pos) + if (isInTailstrictMode) { + lhs.cast[Val.Func].apply1(visitExpr(e.a1), e.pos) + } else if (e.tailstrict) { + isInTailstrictMode = true + val res = lhs.cast[Val.Func].apply1(visitExpr(e.a1), e.pos) + isInTailstrictMode = false + res + } else { + val l1 = visitAsLazy(e.a1) + lhs.cast[Val.Func].apply1(l1, e.pos) + } } private def visitApply2(e: Apply2)(implicit scope: ValScope): Val = { val lhs = visitExpr(e.value) - val l1 = visitAsLazy(e.a1) - val l2 = visitAsLazy(e.a2) - lhs.cast[Val.Func].apply2(l1, l2, e.pos) + if (isInTailstrictMode) { + lhs.cast[Val.Func].apply2(visitExpr(e.a1), visitExpr(e.a2), e.pos) + } else if (e.tailstrict) { + isInTailstrictMode = true + val res = lhs.cast[Val.Func].apply2(visitExpr(e.a1), visitExpr(e.a2), e.pos) + isInTailstrictMode = false + res + } else { + val l1 = visitAsLazy(e.a1) + val l2 = visitAsLazy(e.a2) + lhs.cast[Val.Func].apply2(l1, l2, e.pos) + } } private def visitApply3(e: Apply3)(implicit scope: ValScope): Val = { val lhs = visitExpr(e.value) - val l1 = visitAsLazy(e.a1) - val l2 = visitAsLazy(e.a2) - val l3 = visitAsLazy(e.a3) - lhs.cast[Val.Func].apply3(l1, l2, l3, e.pos) + if (isInTailstrictMode) { + lhs.cast[Val.Func].apply3(visitExpr(e.a1), visitExpr(e.a2), visitExpr(e.a3), e.pos) + } else if (e.tailstrict) { + isInTailstrictMode = true + val res = lhs.cast[Val.Func].apply3(visitExpr(e.a1), visitExpr(e.a2), visitExpr(e.a3), e.pos) + isInTailstrictMode = false + res + } else { + val l1 = visitAsLazy(e.a1) + val l2 = visitAsLazy(e.a2) + val l3 = visitAsLazy(e.a3) + lhs.cast[Val.Func].apply3(l1, l2, l3, e.pos) + } } private def visitApplyBuiltin1(e: ApplyBuiltin1)(implicit scope: ValScope) = @@ -642,7 +684,8 @@ class Evaluator(resolver: CachedResolver, newSelf } - def visitComp(f: List[CompSpec], scopes: Array[ValScope]): Array[ValScope] = f match{ + @inline + private final def visitComp(f: List[CompSpec], scopes: Array[ValScope]): Array[ValScope] = f match{ case (spec @ ForSpec(_, name, expr)) :: rest => visitComp( rest, diff --git a/sjsonnet/src/sjsonnet/Expr.scala b/sjsonnet/src/sjsonnet/Expr.scala index 8216142c..0baee6cf 100644 --- a/sjsonnet/src/sjsonnet/Expr.scala +++ b/sjsonnet/src/sjsonnet/Expr.scala @@ -128,11 +128,11 @@ object Expr{ case class ImportStr(pos: Position, value: String) extends Expr case class ImportBin(pos: Position, value: String) extends Expr case class Error(pos: Position, value: Expr) extends Expr - case class Apply(pos: Position, value: Expr, args: Array[Expr], namedNames: Array[String]) extends Expr - case class Apply0(pos: Position, value: Expr) extends Expr - case class Apply1(pos: Position, value: Expr, a1: Expr) extends Expr - case class Apply2(pos: Position, value: Expr, a1: Expr, a2: Expr) extends Expr - case class Apply3(pos: Position, value: Expr, a1: Expr, a2: Expr, a3: Expr) extends Expr + case class Apply(pos: Position, value: Expr, args: Array[Expr], namedNames: Array[String], tailstrict: Boolean) extends Expr + case class Apply0(pos: Position, value: Expr, tailstrict: Boolean) extends Expr + case class Apply1(pos: Position, value: Expr, a1: Expr, tailstrict: Boolean) extends Expr + case class Apply2(pos: Position, value: Expr, a1: Expr, a2: Expr, tailstrict: Boolean) extends Expr + case class Apply3(pos: Position, value: Expr, a1: Expr, a2: Expr, a3: Expr, tailstrict: Boolean) extends Expr case class ApplyBuiltin(pos: Position, func: Val.Builtin, argExprs: Array[Expr]) extends Expr { override def exprErrorString: String = s"std.${func.functionName}" } diff --git a/sjsonnet/src/sjsonnet/ExprTransform.scala b/sjsonnet/src/sjsonnet/ExprTransform.scala index 964833ee..d7d0657d 100644 --- a/sjsonnet/src/sjsonnet/ExprTransform.scala +++ b/sjsonnet/src/sjsonnet/ExprTransform.scala @@ -14,37 +14,37 @@ abstract class ExprTransform { if(x2 eq x) expr else Select(pos, x2, name) - case Apply(pos, x, y, namedNames) => + case Apply(pos, x, y, namedNames, tailstrict) => val x2 = transform(x) val y2 = transformArr(y) if((x2 eq x) && (y2 eq y)) expr - else Apply(pos, x2, y2, namedNames) + else Apply(pos, x2, y2, namedNames, tailstrict) - case Apply0(pos, x) => + case Apply0(pos, x, tailstrict) => val x2 = transform(x) if((x2 eq x)) expr - else Apply0(pos, x2) + else Apply0(pos, x2, tailstrict) - case Apply1(pos, x, y) => + case Apply1(pos, x, y, tailstrict) => val x2 = transform(x) val y2 = transform(y) if((x2 eq x) && (y2 eq y)) expr - else Apply1(pos, x2, y2) + else Apply1(pos, x2, y2, tailstrict) - case Apply2(pos, x, y, z) => + case Apply2(pos, x, y, z, tailstrict) => val x2 = transform(x) val y2 = transform(y) val z2 = transform(z) if((x2 eq x) && (y2 eq y) && (z2 eq z)) expr - else Apply2(pos, x2, y2, z2) + else Apply2(pos, x2, y2, z2, tailstrict) - case Apply3(pos, x, y, z, a) => + case Apply3(pos, x, y, z, a, tailstrict) => val x2 = transform(x) val y2 = transform(y) val z2 = transform(z) val a2 = transform(a) if((x2 eq x) && (y2 eq y) && (z2 eq z) && (a2 eq a)) expr - else Apply3(pos, x2, y2, z2, a2) + else Apply3(pos, x2, y2, z2, a2, tailstrict) case ApplyBuiltin(pos, func, x) => val x2 = transformArr(x) diff --git a/sjsonnet/src/sjsonnet/Interpreter.scala b/sjsonnet/src/sjsonnet/Interpreter.scala index 6ce485bc..59513edd 100644 --- a/sjsonnet/src/sjsonnet/Interpreter.scala +++ b/sjsonnet/src/sjsonnet/Interpreter.scala @@ -107,7 +107,7 @@ class Interpreter(extVars: Map[String, String], override def evalDefault(expr: Expr, vs: ValScope, es: EvalScope) = { evaluator.visitExpr(expr)(if (tlaExpressions.exists(_ eq expr)) ValScope.empty else vs) } - }.apply0(f.pos)(evaluator) + }.apply0(f.pos)(evaluator, f.defSiteValScope) case x => x } } yield res diff --git a/sjsonnet/src/sjsonnet/Parser.scala b/sjsonnet/src/sjsonnet/Parser.scala index 1c06df6a..a3d52c5d 100644 --- a/sjsonnet/src/sjsonnet/Parser.scala +++ b/sjsonnet/src/sjsonnet/Parser.scala @@ -227,8 +227,8 @@ class Parser(val currentFile: Path, case (Some(tree), Seq()) => Expr.Lookup(i, _: Expr, tree) case (start, ins) => Expr.Slice(i, _: Expr, start, ins.lift(0).flatten, ins.lift(1).flatten) } - case '(' => Pass ~ (args ~ ")").map { case (args, namedNames) => - Expr.Apply(i, _: Expr, args, if(namedNames.length == 0) null else namedNames) + case '(' => Pass ~ (args ~ ")" ~ "tailstrict".?.!).map { + case (args, namedNames, tailstrict) => Expr.Apply(i, _: Expr, args, if(namedNames.length == 0) null else namedNames, tailstrict.nonEmpty) } case '{' => Pass ~ (objinside ~ "}").map(x => Expr.ObjExtend(i, _: Expr, x)) case _ => Fail diff --git a/sjsonnet/src/sjsonnet/StaticOptimizer.scala b/sjsonnet/src/sjsonnet/StaticOptimizer.scala index 6e1e2854..694ef7fb 100644 --- a/sjsonnet/src/sjsonnet/StaticOptimizer.scala +++ b/sjsonnet/src/sjsonnet/StaticOptimizer.scala @@ -111,7 +111,7 @@ class StaticOptimizer( } private def transformApply(a: Apply): Expr = { - val rebound = rebindApply(a.pos, a.value, a.args, a.namedNames) match { + val rebound = rebindApply(a.pos, a.value, a.args, a.namedNames, a.tailstrict) match { case null => a case a => a } @@ -121,7 +121,7 @@ class StaticOptimizer( } } - private def tryStaticApply(pos: Position, f: Val.Builtin, args: Array[Expr]): Expr = { + private def tryStaticApply(pos: Position, f: Val.Builtin, args: Array[Expr], tailstrict: Boolean): Expr = { if(f.staticSafe && args.forall(_.isInstanceOf[Val])) { val vargs = args.map(_.asInstanceOf[Val]) try f.apply(vargs, null, pos)(ev).asInstanceOf[Expr] catch { case _: Exception => return null } @@ -131,20 +131,20 @@ class StaticOptimizer( private def specializeApplyArity(a: Apply): Expr = { if(a.namedNames != null) a else a.args.length match { - case 0 => Apply0(a.pos, a.value) - case 1 => Apply1(a.pos, a.value, a.args(0)) - case 2 => Apply2(a.pos, a.value, a.args(0), a.args(1)) - case 3 => Apply3(a.pos, a.value, a.args(0), a.args(1), a.args(2)) + case 0 => Apply0(a.pos, a.value, a.tailstrict) + case 1 => Apply1(a.pos, a.value, a.args(0), a.tailstrict) + case 2 => Apply2(a.pos, a.value, a.args(0), a.args(1), a.tailstrict) + case 3 => Apply3(a.pos, a.value, a.args(0), a.args(1), a.args(2), a.tailstrict) case _ => a } } - private def rebindApply(pos: Position, lhs: Expr, args: Array[Expr], names: Array[String]): Expr = lhs match { + private def rebindApply(pos: Position, lhs: Expr, args: Array[Expr], names: Array[String], tailstrict: Boolean): Expr = lhs match { case f: Val.Builtin => rebind(args, names, f.params) match { case null => null case newArgs => - tryStaticApply(pos, f, newArgs) match { + tryStaticApply(pos, f, newArgs, tailstrict) match { case null => val (f2, rargs) = f.specialize(newArgs) match { case null => (f, newArgs) @@ -166,12 +166,12 @@ class StaticOptimizer( case ScopedVal(Function(_, params, _), _, _) => rebind(args, names, params) match { case null => null - case newArgs => Apply(pos, lhs, newArgs, null) + case newArgs => Apply(pos, lhs, newArgs, null, tailstrict) } case ScopedVal(Bind(_, _, params, _), _, _) => rebind(args, names, params) match { case null => null - case newArgs => Apply(pos, lhs, newArgs, null) + case newArgs => Apply(pos, lhs, newArgs, null, tailstrict) } case _ => null } diff --git a/sjsonnet/src/sjsonnet/Val.scala b/sjsonnet/src/sjsonnet/Val.scala index 9e02311d..78bba4c4 100644 --- a/sjsonnet/src/sjsonnet/Val.scala +++ b/sjsonnet/src/sjsonnet/Val.scala @@ -377,11 +377,14 @@ object Val{ override def asFunc: Func = this - def apply(argsL: Array[_ <: Lazy], namedNames: Array[String], outerPos: Position)(implicit ev: EvalScope): Val = { + def apply(argsL: Array[_ <: Lazy], namedNames: Array[String], outerPos: Position)(implicit ev: EvalScope, vs: ValScope = defSiteValScope): Val = { val simple = namedNames == null && params.names.length == argsL.length val funDefFileScope: FileScope = pos match { case null => outerPos.fileScope case p => p.fileScope } //println(s"apply: argsL: ${argsL.length}, namedNames: $namedNames, paramNames: ${params.names.mkString(",")}") - if(simple) { + if (ev.tailstrict) { + System.arraycopy(argsL, 0, vs.bindings, defSiteValScope.length, argsL.length) + evalRhs(vs, ev, funDefFileScope, outerPos) + } else if(simple) { val newScope = defSiteValScope.extendSimple(argsL) evalRhs(newScope, ev, funDefFileScope, outerPos) } else { @@ -434,7 +437,7 @@ object Val{ } } - def apply0(outerPos: Position)(implicit ev: EvalScope): Val = { + def apply0(outerPos: Position)(implicit ev: EvalScope, vs: ValScope = defSiteValScope): Val = { if(params.names.length != 0) apply(Evaluator.emptyLazyArray, null, outerPos) else { val funDefFileScope: FileScope = pos match { case null => outerPos.fileScope case p => p.fileScope } @@ -442,30 +445,48 @@ object Val{ } } - def apply1(argVal: Lazy, outerPos: Position)(implicit ev: EvalScope): Val = { + def apply1(argVal: Lazy, outerPos: Position)(implicit ev: EvalScope, vs: ValScope = defSiteValScope): Val = { if(params.names.length != 1) apply(Array(argVal), null, outerPos) else { val funDefFileScope: FileScope = pos match { case null => outerPos.fileScope case p => p.fileScope } - val newScope: ValScope = defSiteValScope.extendSimple(argVal) - evalRhs(newScope, ev, funDefFileScope, outerPos) + if (ev.tailstrict) { + vs.bindings(defSiteValScope.length) = argVal + evalRhs(vs, ev, funDefFileScope, outerPos) + } else { + val newScope: ValScope = defSiteValScope.extendSimple(argVal) + evalRhs(newScope, ev, funDefFileScope, outerPos) + } } } - def apply2(argVal1: Lazy, argVal2: Lazy, outerPos: Position)(implicit ev: EvalScope): Val = { + def apply2(argVal1: Lazy, argVal2: Lazy, outerPos: Position)(implicit ev: EvalScope, vs: ValScope = defSiteValScope): Val = { if(params.names.length != 2) apply(Array(argVal1, argVal2), null, outerPos) else { val funDefFileScope: FileScope = pos match { case null => outerPos.fileScope case p => p.fileScope } - val newScope: ValScope = defSiteValScope.extendSimple(argVal1, argVal2) - evalRhs(newScope, ev, funDefFileScope, outerPos) + if (ev.tailstrict) { + vs.bindings(defSiteValScope.length) = argVal1 + vs.bindings(defSiteValScope.length+1) = argVal2 + evalRhs(vs, ev, funDefFileScope, outerPos) + } else { + val newScope: ValScope = defSiteValScope.extendSimple(argVal1, argVal2) + evalRhs(newScope, ev, funDefFileScope, outerPos) + } } } - def apply3(argVal1: Lazy, argVal2: Lazy, argVal3: Lazy, outerPos: Position)(implicit ev: EvalScope): Val = { + def apply3(argVal1: Lazy, argVal2: Lazy, argVal3: Lazy, outerPos: Position)(implicit ev: EvalScope, vs: ValScope = defSiteValScope): Val = { if(params.names.length != 3) apply(Array(argVal1, argVal2, argVal3), null, outerPos) else { val funDefFileScope: FileScope = pos match { case null => outerPos.fileScope case p => p.fileScope } - val newScope: ValScope = defSiteValScope.extendSimple(argVal1, argVal2, argVal3) - evalRhs(newScope, ev, funDefFileScope, outerPos) + if (ev.tailstrict) { + vs.bindings(defSiteValScope.length) = argVal1 + vs.bindings(defSiteValScope.length+1) = argVal2 + vs.bindings(defSiteValScope.length+2) = argVal3 + evalRhs(vs, ev, funDefFileScope, outerPos) + } else { + val newScope: ValScope = defSiteValScope.extendSimple(argVal1, argVal2, argVal3) + evalRhs(newScope, ev, funDefFileScope, outerPos) + } } } } @@ -483,13 +504,17 @@ object Val{ def evalRhs(args: Array[_ <: Lazy], ev: EvalScope, pos: Position): Val - override def apply1(argVal: Lazy, outerPos: Position)(implicit ev: EvalScope): Val = - if(params.names.length != 1) apply(Array(argVal), null, outerPos) - else evalRhs(Array(argVal), ev, outerPos) + override def apply(argsL: Array[_ <: Lazy], namedNames: Array[String], outerPos: Position)(implicit ev: EvalScope, vs: ValScope): Val = + evalRhs(argsL, ev, outerPos) - override def apply2(argVal1: Lazy, argVal2: Lazy, outerPos: Position)(implicit ev: EvalScope): Val = - if(params.names.length != 2) apply(Array(argVal1, argVal2), null, outerPos) - else evalRhs(Array(argVal1, argVal2), ev, outerPos) + override def apply1(argVal: Lazy, outerPos: Position)(implicit ev: EvalScope, vs: ValScope): Val = + evalRhs(Array(argVal), ev, outerPos) + + override def apply2(argVal1: Lazy, argVal2: Lazy, outerPos: Position)(implicit ev: EvalScope, vs: ValScope): Val = + evalRhs(Array(argVal1, argVal2), ev, outerPos) + + override def apply3(argVal1: Lazy, argVal2: Lazy, argVal3: Lazy, outerPos: Position)(implicit ev: EvalScope, vs: ValScope): Val = + evalRhs(Array(argVal1, argVal2, argVal3), ev, outerPos) /** Specialize a call to this function in the optimizer. Must return either `null` to leave the * call-site as it is or a pair of a (possibly different) `Builtin` and the arguments to pass @@ -508,11 +533,11 @@ object Val{ def evalRhs(arg1: Val, ev: EvalScope, pos: Position): Val - override def apply(argVals: Array[_ <: Lazy], namedNames: Array[String], outerPos: Position)(implicit ev: EvalScope): Val = + override def apply(argVals: Array[_ <: Lazy], namedNames: Array[String], outerPos: Position)(implicit ev: EvalScope, vs: ValScope): Val = if(namedNames == null && argVals.length == 1) evalRhs(argVals(0).force, ev, outerPos) else super.apply(argVals, namedNames, outerPos) - override def apply1(argVal: Lazy, outerPos: Position)(implicit ev: EvalScope): Val = + override def apply1(argVal: Lazy, outerPos: Position)(implicit ev: EvalScope, vs: ValScope): Val = if(params.names.length == 1) evalRhs(argVal.force, ev, outerPos) else super.apply(Array(argVal), null, outerPos) } @@ -523,12 +548,12 @@ object Val{ def evalRhs(arg1: Val, arg2: Val, ev: EvalScope, pos: Position): Val - override def apply(argVals: Array[_ <: Lazy], namedNames: Array[String], outerPos: Position)(implicit ev: EvalScope): Val = + override def apply(argVals: Array[_ <: Lazy], namedNames: Array[String], outerPos: Position)(implicit ev: EvalScope, vs: ValScope): Val = if(namedNames == null && argVals.length == 2) evalRhs(argVals(0).force, argVals(1).force, ev, outerPos) else super.apply(argVals, namedNames, outerPos) - override def apply2(argVal1: Lazy, argVal2: Lazy, outerPos: Position)(implicit ev: EvalScope): Val = + override def apply2(argVal1: Lazy, argVal2: Lazy, outerPos: Position)(implicit ev: EvalScope, vs: ValScope): Val = if(params.names.length == 2) evalRhs(argVal1.force, argVal2.force, ev, outerPos) else super.apply(Array(argVal1, argVal2), null, outerPos) } @@ -539,7 +564,7 @@ object Val{ def evalRhs(arg1: Val, arg2: Val, arg3: Val, ev: EvalScope, pos: Position): Val - override def apply(argVals: Array[_ <: Lazy], namedNames: Array[String], outerPos: Position)(implicit ev: EvalScope): Val = + override def apply(argVals: Array[_ <: Lazy], namedNames: Array[String], outerPos: Position)(implicit ev: EvalScope, vs: ValScope): Val = if(namedNames == null && argVals.length == 3) evalRhs(argVals(0).force, argVals(1).force, argVals(2).force, ev, outerPos) else super.apply(argVals, namedNames, outerPos) @@ -551,7 +576,7 @@ object Val{ def evalRhs(arg1: Val, arg2: Val, arg3: Val, arg4: Val, ev: EvalScope, pos: Position): Val - override def apply(argVals: Array[_ <: Lazy], namedNames: Array[String], outerPos: Position)(implicit ev: EvalScope): Val = + override def apply(argVals: Array[_ <: Lazy], namedNames: Array[String], outerPos: Position)(implicit ev: EvalScope, vs: ValScope): Val = if(namedNames == null && argVals.length == 4) evalRhs(argVals(0).force, argVals(1).force, argVals(2).force, argVals(3).force, ev, outerPos) else super.apply(argVals, namedNames, outerPos) @@ -563,6 +588,8 @@ object Val{ * throughout the Jsonnet evaluation. */ abstract class EvalScope extends EvalErrorScope with Ordering[Val] { + def tailstrict: Boolean + def visitExpr(expr: Expr) (implicit scope: ValScope): Val diff --git a/sjsonnet/test/resources/test_suite/recursive_function_native.jsonnet b/sjsonnet/test/resources/test_suite/recursive_function_native.jsonnet new file mode 100644 index 00000000..beaaead0 --- /dev/null +++ b/sjsonnet/test/resources/test_suite/recursive_function_native.jsonnet @@ -0,0 +1,38 @@ +/* +Copyright 2015 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +local fibonacci(n) = + if n <= 1 then + 1 + else + fibonacci(n - 1) + fibonacci(n - 2); + +std.assertEqual(fibonacci(15), 987) && + +// Tail recursive call +local sum(x, v) = + if x <= 0 then + v + else + sum(x - 1, x + v) tailstrict; + +// Scala Native is struggling with large stacks. +local sz = 1000; +std.assertEqual(sum(sz, 0), sz * (sz + 1) / 2) && + +std.assertEqual(local x() = 3; x() tailstrict, 3) && + +true diff --git a/sjsonnet/test/src-jvm/sjsonnet/FileTests.scala b/sjsonnet/test/src-jvm/sjsonnet/FileTests.scala index 395ef37e..688133c2 100644 --- a/sjsonnet/test/src-jvm/sjsonnet/FileTests.scala +++ b/sjsonnet/test/src-jvm/sjsonnet/FileTests.scala @@ -53,7 +53,7 @@ object FileTests extends TestSuite{ test("oop_extra") - check() test("parsing_edge_cases") - check() test("precedence") - check() -// test("recursive_function") - check() + test("recursive_function") - check() test("recursive_import_ok") - check() test("recursive_object") - check() test("regex") - check() diff --git a/sjsonnet/test/src-native/sjsonnet/FileTests.scala b/sjsonnet/test/src-native/sjsonnet/FileTests.scala index 01330941..bd4ff5de 100644 --- a/sjsonnet/test/src-native/sjsonnet/FileTests.scala +++ b/sjsonnet/test/src-native/sjsonnet/FileTests.scala @@ -53,7 +53,7 @@ object FileTests extends TestSuite{ test("oop_extra") - check() test("parsing_edge_cases") - check() test("precedence") - check() -// test("recursive_function") - check() + test("recursive_function_native") - check() test("recursive_import_ok") - check() test("recursive_object") - check() test("sanity") - checkGolden() diff --git a/sjsonnet/test/src/sjsonnet/FormatTests.scala b/sjsonnet/test/src/sjsonnet/FormatTests.scala index 936d8bfe..1ba6357d 100644 --- a/sjsonnet/test/src/sjsonnet/FormatTests.scala +++ b/sjsonnet/test/src/sjsonnet/FormatTests.scala @@ -10,6 +10,7 @@ object FormatTests extends TestSuite{ val json = ujson.read(jsonStr) val formatted = Format.format(fmt, Materializer.reverse(null, json), dummyPos)( new EvalScope{ + def tailstrict: Boolean = false def extVars = _ => None def wd: Path = DummyPath() def visitExpr(expr: Expr)(implicit scope: ValScope): Val = ???