From 61e91d483de1238e5165941b35b6d0d6d1cb4f5a Mon Sep 17 00:00:00 2001 From: Lazerbeak12345 Date: Sun, 3 Sep 2023 01:26:02 +0000 Subject: [PATCH] feat(walk_inner): provide parent info --- helpers.lua | 51 ++++++++++++----- test.lua | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+), 15 deletions(-) diff --git a/helpers.lua b/helpers.lua index 73aa8a1..46eb9a7 100644 --- a/helpers.lua +++ b/helpers.lua @@ -46,26 +46,47 @@ function formspec_ast.interpret(spec, custom_handlers) end local function walk_inner(tree, container_elems) - local parents = {} - local i = 1 + -- Use two tables to store values so that a new table doesn't have to be + -- created every time a container is entered + local parent_trees = {} + local parent_indexes = {} + + local parent_idx = 0 + local i = 0 return function() - local res = tree[i] - while not res do - local n = table.remove(parents) - if not n then - return - end - tree, i = n[1], n[2] - res = tree[i] + -- If the previously yielded element has children + if i > 0 and container_elems[tree[i].type] then + -- Save the parent element and the next index + parent_idx = parent_idx + 1 + parent_trees[parent_idx] = tree + parent_indexes[parent_idx] = i + 1 + + -- Set the new tree + tree = tree[i] + + -- Reset I to initial value (zero) + i = 0 end + + -- Point index to next child i = i + 1 - if container_elems[res.type] then - table.insert(parents, {tree, i}) - tree = res - i = 1 + -- Get child at index + local elem = tree[i] + while not elem do -- current child is invalid + if parent_idx < 1 then + return + end + + -- Restore parent's relative index + tree, i = parent_trees[parent_idx], parent_indexes[parent_idx] + parent_idx = parent_idx - 1 + + -- Get child at index + elem = tree[i] end - return res + + return elem, tree, i end end diff --git a/test.lua b/test.lua index a406ca7..0238e03 100644 --- a/test.lua +++ b/test.lua @@ -620,3 +620,159 @@ it("does not parse invalid tabheader elements", function() assert.is_nil(formspec_ast.parse("tabheader[1,2;name;a,b,c;1;false]")) assert.is_nil(formspec_ast.parse("tabheader[1,2;3,4;name;a,b,c;1]")) end) + +describe("helpers", function () + describe("walk", function () + it("walks over every element", function () + local tree = { + { type = "box", color = "green" }, + { type = "label", label = "the text" }, + { + type = "container", + { type = "label", label = "the text" } + } + } + for node in formspec_ast.walk(tree) do + node.visited = true + end + assert.same({ + { type = "box", color = "green", visited = true }, + { type = "label", label = "the text", visited = true }, + { + type = "container", + visited = true, + { type = "label", label = "the text", visited = true } + } + }, tree) + end) + it("can be stopped", function () + local tree = { + { type = "box", color = "green" }, + { type = "label", label = "the text" }, + { + type = "container", + { type = "label", label = "the text" } + }, + { type = "label", label = "the text" } + } + local count = 0 + for node in formspec_ast.walk(tree) do + count = count + 1 + node.visited = true + if count == 3 then break end + end + assert.same({ + { type = "box", color = "green", visited = true }, + { type = "label", label = "the text", visited = true }, + { + type = "container", + visited = true, + { type = "label", label = "the text" } + }, + { type = "label", label = "the text" } + }, tree) + end) + it("can accept custom element list", function () + local tree = { + { type = "box", color = "green" }, + { type = "label", label = "the text" }, + { + type = "nonsensewordhere", + { type = "label", label = "the text" } + }, + { + type = "container", + { type = "label", label = "the text" } + }, + { type = "label", label = "the text" } + } + for node in formspec_ast.walk(tree, {nonsensewordhere = true}) do + node.visited = true + end + assert.same({ + { type = "box", color = "green", visited = true }, + { type = "label", label = "the text", visited = true }, + { + type = "nonsensewordhere", + visited = true, + { type = "label", label = "the text", visited = true } + }, + { + type = "container", + visited = true, + { type = "label", label = "the text" } + }, + { type = "label", label = "the text", visited = true } + }, tree) + end) + it("can provide parent info when walking", function () +local tree = { + { type = "box", color = "green" }, + { type = "label", label = "the text" }, + { + type = "container", + { type = "label", label = "the text" } + }, + { type = "label", label = "the text" } + } + local logged_indexes = {} + for node, parent, index in formspec_ast.walk(tree) do + node.visited = true + parent.parent_of = (parent.parent_of or 0) + 1 + logged_indexes[#logged_indexes+1] = index + end + assert.same({ + parent_of = 4, + { type = "box", color = "green", visited = true }, + { type = "label", label = "the text", visited = true }, + { + type = "container", + visited = true, + parent_of = 1, + { type = "label", label = "the text", visited = true } + }, + { type = "label", label = "the text", visited = true }, + }, tree) + -- NOTE: this is, in effect, asserting the order of the crawl + assert.same({ 1, 2, 3, 1, 4}, logged_indexes) + end) + it("parent info can be modified without failure", function () + -- INFO: This test is a regression test. + local tree = { + { type = "box", color = "green" }, + { type = "label", label = "the text" }, + { + type = "container", + { type = "label", label = "the text" } + } + } + local found_child = false + for node, parent in formspec_ast.walk(tree) do + if node.type == "container" then + node[#node+1] = { type = "label", label = "the new text" } + elseif parent.type == "container" then + node.is_child_thingy = true + if not found_child then + found_child = true + node.type = "container" + node[1] = { type = "box", color = "red" } + node.label = nil + end + end + end + assert.same({ + { type = "box", color = "green" }, + { type = "label", label = "the text" }, + { + type = "container", + { + type = "container", + is_child_thingy = true, + { type = "box", color = "red", is_child_thingy = true } + }, + { type = "label", label = "the new text", is_child_thingy = true } + } + }, tree) + end) + end) +end)