Skip to content
Closed
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
2 changes: 1 addition & 1 deletion sjsonnet/src/sjsonnet/Evaluator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1103,7 +1103,7 @@ class Evaluator(
}

def visitObjComp(e: ObjBody.ObjComp, sup: Val.Obj)(implicit scope: ValScope): Val.Obj = {
val binds = e.preLocals ++ e.postLocals
val binds = e.allLocals
val compScope: ValScope = scope // .clearSuper
val builder = new java.util.LinkedHashMap[String, Val.Obj.Member]
val compScopes = visitComp(e.first :: e.rest, Array(compScope))
Expand Down
2 changes: 2 additions & 0 deletions sjsonnet/src/sjsonnet/Expr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,8 @@ object Expr {
override def exprErrorString: String = "object comprehension"
override def toString: String =
s"ObjComp($pos, ${arrStr(preLocals)}, $key, $value, ${arrStr(postLocals)}, $first, $rest)"
// Cache concatenated locals to avoid repeated array allocation in visitObjComp
lazy val allLocals: Array[Bind] = preLocals ++ postLocals
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion sjsonnet/src/sjsonnet/Val.scala
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,11 @@ object Val {
buf.sizeHint(value0.size())
value0.forEach((k, m) => if (m.visibility != Visibility.Hidden) buf += k)
} else {
getAllKeys.forEach((k, b) => if (b == java.lang.Boolean.FALSE) buf += k)
val iter = getAllKeys.entrySet().iterator()
while (iter.hasNext()) {
val e = iter.next()
if (e.getValue() == java.lang.Boolean.FALSE) buf += e.getKey()
}
}
buf.result()
}
Expand Down
44 changes: 29 additions & 15 deletions sjsonnet/src/sjsonnet/stdlib/ArrayModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -487,35 +487,49 @@ object ArrayModule extends AbstractFunctionModule {
if (idx == -1) {
arr
} else {
Val.Arr(
arr.pos,
arr.asLazyArray.slice(0, idx) ++ arr.asLazyArray.slice(idx + 1, arr.length)
)
val src = arr.asLazyArray
val result = new Array[Eval](src.length - 1)
System.arraycopy(src, 0, result, 0, idx)
System.arraycopy(src, idx + 1, result, idx, src.length - idx - 1)
Val.Arr(arr.pos, result)
}
},
builtin("removeAt", "arr", "idx") { (_, _, arr: Val.Arr, idx: Int) =>
if (!(0 <= idx && idx < arr.length)) {
Error.fail("index out of bounds: 0 <= " + idx + " < " + arr.length)
}
Val.Arr(
arr.pos,
arr.asLazyArray.slice(0, idx) ++ arr.asLazyArray.slice(idx + 1, arr.length)
)
val src = arr.asLazyArray
val result = new Array[Eval](src.length - 1)
System.arraycopy(src, 0, result, 0, idx)
System.arraycopy(src, idx + 1, result, idx, src.length - idx - 1)
Val.Arr(arr.pos, result)
},
builtin("sum", "arr") { (_, _, arr: Val.Arr) =>
if (!arr.forall(_.isInstanceOf[Val.Num])) {
Error.fail("Argument must be an array of numbers")
val a = arr.asLazyArray
var sum = 0.0
var i = 0
while (i < a.length) {
val v = a(i).value
if (!v.isInstanceOf[Val.Num]) Error.fail("Argument must be an array of numbers")
sum += v.asInstanceOf[Val.Num].asDouble
i += 1
}
arr.asLazyArray.map(_.value.asDouble).sum
sum
},
builtin("avg", "arr") { (_, _, arr: Val.Arr) =>
if (!arr.forall(_.isInstanceOf[Val.Num])) {
Error.fail("Argument must be an array of numbers")
}
if (arr.length == 0) {
Error.fail("Cannot calculate average of an empty array")
}
arr.asLazyArray.map(_.value.asDouble).sum / arr.length
val a = arr.asLazyArray
var sum = 0.0
var i = 0
while (i < a.length) {
val v = a(i).value
if (!v.isInstanceOf[Val.Num]) Error.fail("Argument must be an array of numbers")
sum += v.asInstanceOf[Val.Num].asDouble
i += 1
}
sum / arr.length
}
)
}
10 changes: 9 additions & 1 deletion sjsonnet/src/sjsonnet/stdlib/EncodingModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,15 @@ object EncodingModule extends AbstractFunctionModule {
},
builtin("base64DecodeBytes", "str") { (pos, _, str: String) =>
try {
Val.Arr(pos, Base64.getDecoder.decode(str).map(i => Val.Num(pos, i)))
val decoded = Base64.getDecoder.decode(str)
val result = new Array[Eval](decoded.length)
var i = 0
while (i < decoded.length) {
// Mask with 0xff to convert signed byte to unsigned int (0-255)
result(i) = Val.Num(pos, decoded(i) & 0xff)
i += 1
}
Val.Arr(pos, result)
} catch {
case e: IllegalArgumentException =>
Error.fail("Invalid base64 string: " + e.getMessage)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Regression test: base64DecodeBytes must return unsigned byte values (0-255)
// Bytes >= 128 must NOT be sign-extended to negative numbers.
local encoded = std.base64([255, 128, 1, 0]);
local decoded = std.base64DecodeBytes(encoded);
std.assertEqual(decoded, [255, 128, 1, 0])
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
true