started adding simulators

This commit is contained in:
Parker TenBroeck 2026-01-10 14:29:08 -05:00
parent 61d7edd929
commit 22ef009122
5 changed files with 190 additions and 68 deletions

View file

@ -1,3 +1,5 @@
import { updateVisualization } from "./visualizer.ts";
export type Machine = Fa | Pda | Tm; export type Machine = Fa | Pda | Tm;
export function machine_from_json(json: string): Machine { export function machine_from_json(json: string): Machine {
@ -60,7 +62,8 @@ export function machine_from_json(json: string): Machine {
const edge = from.state + "#" + to.state; const edge = from.state + "#" + to.state;
if (!machine.edges.has(edge)) machine.edges.set(edge, []); if (!machine.edges.has(edge)) machine.edges.set(edge, []);
machine.edges.get(edge)?.push({ 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, function: to.function,
transition: to.transition, transition: to.transition,
}); });
@ -162,7 +165,10 @@ export type Pda = {
final_states: Map<State, StateInfo> | null; final_states: Map<State, StateInfo> | null;
transitions: Map<PdaTransFrom, PdaTransTo[]>; transitions: Map<PdaTransFrom, PdaTransTo[]>;
transitions_components: Map<State, Map<Symbol, Map<Letter|null, PdaTransTo[]>>>; transitions_components: Map<
State,
Map<Symbol, Map<Letter | null, PdaTransTo[]>>
>;
edges: Map<string, Edge[]>; edges: Map<string, Edge[]>;
}; };
@ -197,12 +203,136 @@ export type Tm = {
edges: Map<string, Edge[]>; edges: Map<string, Edge[]>;
}; };
export type FaState = { export type FaState = {
state: State, state: State;
position: number position: number;
} };
export class FaSim { export class FaSim {
step(): string {
return "";
}
}
export type PdaState = {
state: State;
stack: Symbol[];
position: number;
};
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;
}
} }

View file

@ -1,3 +1,4 @@
import { resetSimulation, stepSimulation } from "./automata.ts";
import {nodes, edges, network} from "./visualizer.ts" import {nodes, edges, network} from "./visualizer.ts"
const togglePhysicsBtn = document.getElementById("togglePhysics") as HTMLButtonElement; 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; const resetSimBtn = document.getElementById("resetSim") as HTMLButtonElement;
function stepSimulation(): void {
console.log("step");
}
function resetSimulation(): void {
console.log("reset");
}
// ---- Physics toggle (styled label) ---- // ---- Physics toggle (styled label) ----
function setPhysicsButtonUI(enabled: boolean) { function setPhysicsButtonUI(enabled: boolean) {
togglePhysicsBtn.classList.toggle("active", enabled); togglePhysicsBtn.classList.toggle("active", enabled);
@ -106,12 +99,7 @@ stepBtn.onclick = () => {
}; };
resetSimBtn.onclick = () => { resetSimBtn.onclick = () => {
// Stop if running
if (running) setRunning(false); if (running) setRunning(false);
// Reset
resetSimulation(); resetSimulation();
// Optional: re-enable Step after reset
stepBtn.disabled = false; stepBtn.disabled = false;
}; };

View file

@ -20,8 +20,7 @@ import { closeBrackets } from "npm:@codemirror/autocomplete";
import wasm from "./wasm.ts" import wasm from "./wasm.ts"
import { terminalPlugin } from "./terminal.ts"; import { terminalPlugin } from "./terminal.ts";
import { setAutomaton } from "./visualizer.ts"; import { machine_from_json, setAutomaton } from "./automata.ts";
import { machine_from_json } from "./automata.ts";
import { sharedText } from "./share.ts"; import { sharedText } from "./share.ts";
import { examples } from "./examples.ts"; import { examples } from "./examples.ts";

View file

@ -104,9 +104,40 @@ d(qeq, b, z0) = (qmb, z0)
d(qmb, b, z0) = (qmb, z0)`, d(qmb, b, z0) = (qmb, z0)`,
), ),
new Example( new Example(
"NPDA", "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 `type=NPDA
Q = {q0, q1} // states Q = {q0, q1} // states
E = {a, b} // alphabet E = {a, b} // alphabet

View file

@ -3,7 +3,7 @@
// deno-lint-ignore no-import-prefix // deno-lint-ignore no-import-prefix
import * as vis from "npm:vis-network/standalone"; import * as vis from "npm:vis-network/standalone";
import { StateEffect } from "npm:@codemirror/state"; import { StateEffect } from "npm:@codemirror/state";
import { Machine } from "./automata.ts"; import { automaton, Machine, setAutomaton } from "./automata.ts";
import { getText } from "./editor.ts"; import { getText } from "./editor.ts";
export const nodes = new vis.DataSet<vis.Node>(); export const nodes = new vis.DataSet<vis.Node>();
@ -125,30 +125,6 @@ export function updateGraphTheme() {
setAutomaton(automaton) 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; let _measureCanvas: HTMLCanvasElement | null = null;
@ -163,9 +139,7 @@ export function measureTextWidth(text: string, font: string): number {
return ctx.measureText(text).width; return ctx.measureText(text).width;
} }
export function setAutomaton(auto: Machine) { export function updateVisualization() {
automaton = auto;
// Populate nodes // Populate nodes
for (const state of automaton.states.keys()) { for (const state of automaton.states.keys()) {
@ -186,7 +160,7 @@ export function setAutomaton(auto: Machine) {
} }
// Populate edges // 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 to_from = edge_id.split("#");
const vadjust = -getGraphTheme().edge_font_size * const vadjust = -getGraphTheme().edge_font_size *
Math.floor(transitions.length / 2); Math.floor(transitions.length / 2);
@ -202,7 +176,7 @@ export function setAutomaton(auto: Machine) {
font, font,
from: to_from[0], from: to_from[0],
to: to_from[1], 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 { } else {
edges.add({ edges.add({
@ -210,19 +184,19 @@ export function setAutomaton(auto: Machine) {
font, font,
from: to_from[0], from: to_from[0],
to: to_from[1], 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()) { 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); edges.remove(edge_id);
} }
} }
for (const node_id of nodes.getIds()) { 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); nodes.remove(node_id);
} }
} }