From 22ef00912275f6ca91cb7ba889f1ddea4176f6e7 Mon Sep 17 00:00:00 2001 From: Parker TenBroeck <51721964+ParkerTenBroeck@users.noreply.github.com> Date: Sat, 10 Jan 2026 14:29:08 -0500 Subject: [PATCH] started adding simulators --- web/root/src/automata.ts | 168 ++++++++++++++++++++++++++++++++----- web/root/src/controls.ts | 14 +--- web/root/src/editor.ts | 3 +- web/root/src/examples.ts | 33 +++++++- web/root/src/visualizer.ts | 40 ++------- 5 files changed, 190 insertions(+), 68 deletions(-) diff --git a/web/root/src/automata.ts b/web/root/src/automata.ts index c22eef7..e291461 100644 --- a/web/root/src/automata.ts +++ b/web/root/src/automata.ts @@ -1,3 +1,5 @@ +import { updateVisualization } from "./visualizer.ts"; + export type Machine = Fa | Pda | Tm; export function machine_from_json(json: string): Machine { @@ -26,16 +28,16 @@ export function machine_from_json(json: string): Machine { for (const [from, tos] of machine.transitions) { for (const to of tos) { const layer_0 = machine.transitions_components; - if(!layer_0.has(from.state)) layer_0.set(from.state, new Map()); + if (!layer_0.has(from.state)) layer_0.set(from.state, new Map()); const layer_1 = machine.transitions_components.get(from.state)!; - if(!layer_1.has(from.letter)) layer_1.set(from.letter, []); + if (!layer_1.has(from.letter)) layer_1.set(from.letter, []); const layer_2 = layer_1.get(from.letter)!; layer_2.push(to); const edge = from.state + "#" + to.state; if (!machine.edges.has(edge)) machine.edges.set(edge, []); machine.edges.get(edge)?.push({ - repr: from.letter?from.letter:"ε", + repr: from.letter ? from.letter : "ε", function: to.function, transition: to.transition, }); @@ -49,18 +51,19 @@ export function machine_from_json(json: string): Machine { for (const [from, tos] of machine.transitions) { for (const to of tos) { const layer_0 = machine.transitions_components; - if(!layer_0.has(from.state)) layer_0.set(from.state, new Map()); + if (!layer_0.has(from.state)) layer_0.set(from.state, new Map()); const layer_1 = machine.transitions_components.get(from.state)!; - if(!layer_1.has(from.symbol)) layer_1.set(from.symbol, new Map()); + if (!layer_1.has(from.symbol)) layer_1.set(from.symbol, new Map()); const layer_2 = layer_1.get(from.symbol)!; - if(!layer_2.has(from.letter)) layer_2.set(from.letter, []); + if (!layer_2.has(from.letter)) layer_2.set(from.letter, []); const layer_3 = layer_2.get(from.letter)!; layer_3.push(to); const edge = from.state + "#" + to.state; if (!machine.edges.has(edge)) machine.edges.set(edge, []); machine.edges.get(edge)?.push({ - repr: (from.letter?from.letter:"ε")+","+from.symbol+"->["+to.stack+"]", + repr: (from.letter ? from.letter : "ε") + "," + from.symbol + + "->[" + to.stack + "]", function: to.function, transition: to.transition, }); @@ -74,16 +77,16 @@ export function machine_from_json(json: string): Machine { for (const [from, tos] of machine.transitions) { for (const to of tos) { const layer_0 = machine.transitions_components; - if(!layer_0.has(from.state)) layer_0.set(from.state, new Map()); + if (!layer_0.has(from.state)) layer_0.set(from.state, new Map()); const layer_1 = machine.transitions_components.get(from.state)!; - if(!layer_1.has(from.symbol)) layer_1.set(from.symbol, []); + if (!layer_1.has(from.symbol)) layer_1.set(from.symbol, []); const layer_2 = layer_1.get(from.symbol)!; layer_2.push(to); const edge = from.state + "#" + to.state; if (!machine.edges.has(edge)) machine.edges.set(edge, []); machine.edges.get(edge)?.push({ - repr: from.symbol+"->"+to.symbol+","+to.direction, + repr: from.symbol + "->" + to.symbol + "," + to.direction, function: to.function, transition: to.transition, }); @@ -107,7 +110,7 @@ export type SymbolInfo = { definition: Span }; export type FaTransFrom = { state: State; - letter: Letter|null; + letter: Letter | null; }; export type FaTransTo = { @@ -132,14 +135,14 @@ export type Fa = { final_states: Map; transitions: Map; - transitions_components: Map>; + transitions_components: Map>; edges: Map; }; export type PdaTransFrom = { state: State; - letter: Letter|null; + letter: Letter | null; symbol: Symbol; }; @@ -162,7 +165,10 @@ export type Pda = { final_states: Map | null; transitions: Map; - transitions_components: Map>>; + transitions_components: Map< + State, + Map> + >; edges: Map; }; @@ -197,12 +203,136 @@ export type Tm = { edges: Map; }; - export type FaState = { - state: State, - position: number + state: State; + position: number; +}; + +export class FaSim { + step(): string { + return ""; + } } -export class FaSim{ +export type PdaState = { + state: State; + stack: Symbol[]; + position: number; +}; -} \ No newline at end of file +export class PdaSim { + machine: Pda; + paths: PdaState[]; + input: string; + + constructor(machine: Pda, input: string) { + this.machine = machine; + this.paths = [{ + state: machine.initial_state, + stack: [machine.initial_stack], + position: 0, + }]; + this.input = input; + } + + step(): string { + const paths = []; + console.log(this.paths); + for (const path of this.paths) { + if ( + path.position == this.input.length && this.machine.final_states && + this.machine.final_states.has(path.state) + ) return "accept"; + if ( + path.position == this.input.length && !this.machine.final_states && + path.stack.length == 1 && path.stack[0] == this.machine.initial_stack + ) return "accept"; + + const stack = path.stack.pop()!; + const letter_map = this.machine.transitions_components.get(path.state) + ?.get(stack); + if (!letter_map) continue; + + for (const to of letter_map.get(null) ?? []) { + paths.push({ + state: to.state, + position: path.position, + stack: path.stack.concat(to.stack), + }); + } + + if (path.position >= this.input.length) continue; + + const char = this.input.charAt(path.position); + + for (const to of letter_map.get(char) ?? []) { + paths.push({ + state: to.state, + position: path.position + 1, + stack: path.stack.concat(to.stack), + }); + } + } + this.paths = paths; + return paths.length == 0 ? "reject" : "pending"; + } +} + +export type Sim = FaSim | PdaSim | null +export let sim: Sim = null; + +export let automaton: Machine = { + type: "fa", + alphabet: new Map(), + final_states: new Map(), + initial_state: "", + states: new Map(), + transitions: new Map(), + transitions_components: new Map(), + edges: new Map(), +}; + +export function clearSimulation(){ + setSimulation(null); +} + +export function setSimulation(sim_: Sim){ + sim = sim_; +} + +export function setAutomaton(auto: Machine) { + automaton = auto; + sim = null; + updateVisualization() +} + +export function clearAutomaton() { + setAutomaton({ + type: "fa", + alphabet: new Map(), + final_states: new Map(), + initial_state: "", + states: new Map(), + transitions: new Map(), + transitions_components: new Map(), + edges: new Map(), + }); +} + +export function stepSimulation(): void { + if (sim) { + console.log(sim.step()); + } +} + +export function resetSimulation(): void { + switch (automaton.type) { + case "fa": + break; + case "pda": + setSimulation(new PdaSim(automaton as Pda, "aabb")); + break; + case "tm": + break; + } +} diff --git a/web/root/src/controls.ts b/web/root/src/controls.ts index 58a6f38..d54fe85 100644 --- a/web/root/src/controls.ts +++ b/web/root/src/controls.ts @@ -1,3 +1,4 @@ +import { resetSimulation, stepSimulation } from "./automata.ts"; import {nodes, edges, network} from "./visualizer.ts" const togglePhysicsBtn = document.getElementById("togglePhysics") as HTMLButtonElement; @@ -9,14 +10,6 @@ const speedLabel = document.getElementById("speedLabel") as HTMLSpanEle const resetSimBtn = document.getElementById("resetSim") as HTMLButtonElement; -function stepSimulation(): void { - console.log("step"); -} - -function resetSimulation(): void { - console.log("reset"); -} - // ---- Physics toggle (styled label) ---- function setPhysicsButtonUI(enabled: boolean) { togglePhysicsBtn.classList.toggle("active", enabled); @@ -106,12 +99,7 @@ stepBtn.onclick = () => { }; resetSimBtn.onclick = () => { - // Stop if running if (running) setRunning(false); - - // Reset resetSimulation(); - - // Optional: re-enable Step after reset stepBtn.disabled = false; }; \ No newline at end of file diff --git a/web/root/src/editor.ts b/web/root/src/editor.ts index 344ca61..334d0b8 100644 --- a/web/root/src/editor.ts +++ b/web/root/src/editor.ts @@ -20,8 +20,7 @@ import { closeBrackets } from "npm:@codemirror/autocomplete"; import wasm from "./wasm.ts" import { terminalPlugin } from "./terminal.ts"; -import { setAutomaton } from "./visualizer.ts"; -import { machine_from_json } from "./automata.ts"; +import { machine_from_json, setAutomaton } from "./automata.ts"; import { sharedText } from "./share.ts"; import { examples } from "./examples.ts"; diff --git a/web/root/src/examples.ts b/web/root/src/examples.ts index 7987d8e..f9cb9c4 100644 --- a/web/root/src/examples.ts +++ b/web/root/src/examples.ts @@ -104,9 +104,40 @@ d(qeq, b, z0) = (qmb, z0) d(qmb, b, z0) = (qmb, z0)`, ), + new Example( "NPDA", - "unequal", + "palindrome", + `type=NPDA +Q = {q0, q1} // states +E = {a, b} // alphabet +T = {z0, A, B} // stack +q0 = q0 +z0 = z0 + +// push letters we see to stack +d(q0, a, z0) = (q0, [A z0]) +d(q0, b, z0) = (q0, [B z0]) + +d(q0, a, A) = (q0, [A A]) +d(q0, b, A) = (q0, [B A]) + +d(q0, a, B) = (q0, [A B]) +d(q0, b, B) = (q0, [B B]) + +// transition to q1 +d(q0, epsilon, z0) = { (q1, z0) } +d(q0, epsilon, A) = { (q1, A) } +d(q0, epsilon, B) = { (q1, B) } + +// consume stack until empty +d(q1, a, A) = { (q1, epsilon) } +d(q1, b, B) = { (q1, epsilon) }`, + ), + + new Example( + "NPDA", + "kleen star stack", `type=NPDA Q = {q0, q1} // states E = {a, b} // alphabet diff --git a/web/root/src/visualizer.ts b/web/root/src/visualizer.ts index dc0daaf..292776c 100644 --- a/web/root/src/visualizer.ts +++ b/web/root/src/visualizer.ts @@ -3,7 +3,7 @@ // deno-lint-ignore no-import-prefix import * as vis from "npm:vis-network/standalone"; import { StateEffect } from "npm:@codemirror/state"; -import { Machine } from "./automata.ts"; +import { automaton, Machine, setAutomaton } from "./automata.ts"; import { getText } from "./editor.ts"; export const nodes = new vis.DataSet(); @@ -125,30 +125,6 @@ export function updateGraphTheme() { setAutomaton(automaton) } -let automaton: Machine = { - type: "fa", - alphabet: new Map(), - final_states: new Map(), - initial_state: "", - states: new Map(), - transitions: new Map(), - transitions_components: new Map(), - edges: new Map(), -}; - -export function clearAutomaton() { - automaton = { - type: "fa", - alphabet: new Map(), - final_states: new Map(), - initial_state: "", - states: new Map(), - transitions: new Map(), - transitions_components: new Map(), - edges: new Map(), - }; -} - let _measureCanvas: HTMLCanvasElement | null = null; @@ -163,9 +139,7 @@ export function measureTextWidth(text: string, font: string): number { return ctx.measureText(text).width; } -export function setAutomaton(auto: Machine) { - automaton = auto; - +export function updateVisualization() { // Populate nodes for (const state of automaton.states.keys()) { @@ -186,7 +160,7 @@ export function setAutomaton(auto: Machine) { } // Populate edges - for (const [edge_id, transitions] of auto.edges) { + for (const [edge_id, transitions] of automaton.edges) { const to_from = edge_id.split("#"); const vadjust = -getGraphTheme().edge_font_size * Math.floor(transitions.length / 2); @@ -202,7 +176,7 @@ export function setAutomaton(auto: Machine) { font, from: to_from[0], to: to_from[1], - label: transitions.map(i => i.repr).join(auto.type=="fa"?",":"\n"), + label: transitions.map(i => i.repr).join(automaton.type=="fa"?",":"\n"), }); } else { edges.add({ @@ -210,19 +184,19 @@ export function setAutomaton(auto: Machine) { font, from: to_from[0], to: to_from[1], - label: transitions.map(i => i.repr).join(auto.type=="fa"?",":"\n"), + label: transitions.map(i => i.repr).join(automaton.type=="fa"?",":"\n"), }); } } for (const edge_id of edges.getIds()) { - if (!auto.edges.has(edge_id as string)) { + if (!automaton.edges.has(edge_id as string)) { edges.remove(edge_id); } } for (const node_id of nodes.getIds()) { - if (!auto.states.has(node_id as string)) { + if (!automaton.states.has(node_id as string)) { nodes.remove(node_id); } }