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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ There is no unit test framework. The automated checks are `test:protocol` (plain
- Values are shown through `_glpython_safe_repr`, which writes types as learners do (`int`, not `<class 'int'>`); the program's own output keeps Python's repr.
- It abbreviates long values (`[1, 2, 3, 4, 5, …, 100]`; levels in `_GLPYTHON_ABBREVIATION_LEVELS`) and notes each full text. The next recorded step gets `fullValues` (`{shown text: index}`), the trace result `values` (`{text, cut, length, kind}`). The UI (`fullValuesUpTo`, `ValueText`, `LocalsValue`, `FullValueDialog`) opens one on click.
- A name indexed in a subscript (`dauer` in `dauer[1]`, `_glpython_indexed`) gets no step of its own, so `dauer[1]` is one step to the element.
- A display (`[…]`, `(…)`, `{…}`) gets no step of its own (`_glpython_record_display`), only where its value reads differently (unpacking, duplicates); nor does a negative number literal (`_glpython_is_negative_number`). Every step must change something.
- A display (`[…]`, `(…)`, `{…}`) gets no step of its own (`_glpython_record_display`), only where its value reads differently (unpacking, duplicates); nor does a negative number literal (`_glpython_is_negative_number`), or `-name` where it already reads as its value. Every step must change something.

Worker messages:
- `run` → `result`/`error`; while it runs, `output` messages (`{id, text}`, `id` undefined for a program, the input's id for a REPL input) stream what was printed, at most 50 per 50 ms. The final message brings the whole output again.
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,9 @@ no steps at all, nested at any depth and with negative numbers (`-3`, `-2.5`):
only the parts that change have steps: `[1 + 2, 3, 4] → [3, 3, 4]`,
`["+", 1, ["*", n, 3]] → ["+", 1, ["*", 2, 3]]`. A display keeps its step only
where its value reads differently: unpacking (`[*a, 2] → [1, 2]`) and a set or
dict whose duplicates go (`{1, 1} → {1}`).
dict whose duplicates go (`{1, 1} → {1}`). Likewise `-x` with `x = 7` is one
step, to `-7`; the minus has a step of its own only where it changes what is
shown (`-x` with `x = -7` reads `--7`, then `7`).

A long value is abbreviated, in the step and in the variables panel alike,
keeping its first elements and its last one: `[1, 2, 3, 4, 5, …, 100]`.
Expand Down
10 changes: 9 additions & 1 deletion scripts/check-console-browser.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,15 @@ try {
await page.waitForFunction(() => window.ready, null, { timeout: 120000 })
return page
}
const consoleText = (frame) => frame.locator('.console').innerText()
// The transcript as it reads, without the entries' Visualize buttons: next
// to a ">>>" line or on a line of their own, depending on the width.
const consoleText = (frame) => frame.locator('.console').evaluate((console) => {
const buttons = [...console.querySelectorAll('.repl-entry-visualize')]
for (const button of buttons) button.style.display = 'none'
const text = console.innerText
for (const button of buttons) button.style.display = ''
return text
})
const programLine = (frame) => frame.locator('.repl-line--program .repl-input')
// A REPL input, once the REPL is free again (until then it keeps the text).
const enterRepl = async (frame, text) => {
Expand Down
15 changes: 14 additions & 1 deletion scripts/check-runtime-lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,26 @@ def steps_on_line(result, line):
("a = 5", "w = [a, 2]", [("a", "5")], "[5, 2]"),
("x = 7", "w = [[1, 2], x]", [("x", "7")], "[[1, 2], 7]"),
("n = 2", 'w = ["+", 1, ["*", n, 3]]', [("n", "2")], "['+', 1, ['*', 2, 3]]"),
("n = 2", "w = (n, -n, {n: [n]})", [("n", "2"), ("n", "2"), ("-2", "-2"), ("n", "2"), ("n", "2")], "(2, -2, {2: [2]})"),
("n = 2", "w = (n, -n, {n: [n]})", [("n", "2"), ("n", "2"), ("n", "2"), ("n", "2")], "(2, -2, {2: [2]})"),
("", "w = [len([1, 2]), 3]", [("len(...)", "2")], "[2, 3]"),
]:
result = trace(f"{setup}\n{line}\n")
expect(f"one step each: {line}", evals_on_line(result, 2), expected)
expect(f"the assignment: {line}", [label for kind, label, _ in steps_on_line(result, 2) if kind == "exec"], [f"w = {value}"])

# "-x" is one step, to x's value, where that already reads as the result;
# where it doesn't ("--7", "-True", "-(a + b)"), the minus has its step.
for setup, line, expected in [
("x = 7", "w = -x", [("x", "7")]),
("x = 2.5", "w = -x", [("x", "2.5")]),
("x = -7", "w = -x", [("x", "-7"), ("--7", "7")]),
("x = True", "w = -x", [("x", "True"), ("-True", "-1")]),
("a = 1\nb = 2", "w = -(a + b)", [("a", "1"), ("b", "2"), ("1 + 2", "3"), ("-3", "-3")]),
]:
result = trace(f"{setup}\n{line}\n")
row = setup.count("\n") + 2
expect(f"unary: {setup}; {line}", evals_on_line(result, row), expected)

# Where the value reads differently from the display, the display has its
# step: unpacking, and duplicates in a set or dict. Unpacking as in Python.
for setup, line, expected in [
Expand Down
8 changes: 7 additions & 1 deletion src/runtime/visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,13 @@ def _eval_unaryop(node, scope):
if _glpython_is_negative_number(node):
return value
symbol = _GLPYTHON_OP_SYMBOLS.get(type(node.op), "?")
_glpython_record(node, "eval", f"{symbol}{_glpython_safe_repr(operand, 40)}", _glpython_safe_repr(value), _glpython_current_func())
text = f"{symbol}{_glpython_safe_repr(operand, 40)}"
value_text = _glpython_safe_repr(value)
# "-x" with x = 7 already reads "-7" once x is replaced: no step that
# changes nothing (but "-x" with x = -7 reads "--7", which becomes 7).
if isinstance(node.op, ast.USub) and isinstance(node.operand, ast.Name) and text == value_text:
return value
_glpython_record(node, "eval", text, value_text, _glpython_current_func())
return value

def _eval_boolop(node, scope):
Expand Down
Loading