+
>
);
-}
\ No newline at end of file
+}
diff --git a/recursive_blocks/src/Trace.ts b/recursive_blocks/src/Trace.ts
new file mode 100644
index 0000000..abd7d47
--- /dev/null
+++ b/recursive_blocks/src/Trace.ts
@@ -0,0 +1,116 @@
+import { BlockData, collectDescendantIds } from "./BlockUtil";
+import { blockConfig, BlockEvaluator, ExecutionContext } from "./BlockConfig";
+
+export enum TraceEventType {
+ Enter,
+ Exit,
+ ClearSubtree
+}
+
+export type TraceEvent =
+ | { type: TraceEventType.Enter; blockId: string; inputs: number[] }
+ | { type: TraceEventType.Exit; blockId: string; inputs: number[]; output: number }
+ | { type: TraceEventType.ClearSubtree; blockIds: string[] };
+
+export async function generateTrace(
+ block: BlockData,
+ inputs: number[]
+): Promise<{ result: number; events: TraceEvent[] }> {
+ const events: TraceEvent[] = [];
+
+ const context: ExecutionContext = {
+ onClearSubtree: (blockToClear: BlockData) => {
+ events.push({ type: TraceEventType.ClearSubtree, blockIds: collectDescendantIds(blockToClear) });
+ }
+ };
+
+ const tracingEvaluator: BlockEvaluator = async (b, i, _eval, _ctx) => {
+ events.push({ type: TraceEventType.Enter, blockId: b.id, inputs: [...i] });
+ const config = blockConfig[b.type];
+ const result = await config.evaluate(b, i, tracingEvaluator, context);
+ events.push({ type: TraceEventType.Exit, blockId: b.id, inputs: [...i], output: result });
+ return result;
+ };
+
+ const result = await tracingEvaluator(block, inputs, tracingEvaluator, context);
+ return { result, events };
+}
+
+// Primitive Recursion Trace Panel
+export type PRTraceFrame = {
+ xy_prime: number[]; // x,y' inputs at the current recursion level
+ xy: number[] | null; // x,y inputs at the connected level (null at base case)
+ hxy: number | null; // h(x,y) result from the connected level
+ hxy_prime: number | null; // h(x,y') result at the current level
+};
+
+/**
+ * Build the frame sequence for a PR trace panel from a completed trace.
+ * Each Enter/Exit event for the given PR block produces exactly one frame.
+ * Descent frames show inputs flowing down, ascent frames show outputs building up.
+ */
+export function buildPRFrames(events: TraceEvent[], prBlockId: string): PRTraceFrame[] {
+ const frames: PRTraceFrame[] = [];
+ let depth = 0;
+ let prevExitOutput: number | null = null;
+ let lastDescentFrame: PRTraceFrame | null = null;
+
+ for (const e of events) {
+ if (e.type === TraceEventType.ClearSubtree) continue;
+ if (e.blockId !== prBlockId) continue;
+
+ if (e.type === TraceEventType.Enter) {
+ if (depth === 0) {
+ // Reset state on new invocations
+ prevExitOutput = null;
+ lastDescentFrame = null;
+ }
+ depth++;
+
+ const y = e.inputs[e.inputs.length - 1];
+ if (y > 0) {
+ const frame: PRTraceFrame = {
+ xy_prime: e.inputs,
+ xy: [...e.inputs.slice(0, -1), y - 1],
+ hxy: null,
+ hxy_prime: null,
+ };
+ lastDescentFrame = frame;
+ frames.push(frame);
+ } else if (lastDescentFrame) {
+ // Base case enter after descent, hold the last descent view
+ frames.push({ ...lastDescentFrame });
+ } else {
+ // Direct call with y = 0 is an edge case, no descent to hold, so show as own level
+ frames.push({ xy_prime: e.inputs, xy: null, hxy: null, hxy_prime: null });
+ }
+ } else if (e.type === TraceEventType.Exit) {
+ depth--;
+
+ const y = e.inputs[e.inputs.length - 1];
+ if (y === 0 && lastDescentFrame) {
+ // Base case exit after descent, stay at last descent view, fill in h(x,y)
+ frames.push({
+ xy_prime: lastDescentFrame.xy_prime,
+ xy: lastDescentFrame.xy,
+ hxy: e.output,
+ hxy_prime: null,
+ });
+ } else if (y === 0) {
+ // Direct call with y = 0, just show result
+ frames.push({ xy_prime: e.inputs, xy: null, hxy: null, hxy_prime: e.output });
+ } else {
+ // Ascent: show current level with result from the level below
+ frames.push({
+ xy_prime: e.inputs,
+ xy: [...e.inputs.slice(0, -1), y - 1],
+ hxy: prevExitOutput,
+ hxy_prime: e.output,
+ });
+ }
+ prevExitOutput = e.output;
+ }
+ }
+
+ return frames;
+}
diff --git a/recursive_blocks/src/ValueEditor.tsx b/recursive_blocks/src/ValueEditor.tsx
index be6d16d..bb7bbd4 100644
--- a/recursive_blocks/src/ValueEditor.tsx
+++ b/recursive_blocks/src/ValueEditor.tsx
@@ -1,6 +1,8 @@
import { useState, useEffect } from "react";
import { BlockData } from "./BlockUtil";
import { blockConfig } from "./BlockConfig";
+import { useBlockEditor } from "./BlockEditorContext";
+import { getInformalNotation } from "./Notation";
interface ValueEditorProps {
block: BlockData;
@@ -13,6 +15,7 @@ interface ValueEditorProps {
// Block is the block this value editor is on, and onUpdate is called when a value is updated.
export function ValueEditor({ block, onUpdate, isRunning = false }: ValueEditorProps) {
const [values, setValues] = useState(block.num_values ?? []);
+ const { editMode } = useBlockEditor();
useEffect(() => {
if (JSON.stringify(values) !== JSON.stringify(block.num_values)) {
@@ -27,33 +30,49 @@ export function ValueEditor({ block, onUpdate, isRunning = false }: ValueEditorP
};
if (!values || values.length === 0) {
- return;
+ const notation = getInformalNotation(block);
+ if (!notation) return;
+ return (
+
+
+
+ {notation.label}
+ {notation.value}
+
+
+
+ );
}
return (
-
- {values.map((val, index) => (
-
- {isRunning || block.immutable ? (
-
- {val.name} =
- {val.value}
-
- ) : (
- <>
-
- v.name === val.name)?.min || 0}
- step="1"
- value={val.value}
- onChange={(e) => handleValueChange(index, e.target.value)}
- className="value-input"
- />
- >
- )}
-
- ))}
+
+ {values.map((val, index) => {
+ const staticValue = getInformalNotation(block, val.name, val.value);
+ if (!staticValue) return null;
+
+ return (
+
+ {isRunning || !editMode ? (
+
+ {staticValue.label}
+ {staticValue.value}
+
+ ) : (
+ <>
+
+ v.name === val.name)?.min || 0}
+ step="1"
+ value={val.value}
+ onChange={(e) => handleValueChange(index, e.target.value)}
+ className="value-input"
+ />
+ >
+ )}
+
+ );
+ })}
);
-}
\ No newline at end of file
+}
diff --git a/recursive_blocks/vite.config.ts b/recursive_blocks/vite.config.ts
index 8b0f57b..d29a0e8 100644
--- a/recursive_blocks/vite.config.ts
+++ b/recursive_blocks/vite.config.ts
@@ -3,5 +3,6 @@ import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
+ base: "/",
plugins: [react()],
})