mirror of
https://github.com/ParkerTenBroeck/automata.git
synced 2026-06-06 21:24:06 -04:00
started adding simulators
This commit is contained in:
parent
61d7edd929
commit
22ef009122
5 changed files with 190 additions and 68 deletions
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
@ -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";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue