From 38fff6ba4c959ef2ebdb6aa7bbc44657687a19cb Mon Sep 17 00:00:00 2001 From: Mike Barkmin Date: Wed, 28 Oct 2026 17:37:04 +0100 Subject: [PATCH] Keep the indented lines under a line the parser does not recognise --- .changeset/kind-lines-stay.md | 5 ++++ packages/struktolab/package.json | 1 + .../src/common/pseudocode-parser.js | 24 +++++++++++++++++++ .../src/common/pseudocode-parser.test.js | 17 +++++++++++++ 4 files changed, 47 insertions(+) create mode 100644 .changeset/kind-lines-stay.md create mode 100644 packages/struktolab/src/common/pseudocode-parser.test.js diff --git a/.changeset/kind-lines-stay.md b/.changeset/kind-lines-stay.md new file mode 100644 index 0000000..4b6ff7b --- /dev/null +++ b/.changeset/kind-lines-stay.md @@ -0,0 +1,5 @@ +--- +"struktolab": patch +--- + +Keep the indented lines under a line the pseudocode parser does not recognise (e.g. `while x:` instead of `repeat while x:`); they follow it instead of disappearing diff --git a/packages/struktolab/package.json b/packages/struktolab/package.json index a8c377b..69e0c0d 100644 --- a/packages/struktolab/package.json +++ b/packages/struktolab/package.json @@ -30,6 +30,7 @@ ], "scripts": { "dev": "BUILD_TARGET=editor vite build --watch", + "test": "node --test 'src/**/*.test.js'", "build": "BUILD_TARGET=core vite build && BUILD_TARGET=renderer vite build && BUILD_TARGET=editor vite build", "build:core": "BUILD_TARGET=core vite build", "build:renderer": "BUILD_TARGET=renderer vite build", diff --git a/packages/struktolab/src/common/pseudocode-parser.js b/packages/struktolab/src/common/pseudocode-parser.js index 22d2274..f7e2366 100644 --- a/packages/struktolab/src/common/pseudocode-parser.js +++ b/packages/struktolab/src/common/pseudocode-parser.js @@ -619,6 +619,12 @@ function mergeBlocks(lines, baseIndent, keywords) { } // --- TaskNode (default) --- + // A line no construct claims may still have an indented body (a typo in + // a keyword, say). Its lines follow the task rather than vanish, so they + // stay visible and can be fixed. + if (block.children.length > 0) { + tail = appendChain(mergeBlocks(block.children, baseIndent + 4, keywords), tail); + } tail = makeInsert({ id: uid(), type: "TaskNode", text: text, followElement: tail, @@ -628,6 +634,24 @@ function mergeBlocks(lines, baseIndent, keywords) { return tail; } +/** + * Puts `tail` after the last element of `chain` (both InsertNode-rooted + * sequences, as mergeBlocks builds them). + */ +function appendChain(chain, tail) { + let parent = null; + let node = chain; + // Walk InsertNode → element → InsertNode … to the closing InsertNode. + while (node.followElement) { + parent = node.followElement; + if (parent.type === "Placeholder") return tail; + node = parent.followElement; + } + if (!parent) return tail; + parent.followElement = tail; + return chain; +} + /** * Parse pseudocode string into a struktog tree. * @param {string} source - The pseudocode diff --git a/packages/struktolab/src/common/pseudocode-parser.test.js b/packages/struktolab/src/common/pseudocode-parser.test.js new file mode 100644 index 0000000..95fade8 --- /dev/null +++ b/packages/struktolab/src/common/pseudocode-parser.test.js @@ -0,0 +1,17 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { KEYWORDS_EN, parsePseudocode } from "./pseudocode-parser.js"; +import { treeToPseudocode } from "./tree-to-pseudocode.js"; + +const roundTrip = (code) => treeToPseudocode(parsePseudocode(code, KEYWORDS_EN), KEYWORDS_EN); + +test("keeps loops and their bodies", () => { + const code = 'repeat while x > 0:\n x = x - 1\noutput("x")'; + assert.equal(roundTrip(code), code); +}); + +test("keeps the body of a line it does not understand", () => { + // "while" alone is not a loop ("repeat while" is); its body used to vanish. + const out = roundTrip('while x > 0:\n x = x - 1\n output("x")\ny = 1'); + assert.equal(out, 'while x > 0:\nx = x - 1\noutput("x")\ny = 1'); +});