Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/kind-lines-stay.md
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions packages/struktolab/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
24 changes: 24 additions & 0 deletions packages/struktolab/src/common/pseudocode-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down
17 changes: 17 additions & 0 deletions packages/struktolab/src/common/pseudocode-parser.test.js
Original file line number Diff line number Diff line change
@@ -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');
});
Loading