Skip to content

Commit

Permalink
Implement final set of missing methods, except the TOML one. (#229)
Browse files Browse the repository at this point in the history
This PR implements all the missing std functions, even the undocumented
ones, except the ManifestTOML ones.
  • Loading branch information
stephenamar-db authored Dec 10, 2024
1 parent 6e03591 commit 09a4fd2
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 129 deletions.
153 changes: 66 additions & 87 deletions sjsonnet/src/sjsonnet/Std.scala
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,37 @@ class Std {
new Val.Arr(pos, out.result())
}
}

private object FlattenDeepArrays extends Val.Builtin1("flattenDeepArray", "value") {
def evalRhs(value: Val, ev: EvalScope, pos: Position): Val = {
val out = new mutable.ArrayBuilder.ofRef[Lazy]
val q = new java.util.ArrayDeque[Lazy]()
value.asArr.asLazyArray.foreach(q.add)
while (!q.isEmpty) {
q.removeFirst().force match {
case v: Val.Arr => v.asLazyArray.reverseIterator.foreach(q.push)
case x => out += x
}
}
new Val.Arr(pos, out.result())
}
}

private object DeepJoin extends Val.Builtin1("deepJoin", "arr") {
def evalRhs(value: Val, ev: EvalScope, pos: Position): Val = {
val out = new StringWriter()
val q = new java.util.ArrayDeque[Lazy]()
q.add(value)
while (!q.isEmpty) {
q.removeFirst().force match {
case v: Val.Arr => v.asLazyArray.reverseIterator.foreach(q.push)
case s: Val.Str => out.write(s.value)
}
}
Val.Str(pos, out.toString)
}
}

private object Reverse extends Val.Builtin1("reverse", "arrs") {
def evalRhs(arrs: Val, ev: EvalScope, pos: Position): Val = {
new Val.Arr(pos, arrs.asArr.asLazyArray.reverse)
Expand Down Expand Up @@ -702,92 +733,6 @@ class Std {
}
}

private object SetInter extends Val.Builtin3("setInter", "a", "b", "keyF", Array(null, null, Val.False(dummyPos))) {
private def isStr(a: Val.Arr) = a.forall(_.isInstanceOf[Val.Str])

override def specialize(args: Array[Expr]): (Val.Builtin, Array[Expr]) = args match {
case Array(a: Val.Arr, b) if isStr(a) => (new Spec1Str(a), Array(b))
case Array(a, b: Val.Arr) if isStr(b) => (new Spec1Str(b), Array(a))
case args if args.length == 2 => (Spec2, args)
case _ => null
}

def asArray(a: Val): Array[Lazy] = a match {
case arr: Val.Arr => arr.asLazyArray
case str: Val.Str => stringChars(pos, str.value).asLazyArray
case _ => Error.fail("Arguments must be either arrays or strings")
}

def evalRhs(_a: Val, _b: Val, _keyF: Val, ev: EvalScope, pos: Position): Val = {
if(_keyF.isInstanceOf[Val.False]) Spec2.evalRhs(_a, _b, ev, pos)
else {
val a = asArray(_a)
val b = asArray(_b)
val keyFFunc = _keyF.asInstanceOf[Val.Func]
val out = new mutable.ArrayBuffer[Lazy]
for (v <- a) {
val appliedX = keyFFunc.apply1(v, pos.noOffset)(ev)
if (b.exists(value => {
val appliedValue = keyFFunc.apply1(value, pos.noOffset)(ev)
ev.equal(appliedValue, appliedX)
}) && !out.exists(value => {
val mValue = keyFFunc.apply1(value, pos.noOffset)(ev)
ev.equal(mValue, appliedX)
})) {
out.append(v)
}
}
sortArr(pos, ev, new Val.Arr(pos, out.toArray), keyFFunc)
}
}

private object Spec2 extends Val.Builtin2("setInter", "a", "b") {
def evalRhs(_a: Val, _b: Val, ev: EvalScope, pos: Position): Val = {
val a = asArray(_a)
val b = asArray(_b)
val out = new mutable.ArrayBuffer[Lazy](a.length)
for (v <- a) {
val vf = v.force
if (b.exists(value => {
ev.equal(value.force, vf)
}) && !out.exists(value => {
ev.equal(value.force, vf)
})) {
out.append(v)
}
}
sortArr(pos, ev, new Val.Arr(pos, out.toArray), null)
}
}

private class Spec1Str(_a: Val.Arr) extends Val.Builtin1("setInter", "b") {
private[this] val a =
ArrayOps.sortInPlaceBy(ArrayOps.distinctBy(_a.asLazyArray)(_.asInstanceOf[Val.Str].value))(_.asInstanceOf[Val.Str].value)
// 2.13+: _a.asLazyArray.distinctBy(_.asInstanceOf[Val.Str].value).sortInPlaceBy(_.asInstanceOf[Val.Str].value)

def evalRhs(_b: Val, ev: EvalScope, pos: Position): Val = {
val b = asArray(_b)
val bs = new mutable.HashSet[String]
var i = 0
while(i < b.length) {
b(i).force match {
case s: Val.Str => bs.add(s.value)
case _ =>
}
i += 1
}
val out = new mutable.ArrayBuilder.ofRef[Lazy]
i = 0
while(i < a.length) {
val s = a(i).asInstanceOf[Val.Str]
if(bs.contains(s.value)) out.+=(s)
i += 1
}
new Val.Arr(pos, out.result())
}
}
}

val functions: Map[String, Val.Func] = Map(
builtin(AssertEqual),
builtin(ToString),
Expand Down Expand Up @@ -889,6 +834,9 @@ class Std {
builtin("floor", "x"){ (pos, ev, x: Double) =>
math.floor(x)
},
builtin("round", "x") { (pos, ev, x: Double) =>
math.round(x)
},
builtin("ceil", "x"){ (pos, ev, x: Double) =>
math.ceil(x)
},
Expand All @@ -904,7 +852,18 @@ class Std {
builtin("tan", "x"){ (pos, ev, x: Double) =>
math.tan(x)
},

builtin("isEven", "x"){ (_, _, x: Double) =>
math.round(x) % 2 == 0
},
builtin("isInteger", "x"){ (_, _, x: Double) =>
math.round(x).toDouble == x
},
builtin("isOdd", "x"){ (_, _, x: Double) =>
math.round(x) % 2 != 0
},
builtin("isDecimal", "x"){ (_, _, x: Double) =>
math.round(x).toDouble != x
},
builtin("asin", "x"){ (pos, ev, x: Double) =>
math.asin(x)
},
Expand Down Expand Up @@ -1040,6 +999,8 @@ class Std {
},

builtin(FlattenArrays),
builtin(FlattenDeepArrays),
builtin(DeepJoin),
builtin(Reverse),

builtin("manifestIni", "v"){ (pos, ev, v: Val) =>
Expand Down Expand Up @@ -1068,6 +1029,21 @@ class Std {
BaseRenderer.escape(out, str, unicode = true)
out.toString
},

builtin("escapeStringXML", "str"){ (_, _, str: String) =>
val out = new StringWriter()
for (c <- str) {
c match {
case '<' => out.write("&lt;")
case '>' => out.write("&gt;")
case '&' => out.write("&amp;")
case '"' => out.write("&quot;")
case '\'' => out.write("&apos;")
case _ => out.write(c)
}
}
out.toString
},
builtin("escapeStringBash", "str"){ (pos, ev, str: String) =>
"'" + str.replace("'", """'"'"'""") + "'"
},
Expand Down Expand Up @@ -1386,6 +1362,9 @@ class Std {
},
builtin(MinArray),
builtin(MaxArray),
builtin("primitiveEquals", "x", "y") { (_, ev, x: Val, y: Val) =>
x.isInstanceOf[y.type] && ev.compare(x, y) == 0
}
)

private def toSetArrOrString(args: Array[Val], idx: Int, pos: Position, ev: EvalScope) = {
Expand Down
42 changes: 21 additions & 21 deletions sjsonnet/test/resources/test_suite/stdlib.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -313,10 +313,10 @@ std.assertEqual(std.lines(['a', null, 'b']), 'a\nb\n') &&

std.assertEqual(std.flattenArrays([[1, 2, 3], [4, 5, 6], []]), [1, 2, 3, 4, 5, 6]) &&

//std.assertEqual(std.flattenDeepArray([]), []) &&
//std.assertEqual(std.flattenDeepArray([1, 2, 3]), [1, 2, 3]) &&
//std.assertEqual(std.flattenDeepArray([1, [2, 3]]), [1, 2, 3]) &&
//std.assertEqual(std.flattenDeepArray([[1], [2, 3], [[null]]]), [1, 2, 3, null]) &&
std.assertEqual(std.flattenDeepArray([]), []) &&
std.assertEqual(std.flattenDeepArray([1, 2, 3]), [1, 2, 3]) &&
std.assertEqual(std.flattenDeepArray([1, [2, 3]]), [1, 2, 3]) &&
std.assertEqual(std.flattenDeepArray([[1], [2, 3], [[null]]]), [1, 2, 3, null]) &&

std.assertEqual(
std.manifestIni({
Expand Down Expand Up @@ -356,11 +356,11 @@ std.assertEqual(std.escapeStringJson('he"llo'), '"he\\"llo"') &&
std.assertEqual(std.escapeStringJson('he"llo'), '"he\\"llo"') &&
std.assertEqual(std.escapeStringBash("he\"l'lo"), "'he\"l'\"'\"'lo'") &&
std.assertEqual(std.escapeStringDollars('The path is ${PATH}.'), 'The path is $${PATH}.') &&
//std.assertEqual(std.escapeStringXML('2 < 3'), '2 &lt; 3') &&
//std.assertEqual(std.escapeStringXML('3 > 2'), '3 &gt; 2') &&
//std.assertEqual(std.escapeStringXML('"foo"'), '&quot;foo&quot;') &&
//std.assertEqual(std.escapeStringXML("don't believe the hype"), 'don&apos;t believe the hype') &&
//std.assertEqual(std.escapeStringXML('PB&J'), 'PB&amp;J') &&
std.assertEqual(std.escapeStringXML('2 < 3'), '2 &lt; 3') &&
std.assertEqual(std.escapeStringXML('3 > 2'), '3 &gt; 2') &&
std.assertEqual(std.escapeStringXML('"foo"'), '&quot;foo&quot;') &&
std.assertEqual(std.escapeStringXML("don't believe the hype"), 'don&apos;t believe the hype') &&
std.assertEqual(std.escapeStringXML('PB&J'), 'PB&amp;J') &&
std.assertEqual(std.escapeStringJson('!~'), '"!~"') &&

std.assertEqual(std.manifestPython({
Expand Down Expand Up @@ -1520,8 +1520,8 @@ std.assertEqual(std.type(std.parseYaml('id: "12345"').id), 'string') &&
std.assertEqual(std.asciiUpper('!@#$%&*()asdfghFGHJKL09876 '), '!@#$%&*()ASDFGHFGHJKL09876 ') &&
std.assertEqual(std.asciiLower('!@#$%&*()asdfghFGHJKL09876 '), '!@#$%&*()asdfghfghjkl09876 ') &&
//std.assertEqual(std.deepJoin(['a', ['b', 'c', [[], 'd', ['e'], 'f', 'g'], [], []], 'h']),
// 'abcdefgh') &&
std.assertEqual(std.deepJoin(['a', ['b', 'c', [[], 'd', ['e'], 'f', 'g'], [], []], 'h']),
'abcdefgh') &&
std.assertEqual(std.findSubstr('', 'a'), []) &&
std.assertEqual(std.findSubstr('aa', ''), []) &&
Expand Down Expand Up @@ -1581,8 +1581,8 @@ std.assertEqual(std.xor(true, true), false) &&
std.assertEqual(std.xnor(true, false), false) &&
std.assertEqual(std.xnor(true, true), true) &&
//std.assertEqual(std.round(1.2), 1) &&
//std.assertEqual(std.round(1.5), 2) &&
std.assertEqual(std.round(1.2), 1) &&
std.assertEqual(std.round(1.5), 2) &&
std.assertEqual(std.isEmpty(''), true) &&
std.assertEqual(std.isEmpty('non-empty string'), false) &&
Expand All @@ -1593,14 +1593,14 @@ std.assertEqual(std.contains([1, 2, 3], 'foo'), false) &&
std.assertEqual(std.equalsIgnoreCase('foo', 'FOO'), true) &&
std.assertEqual(std.equalsIgnoreCase('foo', 'bar'), false) &&
//std.assertEqual(std.isEven(10), true) &&
//std.assertEqual(std.isEven(5), false) &&
//std.assertEqual(std.isOdd(5), true) &&
//std.assertEqual(std.isOdd(10), false) &&
//std.assertEqual(std.isInteger(1), true) &&
//std.assertEqual(std.isInteger(1.1), false) &&
//std.assertEqual(std.isDecimal(1.1), true) &&
//std.assertEqual(std.isDecimal(1), false) &&
std.assertEqual(std.isEven(10), true) &&
std.assertEqual(std.isEven(5), false) &&
std.assertEqual(std.isOdd(5), true) &&
std.assertEqual(std.isOdd(10), false) &&
std.assertEqual(std.isInteger(1), true) &&
std.assertEqual(std.isInteger(1.1), false) &&
std.assertEqual(std.isDecimal(1.1), true) &&
std.assertEqual(std.isDecimal(1), false) &&
std.assertEqual(std.remove([1, 2, 3], 2), [1, 3]) &&
std.assertEqual(std.removeAt([1, 2, 3], 1), [1, 3]) &&
Expand Down
42 changes: 21 additions & 21 deletions sjsonnet/test/resources/test_suite/stdlib_native.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -313,10 +313,10 @@ std.assertEqual(std.lines(['a', null, 'b']), 'a\nb\n') &&

std.assertEqual(std.flattenArrays([[1, 2, 3], [4, 5, 6], []]), [1, 2, 3, 4, 5, 6]) &&

//std.assertEqual(std.flattenDeepArray([]), []) &&
//std.assertEqual(std.flattenDeepArray([1, 2, 3]), [1, 2, 3]) &&
//std.assertEqual(std.flattenDeepArray([1, [2, 3]]), [1, 2, 3]) &&
//std.assertEqual(std.flattenDeepArray([[1], [2, 3], [[null]]]), [1, 2, 3, null]) &&
std.assertEqual(std.flattenDeepArray([]), []) &&
std.assertEqual(std.flattenDeepArray([1, 2, 3]), [1, 2, 3]) &&
std.assertEqual(std.flattenDeepArray([1, [2, 3]]), [1, 2, 3]) &&
std.assertEqual(std.flattenDeepArray([[1], [2, 3], [[null]]]), [1, 2, 3, null]) &&

std.assertEqual(
std.manifestIni({
Expand Down Expand Up @@ -356,11 +356,11 @@ std.assertEqual(std.escapeStringJson('he"llo'), '"he\\"llo"') &&
std.assertEqual(std.escapeStringJson('he"llo'), '"he\\"llo"') &&
std.assertEqual(std.escapeStringBash("he\"l'lo"), "'he\"l'\"'\"'lo'") &&
std.assertEqual(std.escapeStringDollars('The path is ${PATH}.'), 'The path is $${PATH}.') &&
//std.assertEqual(std.escapeStringXML('2 < 3'), '2 &lt; 3') &&
//std.assertEqual(std.escapeStringXML('3 > 2'), '3 &gt; 2') &&
//std.assertEqual(std.escapeStringXML('"foo"'), '&quot;foo&quot;') &&
//std.assertEqual(std.escapeStringXML("don't believe the hype"), 'don&apos;t believe the hype') &&
//std.assertEqual(std.escapeStringXML('PB&J'), 'PB&amp;J') &&
std.assertEqual(std.escapeStringXML('2 < 3'), '2 &lt; 3') &&
std.assertEqual(std.escapeStringXML('3 > 2'), '3 &gt; 2') &&
std.assertEqual(std.escapeStringXML('"foo"'), '&quot;foo&quot;') &&
std.assertEqual(std.escapeStringXML("don't believe the hype"), 'don&apos;t believe the hype') &&
std.assertEqual(std.escapeStringXML('PB&J'), 'PB&amp;J') &&
std.assertEqual(std.escapeStringJson('!~'), '"!~"') &&

std.assertEqual(std.manifestPython({
Expand Down Expand Up @@ -1518,8 +1518,8 @@ std.assertEqual(std.parseJson('{"a": {"b": ["c", 42]}}'), { a: { b: ['c', 42] }
std.assertEqual(std.asciiUpper('!@#$%&*()asdfghFGHJKL09876 '), '!@#$%&*()ASDFGHFGHJKL09876 ') &&
std.assertEqual(std.asciiLower('!@#$%&*()asdfghFGHJKL09876 '), '!@#$%&*()asdfghfghjkl09876 ') &&
//std.assertEqual(std.deepJoin(['a', ['b', 'c', [[], 'd', ['e'], 'f', 'g'], [], []], 'h']),
// 'abcdefgh') &&
std.assertEqual(std.deepJoin(['a', ['b', 'c', [[], 'd', ['e'], 'f', 'g'], [], []], 'h']),
'abcdefgh') &&
std.assertEqual(std.findSubstr('', 'a'), []) &&
std.assertEqual(std.findSubstr('aa', ''), []) &&
Expand Down Expand Up @@ -1579,8 +1579,8 @@ std.assertEqual(std.xor(true, true), false) &&
std.assertEqual(std.xnor(true, false), false) &&
std.assertEqual(std.xnor(true, true), true) &&
//std.assertEqual(std.round(1.2), 1) &&
//std.assertEqual(std.round(1.5), 2) &&
std.assertEqual(std.round(1.2), 1) &&
std.assertEqual(std.round(1.5), 2) &&
std.assertEqual(std.isEmpty(''), true) &&
std.assertEqual(std.isEmpty('non-empty string'), false) &&
Expand All @@ -1591,14 +1591,14 @@ std.assertEqual(std.contains([1, 2, 3], 'foo'), false) &&
std.assertEqual(std.equalsIgnoreCase('foo', 'FOO'), true) &&
std.assertEqual(std.equalsIgnoreCase('foo', 'bar'), false) &&
//std.assertEqual(std.isEven(10), true) &&
//std.assertEqual(std.isEven(5), false) &&
//std.assertEqual(std.isOdd(5), true) &&
//std.assertEqual(std.isOdd(10), false) &&
//std.assertEqual(std.isInteger(1), true) &&
//std.assertEqual(std.isInteger(1.1), false) &&
//std.assertEqual(std.isDecimal(1.1), true) &&
//std.assertEqual(std.isDecimal(1), false) &&
std.assertEqual(std.isEven(10), true) &&
std.assertEqual(std.isEven(5), false) &&
std.assertEqual(std.isOdd(5), true) &&
std.assertEqual(std.isOdd(10), false) &&
std.assertEqual(std.isInteger(1), true) &&
std.assertEqual(std.isInteger(1.1), false) &&
std.assertEqual(std.isDecimal(1.1), true) &&
std.assertEqual(std.isDecimal(1), false) &&
std.assertEqual(std.remove([1, 2, 3], 2), [1, 3]) &&
std.assertEqual(std.removeAt([1, 2, 3], 1), [1, 3]) &&
Expand Down

0 comments on commit 09a4fd2

Please sign in to comment.