Skip to content
Open
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
33 changes: 33 additions & 0 deletions sjsonnet/src/sjsonnet/Util.scala
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,39 @@ object Util {
override def compare(x: String, y: String): Int = compareStringsByCodepoint(x, y)
}

def compareJsonnetStd(v1: Val, v2: Val, ev: EvalScope): Int = {
val t1 = v1.prettyName
val t2 = v2.prettyName
if (t1 != t2) {
Error.fail("Comparison requires matching types. Got " + t1 + " and " + t2)
}

v1 match {
case arr1: Val.Arr =>
compareJsonnetStdArrays(arr1, v2.asInstanceOf[Val.Arr], ev)
case _: Val.Func | _: Val.Obj | _: Val.Bool =>
Error.fail("Values of type " + t1 + " are not comparable.")
case _: Val.Null =>
Error.fail("binary operator < does not operate on null.")
case _ =>
val cmp = ev.compare(v1, v2)
if (cmp < 0) -1 else if (cmp > 0) 1 else 0
}
}

def compareJsonnetStdArrays(arr1: Val.Arr, arr2: Val.Arr, ev: EvalScope): Int = {
val len1 = arr1.length
val len2 = arr2.length
val minLen = math.min(len1, len2)
var i = 0
while (i < minLen) {
val cmp = compareJsonnetStd(arr1.eval(i).value, arr2.eval(i).value, ev)
if (cmp != 0) return cmp
i += 1
}
java.lang.Integer.compare(len1, len2)
}

val isWindows: Boolean = {
// This is normally non-null on the JVM, but it might be null in ScalaJS hence the Option:
Option(System.getProperty("os.name")).exists(_.toLowerCase.startsWith("windows"))
Expand Down
91 changes: 57 additions & 34 deletions sjsonnet/src/sjsonnet/stdlib/ArrayModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,43 @@ import scala.collection.mutable
object ArrayModule extends AbstractFunctionModule {
def name = "array"

private val DefaultKeyF = Val.Null(dummyPos)
private val DefaultOnEmpty = Val.Null(dummyPos)

@inline private def isDefaultKeyF(v: Val): Boolean = v.asInstanceOf[AnyRef] eq DefaultKeyF
@inline private def isDefaultOnEmpty(v: Val): Boolean =
v.asInstanceOf[AnyRef] eq DefaultOnEmpty

private def applyArrayKey(keyF: Val, elem: Val, pos: Position, ev: EvalScope): Val =
if (isDefaultKeyF(keyF)) elem
else keyF.asFunc.apply1(elem, pos.noOffset)(ev, TailstrictModeDisabled)

private object MinArray
extends Val.Builtin(
"minArray",
Array("arr", "keyF", "onEmpty"),
Array(null, Val.False(dummyPos), Val.False(dummyPos))
Array(null, DefaultKeyF, DefaultOnEmpty)
) {
override def evalRhs(args: Array[? <: Eval], ev: EvalScope, pos: Position): Val = {
val arr = args(0).value.asArr
val keyF = args(1).value
val onEmpty = args(2)
val onEmpty = args(2).value
if (arr.length == 0) {
if (onEmpty.value.isInstanceOf[Val.False]) {
if (isDefaultOnEmpty(onEmpty)) {
Error.fail("Expected at least one element in array. Got none")
} else {
onEmpty.value
onEmpty
}
} else if (keyF.isInstanceOf[Val.False]) {
arr.asStrictArray.min(ev)
} else {
val strict = arr.asStrictArray
val func = keyF.asInstanceOf[Val.Func]
var bestIdx = 0
var bestVal = func.apply1(strict(0), pos.fileScope.noOffsetPos)(ev, TailstrictModeDisabled)
var bestKey = applyArrayKey(keyF, strict(0), pos, ev)
Util.compareJsonnetStd(bestKey, bestKey, ev)
var i = 1
while (i < strict.length) {
val v = func.apply1(strict(i), pos.fileScope.noOffsetPos)(ev, TailstrictModeDisabled)
if (ev.compare(v, bestVal) < 0) {
bestVal = v
val v = applyArrayKey(keyF, strict(i), pos, ev)
if (Util.compareJsonnetStd(v, bestKey, ev) < 0) {
bestKey = v
bestIdx = i
}
i += 1
Expand All @@ -49,30 +58,28 @@ object ArrayModule extends AbstractFunctionModule {
extends Val.Builtin(
"maxArray",
Array("arr", "keyF", "onEmpty"),
Array(null, Val.False(dummyPos), Val.False(dummyPos))
Array(null, DefaultKeyF, DefaultOnEmpty)
) {
override def evalRhs(args: Array[? <: Eval], ev: EvalScope, pos: Position): Val = {
val arr = args(0).value.asArr
val keyF = args(1).value
val onEmpty = args(2)
val onEmpty = args(2).value
if (arr.length == 0) {
if (onEmpty.value.isInstanceOf[Val.False]) {
if (isDefaultOnEmpty(onEmpty)) {
Error.fail("Expected at least one element in array. Got none")
} else {
onEmpty.value
onEmpty
}
} else if (keyF.isInstanceOf[Val.False]) {
arr.asStrictArray.max(ev)
} else {
val strict = arr.asStrictArray
val func = keyF.asInstanceOf[Val.Func]
var bestIdx = 0
var bestVal = func.apply1(strict(0), pos.fileScope.noOffsetPos)(ev, TailstrictModeDisabled)
var bestKey = applyArrayKey(keyF, strict(0), pos, ev)
Util.compareJsonnetStd(bestKey, bestKey, ev)
var i = 1
while (i < strict.length) {
val v = func.apply1(strict(i), pos.fileScope.noOffsetPos)(ev, TailstrictModeDisabled)
if (ev.compare(v, bestVal) > 0) {
bestVal = v
val v = applyArrayKey(keyF, strict(i), pos, ev)
if (Util.compareJsonnetStd(v, bestKey, ev) > 0) {
bestKey = v
bestIdx = i
}
i += 1
Expand Down Expand Up @@ -205,7 +212,10 @@ object ArrayModule extends AbstractFunctionModule {
private object MapWithIndex extends Val.Builtin2("mapWithIndex", "func", "arr") {
def evalRhs(_func: Eval, _arr: Eval, ev: EvalScope, pos: Position): Val = {
val func = _func.value.asFunc
val arr = _arr.value.asArr.asLazyArray
val arr = _arr.value match {
case Val.Str(_, str) => stringChars(pos, str).asLazyArray
case v => v.asArr.asLazyArray
}
val a = new Array[Eval](arr.length)
val noOff = pos.noOffset
var i = 0
Expand Down Expand Up @@ -243,9 +253,9 @@ object ArrayModule extends AbstractFunctionModule {
out.sizeHint(arr.length * 4) // Rough size hint
for (x <- arr) {
x.value match {
case Val.Null(_) => // do nothing
case v: Val.Arr => out ++= v.asLazyArray
case x => Error.fail("Cannot call flattenArrays on " + x)
case v: Val.Arr => out ++= v.asLazyArray
case x =>
Error.fail("binary operator + requires matching types, got array and " + x.prettyName)
}
}
Val.Arr(pos, out.result())
Expand All @@ -254,7 +264,11 @@ object ArrayModule extends AbstractFunctionModule {

private object FlattenDeepArrays extends Val.Builtin1("flattenDeepArray", "value") {
def evalRhs(value: Eval, ev: EvalScope, pos: Position): Val = {
val lazyArray = value.value.asArr.asLazyArray
val value0 = value.value
if (!value0.isInstanceOf[Val.Arr]) {
return Val.Arr(pos, Array[Eval](value0))
}
val lazyArray = value0.asInstanceOf[Val.Arr].asLazyArray
val out = new mutable.ArrayBuilder.ofRef[Eval]
out.sizeHint(lazyArray.length)
val q = new java.util.ArrayDeque[Eval](lazyArray.length)
Expand Down Expand Up @@ -653,6 +667,9 @@ object ArrayModule extends AbstractFunctionModule {
Val.Arr(pos, b.result())
},
builtin("repeat", "what", "count") { (pos, ev, what: Val, count: Int) =>
if (count < 0) {
Error.fail("makeArray requires size >= 0, got " + count)
}
val res: Val = what match {
case Val.Str(_, str) =>
val builder = new StringBuilder(str.length * count)
Expand Down Expand Up @@ -726,14 +743,20 @@ object ArrayModule extends AbstractFunctionModule {
)
}
},
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)
builtin("removeAt", "arr", "idx") { (_, _, arr: Val.Arr, idx: Val) =>
val removeIdx = idx match {
case n: Val.Num =>
val d = n.asDouble
if (d.isWhole && d >= 0 && d < arr.length) d.toInt else -1
case _ => -1
}
if (removeIdx == -1) arr
else {
Val.Arr(
arr.pos,
arr.asLazyArray.slice(0, removeIdx) ++ arr.asLazyArray.slice(removeIdx + 1, arr.length)
)
}
Val.Arr(
arr.pos,
arr.asLazyArray.slice(0, idx) ++ arr.asLazyArray.slice(idx + 1, arr.length)
)
},
builtin("sum", "arr") { (_, _, arr: Val.Arr) =>
val a = arr.asLazyArray
Expand Down
32 changes: 17 additions & 15 deletions sjsonnet/src/sjsonnet/stdlib/SetModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,19 @@ import scala.collection.Searching.*
object SetModule extends AbstractFunctionModule {
def name = "set"

private object Set_ extends Val.Builtin2("set", "arr", "keyF", Array(null, Val.False(dummyPos))) {
private val DefaultKeyF = Val.Null(dummyPos)

@inline private def isDefaultKeyF(v: Val): Boolean = v.asInstanceOf[AnyRef] eq DefaultKeyF

private object Set_ extends Val.Builtin2("set", "arr", "keyF", Array(null, DefaultKeyF)) {
def evalRhs(arr: Eval, keyF: Eval, ev: EvalScope, pos: Position): Val = {
uniqArr(pos, ev, sortArr(pos, ev, arr.value, keyF.value), keyF.value)
}
}

private def applyKeyFunc(elem: Val, keyF: Val, pos: Position, ev: EvalScope): Val = {
keyF match {
case keyFFunc: Val.Func =>
keyFFunc.apply1(elem, pos.noOffset)(ev, TailstrictModeDisabled).value
case _ => elem
}
if (isDefaultKeyF(keyF)) elem
else keyF.asFunc.apply1(elem, pos.noOffset)(ev, TailstrictModeDisabled).value
}

private def toArrOrString(arg: Val, pos: Position, ev: EvalScope) = {
Expand All @@ -46,6 +47,7 @@ object SetModule extends AbstractFunctionModule {
keyF: Val,
arr: mutable.IndexedSeq[? <: Eval],
toFind: Val): Boolean = {
if (arr.isEmpty) return false
val appliedX = applyKeyFunc(toFind, keyF, pos, ev)
arr
.search(appliedX)((toFind: Eval, value: Eval) => {
Expand All @@ -70,8 +72,8 @@ object SetModule extends AbstractFunctionModule {
while (i < arrValue.length) {
val v = arrValue(i)
val vKey =
if (keyF.isInstanceOf[Val.False]) v.value
else keyF.asInstanceOf[Val.Func].apply1(v, pos.noOffset)(ev, TailstrictModeDisabled)
if (isDefaultKeyF(keyF)) v.value
else keyF.asFunc.apply1(v, pos.noOffset)(ev, TailstrictModeDisabled)
if (lastAddedKey == null || !ev.equal(vKey, lastAddedKey)) {
out.+=(v)
lastAddedKey = vKey
Expand All @@ -88,7 +90,7 @@ object SetModule extends AbstractFunctionModule {
arr
} else {
val keyFFunc =
if (keyF == null || keyF.isInstanceOf[Val.False]) null else keyF.asInstanceOf[Val.Func]
if (keyF == null || isDefaultKeyF(keyF)) null else keyF.asFunc
Val.Arr(
pos,
if (keyFFunc != null) {
Expand Down Expand Up @@ -211,13 +213,13 @@ object SetModule extends AbstractFunctionModule {
(pos, ev, indexable: Val, index: Option[Int], _end: Option[Int], _step: Option[Int]) =>
Util.slice(pos, ev, indexable, index, _end, _step)
},
builtinWithDefaults("uniq", "arr" -> null, "keyF" -> Val.False(dummyPos)) { (args, pos, ev) =>
builtinWithDefaults("uniq", "arr" -> null, "keyF" -> DefaultKeyF) { (args, pos, ev) =>
uniqArr(pos, ev, args(0), args(1))
},
builtinWithDefaults("sort", "arr" -> null, "keyF" -> Val.False(dummyPos)) { (args, pos, ev) =>
builtinWithDefaults("sort", "arr" -> null, "keyF" -> DefaultKeyF) { (args, pos, ev) =>
sortArr(pos, ev, args(0), args(1))
},
builtinWithDefaults("setUnion", "a" -> null, "b" -> null, "keyF" -> Val.False(dummyPos)) {
builtinWithDefaults("setUnion", "a" -> null, "b" -> null, "keyF" -> DefaultKeyF) {
(args, pos, ev) =>
val keyF = args(2)
validateSet(ev, pos, keyF, args(0))
Expand Down Expand Up @@ -274,7 +276,7 @@ object SetModule extends AbstractFunctionModule {
Val.Arr(pos, out.result())
}
},
builtinWithDefaults("setInter", "a" -> null, "b" -> null, "keyF" -> Val.False(dummyPos)) {
builtinWithDefaults("setInter", "a" -> null, "b" -> null, "keyF" -> DefaultKeyF) {
(args, pos, ev) =>
val keyF = args(2)
validateSet(ev, pos, keyF, args(0))
Expand Down Expand Up @@ -314,7 +316,7 @@ object SetModule extends AbstractFunctionModule {

Val.Arr(pos, out.result())
},
builtinWithDefaults("setDiff", "a" -> null, "b" -> null, "keyF" -> Val.False(dummyPos)) {
builtinWithDefaults("setDiff", "a" -> null, "b" -> null, "keyF" -> DefaultKeyF) {
(args, pos, ev) =>
val keyF = args(2)
validateSet(ev, pos, keyF, args(0))
Expand Down Expand Up @@ -362,7 +364,7 @@ object SetModule extends AbstractFunctionModule {

Val.Arr(pos, out.result())
},
builtinWithDefaults("setMember", "x" -> null, "arr" -> null, "keyF" -> Val.False(dummyPos)) {
builtinWithDefaults("setMember", "x" -> null, "arr" -> null, "keyF" -> DefaultKeyF) {
(args, pos, ev) =>
val keyF = args(2)
validateSet(ev, pos, keyF, args(1))
Expand Down
Loading
Loading