diff --git a/bench/resources/bug_suite/deep_array_traversal.jsonnet b/bench/resources/bug_suite/deep_array_traversal.jsonnet new file mode 100644 index 000000000..f5eb2a861 --- /dev/null +++ b/bench/resources/bug_suite/deep_array_traversal.jsonnet @@ -0,0 +1,13 @@ +local nest(n, x) = if n == 0 then x else [nest(n - 1, x)]; +local sharedStrings = std.makeArray(200, function(i) "x"); +local sharedValues = std.makeArray(200, function(i) if i % 2 == 0 then "x" else i); +local sharedStringView = [sharedStrings + [], [] + sharedStrings, sharedStrings]; +local sharedValueView = [sharedValues + [], [] + sharedValues, sharedValues]; +{ + deepJoinFlat: std.length(std.deepJoin(std.makeArray(20000, function(i) "x"))), + deepJoinNested: std.length(std.deepJoin(std.makeArray(1000, function(i) nest(20, "x")))), + flattenFlat: std.length(std.flattenDeepArray(std.makeArray(20000, function(i) i))), + flattenNested: std.length(std.flattenDeepArray(std.makeArray(1000, function(i) nest(20, i)))), + sharedViewDeepJoin: std.length(std.deepJoin(sharedStringView)), + sharedViewFlatten: std.length(std.flattenDeepArray(sharedValueView)), +} diff --git a/bench/resources/bug_suite/deep_array_traversal.jsonnet.golden b/bench/resources/bug_suite/deep_array_traversal.jsonnet.golden new file mode 100644 index 000000000..4bb3fe30b --- /dev/null +++ b/bench/resources/bug_suite/deep_array_traversal.jsonnet.golden @@ -0,0 +1,8 @@ +{ + "deepJoinFlat": 20000, + "deepJoinNested": 1000, + "flattenFlat": 20000, + "flattenNested": 1000, + "sharedViewDeepJoin": 600, + "sharedViewFlatten": 600 +} diff --git a/sjsonnet/src-js/sjsonnet/stdlib/DeepArrayActiveSet.scala b/sjsonnet/src-js/sjsonnet/stdlib/DeepArrayActiveSet.scala new file mode 100644 index 000000000..1ff5d6afb --- /dev/null +++ b/sjsonnet/src-js/sjsonnet/stdlib/DeepArrayActiveSet.scala @@ -0,0 +1,114 @@ +package sjsonnet.stdlib + +import sjsonnet.Val + +private[stdlib] final class DeepArrayActiveSet(initialCapacity: Int) { + private var keys = new Array[Val.Arr](tableSizeFor(initialCapacity)) + private var size = 0 + private var growAt = keys.length >> 1 + + @inline def contains(arr: Val.Arr): Boolean = { + val current = keys + val mask = current.length - 1 + var slot = slotOf(arr, mask) + while (true) { + val key = current(slot) + if (key eq null) return false + if (key eq arr) return true + slot = (slot + 1) & mask + } + false + } + + @inline def add(arr: Val.Arr): Boolean = { + val current = keys + val mask = current.length - 1 + var slot = slotOf(arr, mask) + while (true) { + val key = current(slot) + if (key eq null) { + current(slot) = arr + size += 1 + if (size >= growAt) grow() + return true + } + if (key eq arr) return false + slot = (slot + 1) & mask + } + false + } + + @inline def remove(arr: Val.Arr): Unit = { + val current = keys + val mask = current.length - 1 + var slot = slotOf(arr, mask) + while (true) { + val key = current(slot) + if (key eq null) return + if (key eq arr) { + removeAt(slot) + return + } + slot = (slot + 1) & mask + } + } + + private def grow(): Unit = { + val old = keys + val next = new Array[Val.Arr](old.length << 1) + keys = next + growAt = next.length >> 1 + var i = 0 + while (i < old.length) { + val key = old(i) + if (key ne null) insertExisting(next, key) + i += 1 + } + } + + private def removeAt(slot0: Int): Unit = { + val current = keys + val mask = current.length - 1 + var slot = slot0 + var next = (slot + 1) & mask + while (current(next) ne null) { + val key = current(next) + val home = slotOf(key, mask) + if (((next - home) & mask) >= ((slot - home) & mask)) { + current(slot) = key + slot = next + } + next = (next + 1) & mask + } + current(slot) = null + size -= 1 + } + + private def insertExisting(table: Array[Val.Arr], arr: Val.Arr): Unit = { + val mask = table.length - 1 + var slot = slotOf(arr, mask) + while (table(slot) ne null) { + slot = (slot + 1) & mask + } + table(slot) = arr + } + + @inline private def slotOf(arr: Val.Arr, mask: Int): Int = { + var h = System.identityHashCode(arr) + // Scala.js identityHashCode uses stable object ids; avalanche before + // masking so regular id sequences do not cluster in power-of-two tables. + h ^= h >>> 16 + h *= -2048144789 + h ^= h >>> 13 + h *= -1028477387 + h ^= h >>> 16 + h & mask + } + + private def tableSizeFor(initialCapacity: Int): Int = { + var n = 8 + val target = if (initialCapacity <= 0) 8 else initialCapacity << 1 + while (n < target) n <<= 1 + n + } +} diff --git a/sjsonnet/src-jvm-native/sjsonnet/stdlib/DeepArrayActiveSet.scala b/sjsonnet/src-jvm-native/sjsonnet/stdlib/DeepArrayActiveSet.scala new file mode 100644 index 000000000..cdf1a958b --- /dev/null +++ b/sjsonnet/src-jvm-native/sjsonnet/stdlib/DeepArrayActiveSet.scala @@ -0,0 +1,17 @@ +package sjsonnet.stdlib + +import sjsonnet.Val + +private[stdlib] final class DeepArrayActiveSet(initialCapacity: Int) { + private val active = + new java.util.IdentityHashMap[Val.Arr, java.lang.Boolean](initialCapacity) + + @inline def contains(arr: Val.Arr): Boolean = + active.containsKey(arr) + + @inline def add(arr: Val.Arr): Boolean = + active.put(arr, java.lang.Boolean.TRUE) eq null + + @inline def remove(arr: Val.Arr): Unit = + active.remove(arr) +} diff --git a/sjsonnet/src/sjsonnet/stdlib/ArrayModule.scala b/sjsonnet/src/sjsonnet/stdlib/ArrayModule.scala index 675d331bf..6c4b33b4e 100644 --- a/sjsonnet/src/sjsonnet/stdlib/ArrayModule.scala +++ b/sjsonnet/src/sjsonnet/stdlib/ArrayModule.scala @@ -531,20 +531,11 @@ object ArrayModule extends AbstractFunctionModule { val arr = value0.asInstanceOf[Val.Arr] val out = new mutable.ArrayBuilder.ofRef[Eval] out.sizeHint(arr.length) - val q = new java.util.ArrayDeque[Eval](arr.length) - var initialIdx = 0 - while (initialIdx < arr.length) { - q.add(arr.eval(initialIdx)) - initialIdx += 1 - } - while (!q.isEmpty) { - q.removeFirst().value match { - case v: Val.Arr => - var i = v.length - 1 - while (i >= 0) { q.push(v.eval(i)); i -= 1 } - case x => out += x - } - } + DeepArrayTraversal.appendFlattened( + arr, + out, + math.max(ev.settings.maxMaterializeDepth, ev.settings.maxStack) + ) Val.Arr(pos, out.result()) } } diff --git a/sjsonnet/src/sjsonnet/stdlib/DeepArrayTraversal.scala b/sjsonnet/src/sjsonnet/stdlib/DeepArrayTraversal.scala new file mode 100644 index 000000000..b38733663 --- /dev/null +++ b/sjsonnet/src/sjsonnet/stdlib/DeepArrayTraversal.scala @@ -0,0 +1,255 @@ +package sjsonnet.stdlib + +import sjsonnet._ + +import scala.collection.mutable + +private[stdlib] object DeepArrayTraversal { + private final class ArrFrame( + val arr: Val.Arr, + var index: Int, + val len: Int, + val direct: Array[Eval]) + + private final class ArrFrameStack(initialCapacity: Int) { + private var frames = new Array[ArrFrame](initialCapacity) + private var depth = 0 + + @inline def isEmpty: Boolean = depth == 0 + + @inline def size: Int = depth + + @inline def peek(): ArrFrame = + frames(depth - 1) + + @inline def containsArray(arr: Val.Arr): Boolean = { + var i = depth - 1 + while (i >= 0) { + if (frames(i).arr eq arr) return true + i -= 1 + } + false + } + + @inline def addArraysTo(active: DeepArrayActiveSet): Unit = { + var i = 0 + while (i < depth) { + assert(active.add(frames(i).arr), "active deep array stack should not contain duplicates") + i += 1 + } + } + + @inline def push(frame: ArrFrame): Unit = { + if (depth == frames.length) grow() + frames(depth) = frame + depth += 1 + } + + @inline def pop(): ArrFrame = { + depth -= 1 + val frame = frames(depth) + frames(depth) = null + frame + } + + private def grow(): Unit = { + val old = frames + val next = new Array[ArrFrame](old.length << 1) + System.arraycopy(old, 0, next, 0, old.length) + frames = next + } + } + + private final class TraversalState(maxDepth: Int) { + private val stack = new ArrFrameStack(InitialDepthCapacity) + private var active: DeepArrayActiveSet = null + + @inline def isEmpty: Boolean = stack.isEmpty + + @inline def peek(): ArrFrame = stack.peek() + + @inline def pushArray(arr: Val.Arr): Unit = + pushArray(arr, 0, arr.length, arr.directBackingArray) + + @inline def pushArray(arr: Val.Arr, index: Int, len: Int, direct: Array[Eval]): Unit = { + if (stack.size >= maxDepth) Error.fail(MaxStackFramesExceededMessage) + val currentActive = active + if (currentActive eq null) { + if (stack.containsArray(arr)) Error.fail(MaxStackFramesExceededMessage) + } else if (!currentActive.add(arr)) { + Error.fail(MaxStackFramesExceededMessage) + } + stack.push(new ArrFrame(arr, index, len, direct)) + if ((currentActive eq null) && stack.size == ActiveSetThreshold) { + val nextActive = new DeepArrayActiveSet(ActiveSetThreshold) + stack.addArraysTo(nextActive) + active = nextActive + } + } + + @inline def popFrame(): Unit = { + val popped = stack.pop() + if (active ne null) active.remove(popped.arr) + } + } + + private val InitialDepthCapacity = 4 + private val ActiveSetThreshold = 32 + private val MaxStackFramesExceededMessage = "max stack frames exceeded" + + def appendFlattened(root: Val.Arr, out: mutable.ArrayBuilder.ofRef[Eval], maxDepth: Int): Unit = { + if (maxDepth <= 0) Error.fail(MaxStackFramesExceededMessage) + val rootDirect = root.directBackingArray + val rootLen = root.length + var rootIndex = 0 + while (rootIndex < rootLen) { + val child = + if (rootDirect ne null) rootDirect(rootIndex).value else root.value(rootIndex) + rootIndex += 1 + child match { + case arr: Val.Arr => + appendFlattenedFromNested(root, rootIndex, rootLen, rootDirect, arr, out, maxDepth) + return + case leaf => out += leaf + } + } + } + + private def appendFlattenedFromNested( + root: Val.Arr, + rootIndex: Int, + rootLen: Int, + rootDirect: Array[Eval], + nested: Val.Arr, + out: mutable.ArrayBuilder.ofRef[Eval], + maxDepth: Int): Unit = { + val state = new TraversalState(maxDepth) + state.pushArray(root, rootIndex, rootLen, rootDirect) + state.pushArray(nested) + while (!state.isEmpty) { + val frame = state.peek() + if (frame.index < frame.len) { + val child = { + val d = frame.direct + if (d ne null) d(frame.index).value else frame.arr.value(frame.index) + } + frame.index += 1 + child match { + case arr: Val.Arr => state.pushArray(arr) + case leaf => out += leaf + } + } else { + state.popFrame() + } + } + } + + def appendDeepJoined(root: Val.Arr, out: StringBuilderWriter, maxDepth: Int): Unit = { + if (maxDepth <= 0) Error.fail(MaxStackFramesExceededMessage) + val rootDirect = root.directBackingArray + val rootLen = root.length + var rootIndex = 0 + while (rootIndex < rootLen) { + val child = + if (rootDirect ne null) rootDirect(rootIndex).value else root.value(rootIndex) + rootIndex += 1 + child match { + case arr: Val.Arr => + if (appendDeepJoinedRootNested(root, rootIndex, rootLen, rootDirect, arr, out, maxDepth)) + return + case s: Val.Str => out.write(s.str) + case v => + Error.fail("std.deepJoin: expected string or array, got " + v.prettyName) + } + } + } + + // Scans the first nested segment separately so flat root/first-child cases + // keep the fast path allocation-free and only build traversal state when the + // remaining suffix needs an explicit stack. + private def appendDeepJoinedRootNested( + root: Val.Arr, + rootIndex: Int, + rootLen: Int, + rootDirect: Array[Eval], + initial: Val.Arr, + out: StringBuilderWriter, + maxDepth: Int): Boolean = { + if (maxDepth <= 1) Error.fail(MaxStackFramesExceededMessage) + if (initial eq root) Error.fail(MaxStackFramesExceededMessage) + + val direct = initial.directBackingArray + val len = initial.length + var index = 0 + var nested: Val.Arr = null + while (index < len && (nested eq null)) { + val child = + if (direct ne null) direct(index).value else initial.value(index) + index += 1 + child match { + case arr: Val.Arr => nested = arr + case s: Val.Str => out.write(s.str) + case v => + Error.fail("std.deepJoin: expected string or array, got " + v.prettyName) + } + } + if (nested eq null) return false + + val state = new TraversalState(maxDepth) + state.pushArray(root, rootIndex, rootLen, rootDirect) + state.pushArray(initial, index, len, direct) + appendDeepJoinedNested(state, nested, out) + appendDeepJoinedRemaining(state, out) + true + } + + private def appendDeepJoinedRemaining(state: TraversalState, out: StringBuilderWriter): Unit = { + while (!state.isEmpty) { + val frame = state.peek() + if (frame.index < frame.len) { + val child = { + val d = frame.direct + if (d ne null) d(frame.index).value else frame.arr.value(frame.index) + } + frame.index += 1 + child match { + case arr: Val.Arr => appendDeepJoinedNested(state, arr, out) + case s: Val.Str => out.write(s.str) + case v => + Error.fail("std.deepJoin: expected string or array, got " + v.prettyName) + } + } else { + state.popFrame() + } + } + } + + private def appendDeepJoinedNested( + state: TraversalState, + initial: Val.Arr, + out: StringBuilderWriter): Unit = { + var current = initial + while (true) { + state.pushArray(current) + val frame = state.peek() + var nested: Val.Arr = null + while (frame.index < frame.len && (nested eq null)) { + val child = + if (frame.direct ne null) frame.direct(frame.index).value + else frame.arr.value(frame.index) + frame.index += 1 + child match { + case arr: Val.Arr => nested = arr + case s: Val.Str => out.write(s.str) + case v => + Error.fail("std.deepJoin: expected string or array, got " + v.prettyName) + } + } + if (nested eq null) { + state.popFrame() + return + } + current = nested + } + } +} diff --git a/sjsonnet/src/sjsonnet/stdlib/ManifestModule.scala b/sjsonnet/src/sjsonnet/stdlib/ManifestModule.scala index 364fd5227..b7591e8ac 100644 --- a/sjsonnet/src/sjsonnet/stdlib/ManifestModule.scala +++ b/sjsonnet/src/sjsonnet/stdlib/ManifestModule.scala @@ -380,22 +380,21 @@ object ManifestModule extends AbstractFunctionModule { */ private object DeepJoin extends Val.Builtin1("deepJoin", "arr") { def evalRhs(value: Eval, ev: EvalScope, pos: Position): Val = { - // Use the unsynchronized StringBuilderWriter: each Val.Str chunk triggers - // out.write(...) in a tight loop, and StringWriter's backing StringBuffer - // pays a monitor enter/exit per call. Same swap pattern as TomlRenderer (#875). - val out = new StringBuilderWriter() - val q = new java.util.ArrayDeque[Eval]() - q.add(value) - while (!q.isEmpty) { - q.removeFirst().value match { - case v: Val.Arr => - var i = v.length - 1 - while (i >= 0) { q.push(v.eval(i)); i -= 1 } - case s: Val.Str => out.write(s.str) - case s => Error.fail("std.deepJoin: expected string or array, got " + s.prettyName) - } + value.value match { + case s: Val.Str => Val.Str(pos, s.str) + case arr: Val.Arr => + // Use the unsynchronized StringBuilderWriter: each Val.Str chunk triggers + // out.write(...) in a tight loop, and StringWriter's backing StringBuffer + // pays a monitor enter/exit per call. Same swap pattern as TomlRenderer (#875). + val out = new StringBuilderWriter() + DeepArrayTraversal.appendDeepJoined( + arr, + out, + math.max(ev.settings.maxMaterializeDepth, ev.settings.maxStack) + ) + Val.Str(pos, out.toString) + case s => Error.fail("std.deepJoin: expected string or array, got " + s.prettyName) } - Val.Str(pos, out.toString) } } diff --git a/sjsonnet/test/resources/new_test_suite/deep_array_traversal_shared_arrays.jsonnet b/sjsonnet/test/resources/new_test_suite/deep_array_traversal_shared_arrays.jsonnet new file mode 100644 index 000000000..81231586d --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/deep_array_traversal_shared_arrays.jsonnet @@ -0,0 +1,17 @@ +local shared = ["x", ["y"]]; +local left = ["a"]; +local right = [["b"], "c"]; +local lazyConcatLeft = std.makeArray(256, function(i) []); +local lazyConcat = lazyConcatLeft + [["b"], "c"]; +local nest(n, x) = if n == 0 then x else [nest(n - 1, x)]; +{ + deepJoin: std.deepJoin([shared, shared]), + deepJoinConcatView: std.deepJoin(left + right), + deepJoinDeep: std.deepJoin(nest(200, "z")), + deepJoinLazyConcatView: std.deepJoin(lazyConcat), + flattenDeepArray: std.flattenDeepArray([shared, shared]), + flattenConcatView: std.flattenDeepArray(left + right), + flattenDeepArrayDeep: std.flattenDeepArray(nest(200, "z")), + flattenLazyConcatView: std.flattenDeepArray(lazyConcat), + flattenReverseView: std.flattenDeepArray(std.reverse([["r2"], ["r1"]])), +} diff --git a/sjsonnet/test/resources/new_test_suite/deep_array_traversal_shared_arrays.jsonnet.golden b/sjsonnet/test/resources/new_test_suite/deep_array_traversal_shared_arrays.jsonnet.golden new file mode 100644 index 000000000..e63962bfa --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/deep_array_traversal_shared_arrays.jsonnet.golden @@ -0,0 +1,28 @@ +{ + "deepJoin": "xyxy", + "deepJoinConcatView": "abc", + "deepJoinDeep": "z", + "deepJoinLazyConcatView": "bc", + "flattenConcatView": [ + "a", + "b", + "c" + ], + "flattenDeepArray": [ + "x", + "y", + "x", + "y" + ], + "flattenDeepArrayDeep": [ + "z" + ], + "flattenLazyConcatView": [ + "b", + "c" + ], + "flattenReverseView": [ + "r1", + "r2" + ] +} diff --git a/sjsonnet/test/resources/new_test_suite/error.deepjoin_nested_recursive_array.jsonnet b/sjsonnet/test/resources/new_test_suite/error.deepjoin_nested_recursive_array.jsonnet new file mode 100644 index 000000000..869f911b7 --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/error.deepjoin_nested_recursive_array.jsonnet @@ -0,0 +1 @@ +std.deepJoin(local a = [["x", a]]; a) diff --git a/sjsonnet/test/resources/new_test_suite/error.deepjoin_nested_recursive_array.jsonnet.golden b/sjsonnet/test/resources/new_test_suite/error.deepjoin_nested_recursive_array.jsonnet.golden new file mode 100644 index 000000000..dbb5b4c19 --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/error.deepjoin_nested_recursive_array.jsonnet.golden @@ -0,0 +1,2 @@ +sjsonnet.Error: [std.deepJoin] max stack frames exceeded + at [].(error.deepjoin_nested_recursive_array.jsonnet:1:13) diff --git a/sjsonnet/test/resources/new_test_suite/error.deepjoin_recursive_array.jsonnet b/sjsonnet/test/resources/new_test_suite/error.deepjoin_recursive_array.jsonnet new file mode 100644 index 000000000..3e2575cb2 --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/error.deepjoin_recursive_array.jsonnet @@ -0,0 +1 @@ +std.deepJoin(local a = [a]; a) diff --git a/sjsonnet/test/resources/new_test_suite/error.deepjoin_recursive_array.jsonnet.golden b/sjsonnet/test/resources/new_test_suite/error.deepjoin_recursive_array.jsonnet.golden new file mode 100644 index 000000000..ac0b41ef9 --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/error.deepjoin_recursive_array.jsonnet.golden @@ -0,0 +1,2 @@ +sjsonnet.Error: [std.deepJoin] max stack frames exceeded + at [].(error.deepjoin_recursive_array.jsonnet:1:13) diff --git a/sjsonnet/test/resources/new_test_suite/error.flatten_deep_array_recursive_array.jsonnet b/sjsonnet/test/resources/new_test_suite/error.flatten_deep_array_recursive_array.jsonnet new file mode 100644 index 000000000..d3501b86a --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/error.flatten_deep_array_recursive_array.jsonnet @@ -0,0 +1 @@ +std.flattenDeepArray(local a = [a]; a) diff --git a/sjsonnet/test/resources/new_test_suite/error.flatten_deep_array_recursive_array.jsonnet.golden b/sjsonnet/test/resources/new_test_suite/error.flatten_deep_array_recursive_array.jsonnet.golden new file mode 100644 index 000000000..dff5d21a0 --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/error.flatten_deep_array_recursive_array.jsonnet.golden @@ -0,0 +1,2 @@ +sjsonnet.Error: [std.flattenDeepArray] max stack frames exceeded + at [].(error.flatten_deep_array_recursive_array.jsonnet:1:21) diff --git a/sjsonnet/test/src-jvm/sjsonnet/MainTests.scala b/sjsonnet/test/src-jvm/sjsonnet/MainTests.scala index 7e4438c8c..74c6a3ab8 100644 --- a/sjsonnet/test/src-jvm/sjsonnet/MainTests.scala +++ b/sjsonnet/test/src-jvm/sjsonnet/MainTests.scala @@ -245,6 +245,27 @@ object MainTests extends TestSuite { |""".stripMargin assert((res, out, err) == ((0, expectedJson, ""))) } + + test("maxStackControlsDeepArrayTraversal") { + val source = + """local nest(n, x) = if n == 0 then x else [nest(n - 1, x)]; + |{ + | deepJoin: std.deepJoin(nest(1100, "x")), + | flattenDeepArray: std.flattenDeepArray(nest(1100, "x")), + |} + |""".stripMargin + val (res, out, err) = runMain(source, "--exec", "--max-stack", "1200") + val expectedJson = + """{ + | "deepJoin": "x", + | "flattenDeepArray": [ + | "x" + | ] + |} + |""".stripMargin + assert((res, out, err) == ((0, expectedJson, ""))) + } + test("execYaml") { val source = "local x = [1]; local y = [2]; x + y" val (res, out, err) = runMain(source, "--exec", "--yaml-out") diff --git a/sjsonnet/test/src/sjsonnet/DeepArrayTraversalTests.scala b/sjsonnet/test/src/sjsonnet/DeepArrayTraversalTests.scala new file mode 100644 index 000000000..bd24b9f05 --- /dev/null +++ b/sjsonnet/test/src/sjsonnet/DeepArrayTraversalTests.scala @@ -0,0 +1,21 @@ +package sjsonnet + +import sjsonnet.TestUtils.eval +import utest._ + +object DeepArrayTraversalTests extends TestSuite { + val tests: Tests = Tests { + test("deepJoin and flattenDeepArray preserve default depth and honor larger maxStack") { + val nested = + """local nest(n, x) = if n == 0 then x else [nest(n - 1, x)]; + |""".stripMargin + + eval(nested + """std.deepJoin(nest(600, "x"))""") ==> ujson.Str("x") + eval(nested + """std.flattenDeepArray(nest(600, "x"))""") ==> ujson.Arr("x") + + eval(nested + """std.deepJoin(nest(1100, "x"))""", maxStack = 1200) ==> ujson.Str("x") + eval(nested + """std.flattenDeepArray(nest(1100, "x"))""", maxStack = 1200) ==> + ujson.Arr("x") + } + } +}