simulation

This commit is contained in:
ParkerTenBroeck 2026-01-10 20:14:39 -05:00
parent 22ef009122
commit c7309a75d9
2 changed files with 146 additions and 67 deletions

View file

@ -1,4 +1,4 @@
import { updateVisualization } from "./visualizer.ts"; import { network, updateVisualization } from "./visualizer.ts";
export type Machine = Fa | Pda | Tm; export type Machine = Fa | Pda | Tm;
@ -203,21 +203,58 @@ export type Tm = {
edges: Map<string, Edge[]>; edges: Map<string, Edge[]>;
}; };
export type FaState = { export type SimStepResult = "pending" | "accept" | "reject";
state: State;
position: number; export class FaState {
readonly state: State;
readonly position: number;
readonly input: string;
readonly accepted: boolean = false;
private repr!: string;
constructor(state: State, position: number, input: string){
this.state=state;
this.position=position;
this.input = input;
}
toString(): string{
if(!this.repr) this.repr = this.state + " " + this.position;
return this.repr
}
}; };
export class FaSim { export class FaSim {
step(): string {
return ""; current_states: Map<string, FaState[]> = new Map();
accepted: FaState[] = []
step(): SimStepResult {
return "pending";
} }
} }
export type PdaState = { export class PdaState {
state: State; readonly state: State;
stack: Symbol[]; readonly stack: Symbol[];
position: number;
readonly position: number;
readonly input: string;
readonly accepted: boolean = false
private repr!: string;
constructor(state: State, stack: Symbol[], position: number, input: string){
this.state=state;
this.stack=stack;
this.position=position;
this.input = input;
}
toString(): string{
if(!this.repr) this.repr = this.state + " [" + this.stack + "]" + " " + this.position;
return this.repr
}
}; };
export class PdaSim { export class PdaSim {
@ -225,28 +262,43 @@ export class PdaSim {
paths: PdaState[]; paths: PdaState[];
input: string; input: string;
current_states: Map<string, PdaState[]> = new Map();
accepted: PdaState[] = []
constructor(machine: Pda, input: string) { constructor(machine: Pda, input: string) {
this.machine = machine; this.machine = machine;
this.paths = [{ this.paths = [new PdaState(machine.initial_state, [machine.initial_stack], 0, input)];
state: machine.initial_state, this.current_states.set(machine.initial_state, [this.paths[0]])
stack: [machine.initial_stack],
position: 0,
}];
this.input = input; this.input = input;
} }
step(): string { step(): SimStepResult {
const paths = []; if (this.paths.length == 0) return "reject";
console.log(this.paths); if (this.accepted.length != 0) return "accept";
const paths: PdaState[] = [];
this.current_states.clear();
const push = (state: PdaState) => {
paths.push(state);
if (!this.current_states.has(state.state)) this.current_states.set(state.state, []);
this.current_states.get(state.state)?.push(state);
if (
state.position == this.input.length && this.machine.final_states &&
this.machine.final_states.has(state.state)
||
state.position == this.input.length && !this.machine.final_states &&
state.stack.length == 1 && state.stack[0] == this.machine.initial_stack
) {
// @ts-expect-error sillllyyyy
state.accepted = true
this.accepted.push(state);
}
};
for (const path of 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 stack = path.stack.pop()!;
const letter_map = this.machine.transitions_components.get(path.state) const letter_map = this.machine.transitions_components.get(path.state)
@ -254,11 +306,7 @@ export class PdaSim {
if (!letter_map) continue; if (!letter_map) continue;
for (const to of letter_map.get(null) ?? []) { for (const to of letter_map.get(null) ?? []) {
paths.push({ push(new PdaState(to.state, path.stack.concat(to.stack), path.position, this.input));
state: to.state,
position: path.position,
stack: path.stack.concat(to.stack),
});
} }
if (path.position >= this.input.length) continue; if (path.position >= this.input.length) continue;
@ -266,19 +314,51 @@ export class PdaSim {
const char = this.input.charAt(path.position); const char = this.input.charAt(path.position);
for (const to of letter_map.get(char) ?? []) { for (const to of letter_map.get(char) ?? []) {
paths.push({ push(new PdaState(to.state, path.stack.concat(to.stack), path.position+1, this.input));
state: to.state,
position: path.position + 1,
stack: path.stack.concat(to.stack),
});
} }
} }
this.paths = paths; this.paths = paths;
return paths.length == 0 ? "reject" : "pending";
if (this.paths.length == 0) return "reject";
if (this.accepted.length != 0) return "accept";
return "pending"
} }
} }
export type Sim = FaSim | PdaSim | null export class TmState{
readonly state: State;
readonly tape: Symbol[];
readonly position: number;
readonly input: string;
readonly accepted: boolean = false
private repr!: string;
constructor(state: State, tape: Symbol[], position: number, input: string){
this.state=state;
this.tape = tape;
this.position=position;
this.input = input;
}
toString(): string{
if(!this.repr) this.repr = this.state + " " + this.position;
return this.repr
}
}
export class TmSim {
current_states: Map<string, TmState[]> = new Map();
accepted: TmState[] = []
step(): SimStepResult {
return "pending"
}
}
export type Sim = FaSim | PdaSim | TmSim | null
export let sim: Sim = null; export let sim: Sim = null;
export let automaton: Machine = { export let automaton: Machine = {
@ -298,6 +378,7 @@ export function clearSimulation(){
export function setSimulation(sim_: Sim){ export function setSimulation(sim_: Sim){
sim = sim_; sim = sim_;
network.redraw()
} }
export function setAutomaton(auto: Machine) { export function setAutomaton(auto: Machine) {
@ -323,6 +404,7 @@ export function stepSimulation(): void {
if (sim) { if (sim) {
console.log(sim.step()); console.log(sim.step());
} }
network.redraw()
} }
export function resetSimulation(): void { export function resetSimulation(): void {
@ -330,7 +412,7 @@ export function resetSimulation(): void {
case "fa": case "fa":
break; break;
case "pda": case "pda":
setSimulation(new PdaSim(automaton as Pda, "aabb")); setSimulation(new PdaSim(automaton as Pda, "aabbaabbaa"));
break; break;
case "tm": case "tm":
break; break;

View file

@ -2,9 +2,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 { automaton, setAutomaton, sim } from "./automata.ts";
import { automaton, Machine, setAutomaton } from "./automata.ts";
import { getText } from "./editor.ts";
export const nodes = new vis.DataSet<vis.Node>(); export const nodes = new vis.DataSet<vis.Node>();
export const edges = new vis.DataSet<vis.Edge>(); export const edges = new vis.DataSet<vis.Edge>();
@ -298,7 +296,7 @@ function renderNode({
const isFinal = automaton.final_states const isFinal = automaton.final_states
? automaton.final_states.has(id) ? automaton.final_states.has(id)
: false; : false;
const isActive = false; const isActive = sim?sim.current_states.has(id):false;
const fill = selected ? t.bg_2 : hover ? t.bg_1 : t.bg_0; const fill = selected ? t.bg_2 : hover ? t.bg_1 : t.bg_0;
const stroke = isActive ? t.current_node_border : t.node_border; const stroke = isActive ? t.current_node_border : t.node_border;
@ -340,34 +338,33 @@ function renderNode({
drawInitialArrow(ctx, x, y, r, t.edge); drawInitialArrow(ctx, x, y, r, t.edge);
} }
// const badgeText = "bleh\npee"; if (isActive) {
// if (badgeText) { const paths = sim?.current_states.get(id)!;
// const lines = badgeText.split("\n").slice(0, 3); const padX = 8;
// const padX = 8; const padY = 6;
// const padY = 6; const lineH = 14;
// const lineH = 14;
// let w = 0; let w = 0;
// for (const ln of lines) w = Math.max(w, ctx.measureText(ln).width); for (const ln of paths) w = Math.max(w, ctx.measureText(ln.toString()).width);
// const boxW = w + padX * 2; const boxW = w + padX * 2;
// const boxH = lines.length * lineH + padY * 2; const boxH = paths.length * lineH + padY * 2;
// const bx = x - boxW / 2; const bx = x - boxW / 2;
// const by = y - r - 12 - boxH; const by = y - r - 12 - boxH;
// ctx.fillStyle = t.bg_1; ctx.fillStyle = t.bg_1;
// ctx.strokeStyle = t.bg_2; ctx.strokeStyle = t.bg_2;
// ctx.lineWidth = 1; ctx.lineWidth = 1;
// roundRect(ctx, bx, by, boxW, boxH, 8); roundRect(ctx, bx, by, boxW, boxH, 8);
// ctx.fill(); ctx.fill();
// ctx.stroke(); ctx.stroke();
// ctx.fillStyle = t.fg_0; ctx.textBaseline = "top";
// ctx.textBaseline = "top"; for (let i = 0; i < paths.length; i++) {
// for (let i = 0; i < lines.length; i++) { ctx.fillStyle = paths[i].accepted?t.current_node_border:t.fg_0;
// ctx.fillText(lines[i], x, by + padY + i * lineH); ctx.fillText(paths[i].toString(), x, by + padY + i * lineH);
// } }
// } }
const node: vis.Node = nodes.get(id)!; const node: vis.Node = nodes.get(id)!;
const physicsOff = node.physics === false; const physicsOff = node.physics === false;