Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 59 additions & 21 deletions sjsonnet/src/sjsonnet/Evaluator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -501,11 +501,18 @@ class Evaluator(
checkStackDepth(e.pos, e)
try {
val lhs = visitExpr(e.value)
// Auto-TCO'd calls should normally be intercepted by visitExprWithTailCallSupport,
// but we handle them defensively here to preserve lazy semantics if this path is ever reached.
implicit val tailstrictMode: TailstrictMode =
if (e.tailstrict) TailstrictModeEnabled else TailstrictModeDisabled
if (e.isAutoTCO) TailstrictModeAutoTCO
else if (e.tailstrict) TailstrictModeEnabled
else TailstrictModeDisabled

if (e.tailstrict) {
TailCall.resolve(lhs.cast[Val.Func].apply(e.args.map(visitExpr(_)), e.namedNames, e.pos))
val args: Array[Eval] =
if (e.isAutoTCO) e.args.map(visitAsLazy(_))
else e.args.map(visitExpr(_)).asInstanceOf[Array[Eval]]
TailCall.resolve(lhs.cast[Val.Func].apply(args, e.namedNames, e.pos))
} else {
lhs.cast[Val.Func].apply(e.args.map(visitAsLazy(_)), e.namedNames, e.pos)
}
Expand All @@ -518,7 +525,9 @@ class Evaluator(
try {
val lhs = visitExpr(e.value)
implicit val tailstrictMode: TailstrictMode =
if (e.tailstrict) TailstrictModeEnabled else TailstrictModeDisabled
if (e.isAutoTCO) TailstrictModeAutoTCO
else if (e.tailstrict) TailstrictModeEnabled
else TailstrictModeDisabled
if (e.tailstrict) {
TailCall.resolve(lhs.cast[Val.Func].apply0(e.pos))
} else {
Expand All @@ -533,9 +542,12 @@ class Evaluator(
try {
val lhs = visitExpr(e.value)
implicit val tailstrictMode: TailstrictMode =
if (e.tailstrict) TailstrictModeEnabled else TailstrictModeDisabled
if (e.isAutoTCO) TailstrictModeAutoTCO
else if (e.tailstrict) TailstrictModeEnabled
else TailstrictModeDisabled
if (e.tailstrict) {
TailCall.resolve(lhs.cast[Val.Func].apply1(visitExpr(e.a1), e.pos))
val arg: Eval = if (e.isAutoTCO) visitAsLazy(e.a1) else visitExpr(e.a1)
TailCall.resolve(lhs.cast[Val.Func].apply1(arg, e.pos))
} else {
val l1 = visitAsLazy(e.a1)
lhs.cast[Val.Func].apply1(l1, e.pos)
Expand All @@ -549,10 +561,18 @@ class Evaluator(
try {
val lhs = visitExpr(e.value)
implicit val tailstrictMode: TailstrictMode =
if (e.tailstrict) TailstrictModeEnabled else TailstrictModeDisabled
if (e.isAutoTCO) TailstrictModeAutoTCO
else if (e.tailstrict) TailstrictModeEnabled
else TailstrictModeDisabled

if (e.tailstrict) {
TailCall.resolve(lhs.cast[Val.Func].apply2(visitExpr(e.a1), visitExpr(e.a2), e.pos))
if (e.isAutoTCO) {
TailCall.resolve(
lhs.cast[Val.Func].apply2(visitAsLazy(e.a1), visitAsLazy(e.a2), e.pos)
)
} else {
TailCall.resolve(lhs.cast[Val.Func].apply2(visitExpr(e.a1), visitExpr(e.a2), e.pos))
}
} else {
val l1 = visitAsLazy(e.a1)
val l2 = visitAsLazy(e.a2)
Expand All @@ -567,12 +587,22 @@ class Evaluator(
try {
val lhs = visitExpr(e.value)
implicit val tailstrictMode: TailstrictMode =
if (e.tailstrict) TailstrictModeEnabled else TailstrictModeDisabled
if (e.isAutoTCO) TailstrictModeAutoTCO
else if (e.tailstrict) TailstrictModeEnabled
else TailstrictModeDisabled

if (e.tailstrict) {
TailCall.resolve(
lhs.cast[Val.Func].apply3(visitExpr(e.a1), visitExpr(e.a2), visitExpr(e.a3), e.pos)
)
if (e.isAutoTCO) {
TailCall.resolve(
lhs
.cast[Val.Func]
.apply3(visitAsLazy(e.a1), visitAsLazy(e.a2), visitAsLazy(e.a3), e.pos)
)
} else {
TailCall.resolve(
lhs.cast[Val.Func].apply3(visitExpr(e.a1), visitExpr(e.a2), visitExpr(e.a3), e.pos)
)
}
} else {
val l1 = visitAsLazy(e.a1)
val l2 = visitAsLazy(e.a2)
Expand Down Expand Up @@ -1104,32 +1134,40 @@ class Evaluator(
case e: Apply =>
try {
val func = visitExpr(e.value).cast[Val.Func]
new TailCall(func, e.args.map(visitExpr(_)).asInstanceOf[Array[Eval]], e.namedNames, e)
val isAuto = e.isAutoTCO
val args: Array[Eval] =
if (isAuto) e.args.map(visitAsLazy(_))
else e.args.map(visitExpr(_)).asInstanceOf[Array[Eval]]
new TailCall(func, args, e.namedNames, e, autoTCO = isAuto)
} catch Error.withStackFrame(e)
case e: Apply0 =>
try {
val func = visitExpr(e.value).cast[Val.Func]
new TailCall(func, Evaluator.emptyLazyArray, null, e)
new TailCall(func, Evaluator.emptyLazyArray, null, e, autoTCO = e.isAutoTCO)
} catch Error.withStackFrame(e)
case e: Apply1 =>
try {
val func = visitExpr(e.value).cast[Val.Func]
new TailCall(func, Array[Eval](visitExpr(e.a1)), null, e)
val isAuto = e.isAutoTCO
val arg: Eval = if (isAuto) visitAsLazy(e.a1) else visitExpr(e.a1)
new TailCall(func, Array[Eval](arg), null, e, autoTCO = isAuto)
} catch Error.withStackFrame(e)
case e: Apply2 =>
try {
val func = visitExpr(e.value).cast[Val.Func]
new TailCall(func, Array[Eval](visitExpr(e.a1), visitExpr(e.a2)), null, e)
val isAuto = e.isAutoTCO
val a1: Eval = if (isAuto) visitAsLazy(e.a1) else visitExpr(e.a1)
val a2: Eval = if (isAuto) visitAsLazy(e.a2) else visitExpr(e.a2)
new TailCall(func, Array[Eval](a1, a2), null, e, autoTCO = isAuto)
} catch Error.withStackFrame(e)
case e: Apply3 =>
try {
val func = visitExpr(e.value).cast[Val.Func]
new TailCall(
func,
Array[Eval](visitExpr(e.a1), visitExpr(e.a2), visitExpr(e.a3)),
null,
e
)
val isAuto = e.isAutoTCO
val a1: Eval = if (isAuto) visitAsLazy(e.a1) else visitExpr(e.a1)
val a2: Eval = if (isAuto) visitAsLazy(e.a2) else visitExpr(e.a2)
val a3: Eval = if (isAuto) visitAsLazy(e.a3) else visitExpr(e.a3)
new TailCall(func, Array[Eval](a1, a2, a3), null, e, autoTCO = isAuto)
} catch Error.withStackFrame(e)
case _ => visitExpr(e)
}
Expand Down
40 changes: 35 additions & 5 deletions sjsonnet/src/sjsonnet/Expr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ trait Expr {
*/
trait TailstrictableExpr extends Expr {
def tailstrict: Boolean

/**
* True when this call was auto-TCO'd by the StaticOptimizer (as opposed to an explicit
* `tailstrict` annotation by the user). Auto-TCO'd calls use lazy argument evaluation to preserve
* Jsonnet's standard lazy semantics, whereas explicit `tailstrict` forces eager argument
* evaluation per the Jsonnet spec.
*/
def isAutoTCO: Boolean = false
}

object Expr {
Expand Down Expand Up @@ -231,36 +239,58 @@ object Expr {
value: Expr,
args: Array[Expr],
namedNames: Array[String],
tailstrict: Boolean)
tailstrict: Boolean,
autoTCO: Boolean = false)
extends TailstrictableExpr {
final override private[sjsonnet] def tag = ExprTags.Apply
override def exprErrorString: String = Expr.callTargetName(value)
override def isAutoTCO: Boolean = autoTCO
}
final case class Apply0(var pos: Position, value: Expr, tailstrict: Boolean)
final case class Apply0(
var pos: Position,
value: Expr,
tailstrict: Boolean,
autoTCO: Boolean = false)
extends TailstrictableExpr {
final override private[sjsonnet] def tag = ExprTags.Apply0
override def exprErrorString: String = Expr.callTargetName(value)
override def isAutoTCO: Boolean = autoTCO
}
final case class Apply1(var pos: Position, value: Expr, a1: Expr, tailstrict: Boolean)
final case class Apply1(
var pos: Position,
value: Expr,
a1: Expr,
tailstrict: Boolean,
autoTCO: Boolean = false)
extends TailstrictableExpr {
final override private[sjsonnet] def tag = ExprTags.Apply1
override def exprErrorString: String = Expr.callTargetName(value)
override def isAutoTCO: Boolean = autoTCO
}
final case class Apply2(var pos: Position, value: Expr, a1: Expr, a2: Expr, tailstrict: Boolean)
final case class Apply2(
var pos: Position,
value: Expr,
a1: Expr,
a2: Expr,
tailstrict: Boolean,
autoTCO: Boolean = false)
extends TailstrictableExpr {
final override private[sjsonnet] def tag = ExprTags.Apply2
override def exprErrorString: String = Expr.callTargetName(value)
override def isAutoTCO: Boolean = autoTCO
}
final case class Apply3(
var pos: Position,
value: Expr,
a1: Expr,
a2: Expr,
a3: Expr,
tailstrict: Boolean)
tailstrict: Boolean,
autoTCO: Boolean = false)
extends TailstrictableExpr {
final override private[sjsonnet] def tag = ExprTags.Apply3
override def exprErrorString: String = Expr.callTargetName(value)
override def isAutoTCO: Boolean = autoTCO
}
final case class ApplyBuiltin(
var pos: Position,
Expand Down
20 changes: 10 additions & 10 deletions sjsonnet/src/sjsonnet/ExprTransform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,37 +14,37 @@ abstract class ExprTransform {
if (x2 eq x) expr
else Select(pos, x2, name)

case Apply(pos, x, y, namedNames, tailstrict) =>
case Apply(pos, x, y, namedNames, tailstrict, autoTCO) =>
val x2 = transform(x)
val y2 = transformArr(y)
if ((x2 eq x) && (y2 eq y)) expr
else Apply(pos, x2, y2, namedNames, tailstrict)
else Apply(pos, x2, y2, namedNames, tailstrict, autoTCO)

case Apply0(pos, x, tailstrict) =>
case Apply0(pos, x, tailstrict, autoTCO) =>
val x2 = transform(x)
if (x2 eq x) expr
else Apply0(pos, x2, tailstrict)
else Apply0(pos, x2, tailstrict, autoTCO)

case Apply1(pos, x, y, tailstrict) =>
case Apply1(pos, x, y, tailstrict, autoTCO) =>
val x2 = transform(x)
val y2 = transform(y)
if ((x2 eq x) && (y2 eq y)) expr
else Apply1(pos, x2, y2, tailstrict)
else Apply1(pos, x2, y2, tailstrict, autoTCO)

case Apply2(pos, x, y, z, tailstrict) =>
case Apply2(pos, x, y, z, tailstrict, autoTCO) =>
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, tailstrict)
else Apply2(pos, x2, y2, z2, tailstrict, autoTCO)

case Apply3(pos, x, y, z, a, tailstrict) =>
case Apply3(pos, x, y, z, a, tailstrict, autoTCO) =>
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, tailstrict)
else Apply3(pos, x2, y2, z2, a2, tailstrict, autoTCO)

case ApplyBuiltin(pos, func, x, tailstrict) =>
val x2 = transformArr(x)
Expand Down
Loading