diff --git a/README.md b/README.md index 8344821..c0a921a 100644 --- a/README.md +++ b/README.md @@ -145,9 +145,10 @@ or `while`'s condition; not a bare name or literal): becomes its value with its last child. A call with a single `return ` is replaced by that expression's tree. Any other call, once its box opens, has the box in its node's place - the parent's - edge leads to the box's top, its siblings stay on their row - with the - note why it isn't replaced at its top; inside it, each line has its own - tree and calls of their own boxes. + edge leads to the box's top, its siblings stay on their row - without + the note of the substitution mode (the box in the call's place shows it + stays a box); inside it, each line has its own tree and calls of their + own boxes. 3. **Close:** one step from the trivial tree to the value in the line. For a call of a function of the program's own, each edge to an argument is @@ -298,8 +299,9 @@ Long labels aren't shortened, unlike the visualizer's trees. ### Copying the evaluation as a → chain -The **Copy → chain** item in the toolbar's **⋯ Options** menu copies the evaluation of the statement -the scrubber is on (for a visualized REPL entry: that input) in the course's +The **Copy → chain** item in the toolbar's **⋯ Options** menu copies the whole evaluation of the +statement the scrubber is on - from its first step to its value, at whichever of its steps the +scrubber is (for a visualized REPL entry: that input) - in the course's paper notation: its expression, then one line per change, each starting with `→`, taking the same steps as the visualizer. A call to a function with a single `return ` is replaced as in substitution mode. Any other call is diff --git a/scripts/check-console-browser.mjs b/scripts/check-console-browser.mjs index 5cd6c17..56c9261 100644 --- a/scripts/check-console-browser.mjs +++ b/scripts/check-console-browser.mjs @@ -708,7 +708,8 @@ try { failed += 1 console.error(`Baum: the box in the call node's place: ${error.message}`) } - // The hint at a call that stays a box, in both modes. + // The hint at a call that stays a box: in «Ersetzung», not in «Baum» + // (the box in the call's place says it). for (const name of ['Ersetzung', 'Baum']) { try { await mode(name) @@ -718,10 +719,15 @@ try { for (let index = 0; index < 6; index++) await next.click() // Not a closing box's fading copy (.visualizer-closing-boxes). const hint = frame.locator('.visualizer-view > .visualizer-canvas > .frame-box .frame-hint[data-box-reason="locals"]') - await hint.waitFor({ timeout: 5000 }) - assert.equal(await hint.innerText(), 'notenschnitt hat lokale Variablen – der Aufruf wird als Kasten gezeigt.') + if (name === 'Ersetzung') { + await hint.waitFor({ timeout: 5000 }) + assert.equal(await hint.innerText(), 'notenschnitt hat lokale Variablen – der Aufruf wird als Kasten gezeigt.') + } else { + await frame.locator('.visualizer-view > .visualizer-canvas [data-tree-box-key] .frame-box').first().waitFor({ timeout: 5000 }) + assert.equal(await frame.locator('.visualizer-view > .visualizer-canvas .frame-hint').count(), 0, 'no hint in «Baum»') + } assert.equal(await frame.locator('.visualizer-mode button', { hasText: name }).getAttribute('aria-pressed'), 'true') - console.log(`Baum: the hint at the box in «${name}»: ok`) + console.log(`Baum: the hint at the box in «${name}»${name === 'Baum' ? ': none' : ''}: ok`) } catch (error) { failed += 1 console.error(`Baum: the hint in «${name}»: ${error.message}`) diff --git a/scripts/check-tree-mode.mjs b/scripts/check-tree-mode.mjs index 943017a..96381d0 100644 --- a/scripts/check-tree-mode.mjs +++ b/scripts/check-tree-mode.mjs @@ -163,5 +163,18 @@ check('notenschnitt([4, 5, 4.5]) + 1: the box in the call node\'s place, 1 besid assert.ok(episode.expansions.includes(enter.step) && episode.expansions.includes(close.step), 'laid out again where the box opens and closes') }) +check('the → chain export: the whole statement, wherever the scrubber is in it', () => { + for (const name of ['shortForm', 'locals', 'boxInPlace']) { + const result = results[name] + const nodeIndexByFile = new Map(Object.entries(result.ast).map(([path, file]) => [path, indexAstNodes(file)])) + const chainAt = (step) => buildEvaluationChain({ trace: result.trace, ast: result.ast, nodeIndexByFile, sources: { 'main.py': programs[name] }, rootPath: 'main.py', step, errorMessage: null, comments: false, strings: { conditionComment: () => '', localsComment: '' } }) + const last = result.trace.at(-1) + const full = chainAt(last) + const ofStatement = result.trace.filter((step) => chainAt(step) !== null && chainAt(step).split('\n')[0] === full.split('\n')[0]) + assert.ok(ofStatement.length > 3, `${name}: steps of the statement`) + for (const step of [ofStatement[0], ofStatement[Math.floor(ofStatement.length / 2)], ofStatement.at(-1)]) assert.equal(chainAt(step), full, `${name}: at step ${step.step}`) + } +}) + if (failed > 0) process.exit(1) console.log('Tree mode checks passed.') diff --git a/src/GlPythonWorkspace.tsx b/src/GlPythonWorkspace.tsx index 8494a82..612653d 100644 --- a/src/GlPythonWorkspace.tsx +++ b/src/GlPythonWorkspace.tsx @@ -1058,7 +1058,9 @@ function Frame(props: FrameProps) { ) } - const showBoxHint = substitutionMode && scope.kind === 'call' && !insideBox + // Not in the tree mode: there the box stands in the call's place, which + // shows by itself that the call stays a box. + const showBoxHint = substitutionMode && trees === null && scope.kind === 'call' && !insideBox const boxReason = showBoxHint ? trace.find((step) => step.kind === 'call-enter' && step.frameId === frameId)?.boxReason ?? null : null return (