Skip to content

Commit

Permalink
tailstrict
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenamar-db committed Jan 4, 2025
1 parent 6aa8e1b commit b9b25c8
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 87 deletions.
2 changes: 1 addition & 1 deletion build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion sjsonnet/src-jvm-native/sjsonnet/SjsonnetMain.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
124 changes: 91 additions & 33 deletions sjsonnet/src/sjsonnet/Evaluator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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) =
Expand Down Expand Up @@ -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) =>
Expand All @@ -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,
Expand All @@ -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{
Expand All @@ -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 =>
Expand All @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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{
Expand All @@ -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) {
Expand All @@ -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]
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand Down
10 changes: 5 additions & 5 deletions sjsonnet/src/sjsonnet/Expr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
}
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) =>
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)
Expand Down
2 changes: 1 addition & 1 deletion sjsonnet/src/sjsonnet/Interpreter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions sjsonnet/src/sjsonnet/Parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit b9b25c8

Please sign in to comment.