From b9b25c8281ecff69a1476bfaeb6c2a1c92224f14 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/SjsonnetMain.scala | 2 +- sjsonnet/src/sjsonnet/Evaluator.scala | 124 +++++++++++++----- 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 +++++++---- 9 files changed, 172 insertions(+), 87 deletions(-) diff --git a/build.sc b/build.sc index 041b1876..2e28dae9 100644 --- a/build.sc +++ b/build.sc @@ -111,7 +111,7 @@ object sjsonnet extends Module { ivy"org.yaml:snakeyaml::1.33", ivy"com.google.re2j:re2j:1.7", ) - def scalacOptions = Seq("-opt:l:inline", "-opt-inline-from:sjsonnet.**") + def scalacOptions = Seq("-opt:l:inline", "-opt-inline-from:sjsonnet.*,sjsonnet.**", "-opt-warnings") object test extends ScalaTests with CrossTests { def forkOptions = Seq("-Xss100m") diff --git a/sjsonnet/src-jvm-native/sjsonnet/SjsonnetMain.scala b/sjsonnet/src-jvm-native/sjsonnet/SjsonnetMain.scala index 04d1f8c7..6d993f95 100644 --- a/sjsonnet/src-jvm-native/sjsonnet/SjsonnetMain.scala +++ b/sjsonnet/src-jvm-native/sjsonnet/SjsonnetMain.scala @@ -57,7 +57,7 @@ object SjsonnetMain { } val parser = mainargs.ParserForClass[Config] - val name = s"Sjsonnet ${sjsonnet.Version.version}" + val name = s"Sjsonnet 10" val doc = "usage: sjsonnet [sjsonnet-options] script-file" val result = for{ config <- parser.constructEither( diff --git a/sjsonnet/src/sjsonnet/Evaluator.scala b/sjsonnet/src/sjsonnet/Evaluator.scala index 48213291..b4aa2228 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,40 +189,78 @@ 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 = { + @inline + private final def visitApply0(e: Apply0)(implicit scope: ValScope): Val = { val lhs = visitExpr(e.value) lhs.cast[Val.Func].apply0(e.pos) } 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) = @@ -277,7 +320,8 @@ class Evaluator(resolver: CachedResolver, } } - def visitLookup(e: Lookup)(implicit scope: ValScope): Val = { + @inline + private final def visitLookup(e: Lookup)(implicit scope: ValScope): Val = { val pos = e.pos (visitExpr(e.value), visitExpr(e.index)) match { case (v: Val.Arr, i: Val.Num) => @@ -294,21 +338,25 @@ class Evaluator(resolver: CachedResolver, } } - def visitLookupSuper(e: LookupSuper)(implicit scope: ValScope): Val = { + @inline + private final def visitLookupSuper(e: LookupSuper)(implicit scope: ValScope): Val = { var sup = scope.bindings(e.selfIdx+1).asInstanceOf[Val.Obj] val key = visitExpr(e.index).cast[Val.Str] if(sup == null) sup = scope.bindings(e.selfIdx).asInstanceOf[Val.Obj] sup.value(key.value, e.pos) } - def visitImportStr(e: ImportStr)(implicit scope: ValScope): Val.Str = + @inline + private final def visitImportStr(e: ImportStr)(implicit scope: ValScope): Val.Str = Val.Str(e.pos, importer.resolveAndReadOrFail(e.value, e.pos, binaryData = false)._2.readString()) - def visitImportBin(e: ImportBin): Val.Arr = + @inline + private final def visitImportBin(e: ImportBin): Val.Arr = new Val.Arr(e.pos, importer.resolveAndReadOrFail(e.value, e.pos, binaryData = true)._2.readRawBytes().map( x => Val.Num(e.pos, (x & 0xff).doubleValue))) - def visitImport(e: Import)(implicit scope: ValScope): Val = { + @inline + private final def visitImport(e: Import)(implicit scope: ValScope): Val = { val (p, str) = importer.resolveAndReadOrFail(e.value, e.pos, binaryData = false) cachedImports.getOrElseUpdate( p, @@ -322,7 +370,8 @@ class Evaluator(resolver: CachedResolver, ) } - def visitAnd(e: And)(implicit scope: ValScope) = { + @inline + private final def visitAnd(e: And)(implicit scope: ValScope) = { visitExpr(e.lhs) match { case _: Val.True => visitExpr(e.rhs) match{ @@ -336,7 +385,8 @@ class Evaluator(resolver: CachedResolver, } } - def visitOr(e: Or)(implicit scope: ValScope) = { + @inline + private final def visitOr(e: Or)(implicit scope: ValScope) = { visitExpr(e.lhs) match { case _: Val.True => Val.True(e.pos) case _: Val.False => @@ -350,7 +400,8 @@ class Evaluator(resolver: CachedResolver, } } - def visitInSuper(e: InSuper)(implicit scope: ValScope) = { + @inline + private final def visitInSuper(e: InSuper)(implicit scope: ValScope) = { val sup = scope.bindings(e.selfIdx+1).asInstanceOf[Val.Obj] if(sup == null) Val.False(e.pos) else { @@ -359,7 +410,8 @@ class Evaluator(resolver: CachedResolver, } } - def visitBinaryOp(e: BinaryOp)(implicit scope: ValScope) = { + @inline + private final def visitBinaryOp(e: BinaryOp)(implicit scope: ValScope) = { val l = visitExpr(e.lhs) val r = visitExpr(e.rhs) val pos = e.pos @@ -469,7 +521,8 @@ class Evaluator(resolver: CachedResolver, } } - def visitFieldName(fieldName: FieldName, pos: Position)(implicit scope: ValScope): String = { + @inline + private final def visitFieldName(fieldName: FieldName, pos: Position)(implicit scope: ValScope): String = { fieldName match { case FieldName.Fixed(s) => s case FieldName.Dyn(k) => visitExpr(k) match{ @@ -483,13 +536,15 @@ class Evaluator(resolver: CachedResolver, } } - def visitMethod(rhs: Expr, params: Params, outerPos: Position)(implicit scope: ValScope) = + @inline + private final def visitMethod(rhs: Expr, params: Params, outerPos: Position)(implicit scope: ValScope) = new Val.Func(outerPos, scope, params) { def evalRhs(vs: ValScope, es: EvalScope, fs: FileScope, pos: Position): Val = visitExpr(rhs)(vs) override def evalDefault(expr: Expr, vs: ValScope, es: EvalScope) = visitExpr(expr)(vs) } - def visitBindings(bindings: Array[Bind], scope: (Val.Obj, Val.Obj) => ValScope): Array[(Val.Obj, Val.Obj) => Lazy] = { + @inline + private final def visitBindings(bindings: Array[Bind], scope: (Val.Obj, Val.Obj) => ValScope): Array[(Val.Obj, Val.Obj) => Lazy] = { val arrF = new Array[(Val.Obj, Val.Obj) => Lazy](bindings.length) var i = 0 while(i < bindings.length) { @@ -505,7 +560,8 @@ class Evaluator(resolver: CachedResolver, arrF } - def visitMemberList(objPos: Position, e: ObjBody.MemberList, sup: Val.Obj)(implicit scope: ValScope): Val.Obj = { + @inline + private final def visitMemberList(objPos: Position, e: ObjBody.MemberList, sup: Val.Obj)(implicit scope: ValScope): Val.Obj = { val asserts = e.asserts val fields = e.fields var cachedSimpleScope: ValScope = null.asInstanceOf[ValScope] @@ -609,7 +665,8 @@ class Evaluator(resolver: CachedResolver, cachedObj } - def visitObjComp(e: ObjBody.ObjComp, sup: Val.Obj)(implicit scope: ValScope): Val.Obj = { + @inline + private final def visitObjComp(e: ObjBody.ObjComp, sup: Val.Obj)(implicit scope: ValScope): Val.Obj = { val binds = e.preLocals ++ e.postLocals val compScope: ValScope = scope //.clearSuper @@ -642,7 +699,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