mirror of
https://github.com/ParkerTenBroeck/automata.git
synced 2026-06-07 05:28:45 -04:00
tm simulation, simulation code cleanup
This commit is contained in:
parent
7f519cd7f3
commit
2a777f3198
8 changed files with 460 additions and 266 deletions
|
|
@ -3,12 +3,11 @@ use std::collections::HashSet;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
delta_lower, dual_struct_serde,
|
delta_lower, dual_struct_serde, gamma_upper, loader::{
|
||||||
loader::{
|
|
||||||
BLANK_SYMBOL, Context, INITIAL_STATE, Spanned,
|
BLANK_SYMBOL, Context, INITIAL_STATE, Spanned,
|
||||||
ast::{self, Symbol as Sym},
|
ast::{self, Symbol as Sym},
|
||||||
log::LogSink,
|
log::LogSink,
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
dual_struct_serde! {
|
dual_struct_serde! {
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||||
|
|
@ -23,8 +22,11 @@ dual_struct_serde! {
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
|
#[serde(rename = "<")]
|
||||||
Left,
|
Left,
|
||||||
|
#[serde(rename = ">")]
|
||||||
Right,
|
Right,
|
||||||
|
#[serde(rename = "_")]
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -185,7 +187,7 @@ impl<'a, 'b> TmCompiler<'a, 'b> {
|
||||||
use ast::TopLevel as TL;
|
use ast::TopLevel as TL;
|
||||||
match element {
|
match element {
|
||||||
TL::Item(S("Q", _), list) => self.compile_states(list, span),
|
TL::Item(S("Q", _), list) => self.compile_states(list, span),
|
||||||
TL::Item(S(delta_lower!(pat), _), list) => self.compile_symbols(list, span),
|
TL::Item(S(gamma_upper!(pat), _), list) => self.compile_symbols(list, span),
|
||||||
TL::Item(S("F", _), list) => self.compile_final_states(list, span),
|
TL::Item(S("F", _), list) => self.compile_final_states(list, span),
|
||||||
TL::Item(S(INITIAL_STATE, _), item) => self.compile_initial_state(item, span),
|
TL::Item(S(INITIAL_STATE, _), item) => self.compile_initial_state(item, span),
|
||||||
TL::Item(S(BLANK_SYMBOL, _), item) => self.compile_blank_symbol(item, span),
|
TL::Item(S(BLANK_SYMBOL, _), item) => self.compile_blank_symbol(item, span),
|
||||||
|
|
@ -335,11 +337,11 @@ impl<'a, 'b> TmCompiler<'a, 'b> {
|
||||||
.emit_error("blank symbol already set", top_level)
|
.emit_error("blank symbol already set", top_level)
|
||||||
.emit_help("previously defined here", previous);
|
.emit_help("previously defined here", previous);
|
||||||
}
|
}
|
||||||
if self.states.contains_key(&State(ident)) {
|
if self.symbols.contains_key(&Symbol(ident)) {
|
||||||
self.blank_symbol = Some((Symbol(ident), top_level))
|
self.blank_symbol = Some((Symbol(ident), top_level))
|
||||||
} else {
|
} else {
|
||||||
self.ctx
|
self.ctx
|
||||||
.emit_error("blank symbol not defined as a state", src_d);
|
.emit_error("blank symbol not defined as a symbol", src_d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => _ = self.ctx.emit_error("expected ident", src_d),
|
_ => _ = self.ctx.emit_error("expected ident", src_d),
|
||||||
|
|
|
||||||
|
|
@ -100,11 +100,11 @@ export type State = string;
|
||||||
export type Symbol = string;
|
export type Symbol = string;
|
||||||
export type Letter = string;
|
export type Letter = string;
|
||||||
|
|
||||||
export type Span = [number, number];
|
export type Span = readonly [number, number];
|
||||||
|
|
||||||
export type StateInfo = { definition: Span };
|
export type StateInfo = { readonly definition: Span };
|
||||||
export type LetterInfo = { definition: Span };
|
export type LetterInfo = { readonly definition: Span };
|
||||||
export type SymbolInfo = { definition: Span };
|
export type SymbolInfo = { readonly definition: Span };
|
||||||
|
|
||||||
export type FaTransFrom = {
|
export type FaTransFrom = {
|
||||||
state: State;
|
state: State;
|
||||||
|
|
@ -112,16 +112,16 @@ export type FaTransFrom = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FaTransTo = {
|
export type FaTransTo = {
|
||||||
state: State;
|
readonly state: State;
|
||||||
|
|
||||||
transition: Span;
|
readonly transition: Span;
|
||||||
function: Span;
|
readonly function: Span;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Edge = {
|
export type Edge = {
|
||||||
repr: string;
|
readonly repr: string;
|
||||||
function: Span;
|
readonly function: Span;
|
||||||
transition: Span;
|
readonly transition: Span;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Fa = {
|
export type Fa = {
|
||||||
|
|
@ -139,17 +139,17 @@ export type Fa = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PdaTransFrom = {
|
export type PdaTransFrom = {
|
||||||
state: State;
|
readonly state: State;
|
||||||
letter: Letter | null;
|
readonly letter: Letter | null;
|
||||||
symbol: Symbol;
|
readonly symbol: Symbol;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PdaTransTo = {
|
export type PdaTransTo = {
|
||||||
state: State;
|
readonly state: State;
|
||||||
stack: Symbol[];
|
readonly stack: readonly Symbol[];
|
||||||
|
|
||||||
transition: Span;
|
readonly transition: Span;
|
||||||
function: Span;
|
readonly function: Span;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Pda = {
|
export type Pda = {
|
||||||
|
|
@ -172,17 +172,17 @@ export type Pda = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TmTransFrom = {
|
export type TmTransFrom = {
|
||||||
state: State;
|
readonly state: State;
|
||||||
symbol: Symbol;
|
readonly symbol: Symbol;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TmTransTo = {
|
export type TmTransTo = {
|
||||||
state: State;
|
readonly state: State;
|
||||||
symbol: Symbol;
|
readonly symbol: Symbol;
|
||||||
direction: "L" | "R" | "N";
|
readonly direction: "<" | ">" | "_";
|
||||||
|
|
||||||
transition: Span;
|
readonly transition: Span;
|
||||||
function: Span;
|
readonly function: Span;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Tm = {
|
export type Tm = {
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,30 @@ d(q0, epsilon, B) = { (q1, B) }
|
||||||
d(q1, a, A) = { (q1, epsilon) }
|
d(q1, a, A) = { (q1, epsilon) }
|
||||||
d(q1, b, B) = { (q1, epsilon) }`,
|
d(q1, b, B) = { (q1, epsilon) }`,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
new Example("TM", "a^nb^n",
|
||||||
|
`// accepts all strings on {a,b}+ of the form anbn
|
||||||
|
|
||||||
|
type = TM
|
||||||
|
Q = { q0, q1, q2, q3, q4 } // set of internal states
|
||||||
|
F = { q4 } // set of final states
|
||||||
|
T = { a, b, X, Y, B } // tape alphabet
|
||||||
|
B = B // the blank symbol (tape initializer symbol)
|
||||||
|
q0 = q0 // initial state
|
||||||
|
|
||||||
|
d(q0,a)=(q1,x,R)
|
||||||
|
d(q1,a)=(q1,a,R)
|
||||||
|
d(q1,Y)=(q1,y,R)
|
||||||
|
d(q1,b)=(q2,y,L)
|
||||||
|
|
||||||
|
d(q2,Y)=(q2,y,L)
|
||||||
|
d(q2,a)=(q2,a,L)
|
||||||
|
d(q2,X)=(q0,x,R)
|
||||||
|
|
||||||
|
d(q0,Y)=(q3,y,R)
|
||||||
|
d(q3,Y)=(q3,y,R)
|
||||||
|
d(q3,B)=(q4,B,R)
|
||||||
|
`)
|
||||||
];
|
];
|
||||||
|
|
||||||
const CATEGORY_ORDER: Category[] = [
|
const CATEGORY_ORDER: Category[] = [
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,21 @@
|
||||||
import { bus } from "./bus.ts";
|
import { bus } from "./bus.ts";
|
||||||
import type {
|
import type {
|
||||||
Fa,
|
|
||||||
Machine,
|
Machine,
|
||||||
|
Fa,
|
||||||
Pda,
|
Pda,
|
||||||
State,
|
|
||||||
Symbol,
|
|
||||||
Tm,
|
Tm,
|
||||||
} from "./automata.ts";
|
} from "./automata.ts";
|
||||||
import {parse_machine_from_json} from "./automata.ts";
|
import {parse_machine_from_json} from "./automata.ts";
|
||||||
|
|
||||||
|
import { FaSim } from "./simulation/fa.ts";
|
||||||
|
export { FaSim } from "./simulation/fa.ts";
|
||||||
|
|
||||||
|
import { PdaSim } from "./simulation/pda.ts";
|
||||||
|
export { PdaSim } from "./simulation/pda.ts";
|
||||||
|
|
||||||
|
import { TmSim } from "./simulation/tm.ts";
|
||||||
|
export { TmSim } from "./simulation/tm.ts";
|
||||||
|
|
||||||
export type SimStepResult = "pending" | "accept" | "reject";
|
export type SimStepResult = "pending" | "accept" | "reject";
|
||||||
export type Sim = FaSim | PdaSim | TmSim;
|
export type Sim = FaSim | PdaSim | TmSim;
|
||||||
|
|
||||||
|
|
@ -93,234 +100,3 @@ bus.on("automata/sim/after_step", ({result}) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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.input.substring(this.position);
|
|
||||||
}
|
|
||||||
return this.repr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FaSim {
|
|
||||||
machine: Fa;
|
|
||||||
paths: FaState[];
|
|
||||||
input: string;
|
|
||||||
|
|
||||||
current_states: Map<string, FaState[]> = new Map();
|
|
||||||
accepted: FaState[] = [];
|
|
||||||
|
|
||||||
constructor(machine: Fa, input: string) {
|
|
||||||
this.machine = machine;
|
|
||||||
this.paths = [new FaState(machine.initial_state, 0, input)];
|
|
||||||
this.current_states.set(machine.initial_state, [this.paths[0]]);
|
|
||||||
this.input = input;
|
|
||||||
}
|
|
||||||
|
|
||||||
step(): SimStepResult {
|
|
||||||
if (this.paths.length == 0) return "reject";
|
|
||||||
if (this.accepted.length != 0) return "accept";
|
|
||||||
|
|
||||||
const paths: FaState[] = [];
|
|
||||||
this.current_states.clear();
|
|
||||||
|
|
||||||
const push = (state: FaState) => {
|
|
||||||
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.has(state.state)
|
|
||||||
) {
|
|
||||||
// @ts-expect-error sillllyyyy
|
|
||||||
state.accepted = true;
|
|
||||||
this.accepted.push(state);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const path of this.paths) {
|
|
||||||
const letter_map = this.machine.transitions_components.get(path.state)!;
|
|
||||||
if (!letter_map) continue;
|
|
||||||
|
|
||||||
for (const to of letter_map.get(null) ?? []) {
|
|
||||||
push(new FaState(to.state, path.position, this.input));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.position >= this.input.length) continue;
|
|
||||||
|
|
||||||
const char = this.input.charAt(path.position);
|
|
||||||
|
|
||||||
for (const to of letter_map.get(char) ?? []) {
|
|
||||||
push(new FaState(to.state, path.position + 1, this.input));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.paths = paths;
|
|
||||||
|
|
||||||
if (this.paths.length == 0) return "reject";
|
|
||||||
if (this.accepted.length != 0) return "accept";
|
|
||||||
return "pending";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PdaState {
|
|
||||||
readonly state: State;
|
|
||||||
readonly stack: Symbol[];
|
|
||||||
|
|
||||||
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.input.substring(this.position);
|
|
||||||
}
|
|
||||||
return this.repr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PdaSim {
|
|
||||||
machine: Pda;
|
|
||||||
paths: PdaState[];
|
|
||||||
input: string;
|
|
||||||
|
|
||||||
current_states: Map<string, PdaState[]> = new Map();
|
|
||||||
accepted: PdaState[] = [];
|
|
||||||
|
|
||||||
constructor(machine: Pda, input: string) {
|
|
||||||
this.machine = machine;
|
|
||||||
this.paths = [
|
|
||||||
new PdaState(machine.initial_state, [machine.initial_stack], 0, input),
|
|
||||||
];
|
|
||||||
this.current_states.set(machine.initial_state, [this.paths[0]]);
|
|
||||||
this.input = input;
|
|
||||||
}
|
|
||||||
|
|
||||||
step(): SimStepResult {
|
|
||||||
if (this.paths.length == 0) return "reject";
|
|
||||||
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) {
|
|
||||||
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) ?? []) {
|
|
||||||
push(
|
|
||||||
new PdaState(
|
|
||||||
to.state,
|
|
||||||
path.stack.concat(to.stack),
|
|
||||||
path.position,
|
|
||||||
this.input,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.position >= this.input.length) continue;
|
|
||||||
|
|
||||||
const char = this.input.charAt(path.position);
|
|
||||||
|
|
||||||
for (const to of letter_map.get(char) ?? []) {
|
|
||||||
push(
|
|
||||||
new PdaState(
|
|
||||||
to.state,
|
|
||||||
path.stack.concat(to.stack),
|
|
||||||
path.position + 1,
|
|
||||||
this.input,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.paths = paths;
|
|
||||||
|
|
||||||
if (this.paths.length == 0) return "reject";
|
|
||||||
if (this.accepted.length != 0) return "accept";
|
|
||||||
return "pending";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
machine: Tm;
|
|
||||||
input: string;
|
|
||||||
current_states: Map<string, TmState[]> = new Map();
|
|
||||||
accepted: TmState[] = [];
|
|
||||||
|
|
||||||
constructor(machine: Tm, input: string) {
|
|
||||||
this.machine = machine;
|
|
||||||
this.input = input;
|
|
||||||
}
|
|
||||||
|
|
||||||
step(): SimStepResult {
|
|
||||||
return "pending";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
117
web/root/src/simulation/fa.ts
Normal file
117
web/root/src/simulation/fa.ts
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
import type {
|
||||||
|
Fa,
|
||||||
|
FaTransTo,
|
||||||
|
State,
|
||||||
|
} from "../automata.ts";
|
||||||
|
import { SimStepResult } from "../simulation.ts";
|
||||||
|
|
||||||
|
export type FaState = {
|
||||||
|
readonly state: State;
|
||||||
|
readonly position: number;
|
||||||
|
|
||||||
|
readonly accepted: boolean;
|
||||||
|
readonly repr: string;
|
||||||
|
|
||||||
|
readonly path: readonly FaTransTo[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type Initializer<T> = { -readonly [P in keyof T]?: T[P] | undefined };
|
||||||
|
|
||||||
|
export class FaSim {
|
||||||
|
readonly machine: Fa;
|
||||||
|
readonly input: string;
|
||||||
|
|
||||||
|
paths: FaState[] = [];
|
||||||
|
current_states: Map<string, FaState[]> = new Map();
|
||||||
|
accepted: FaState[] = [];
|
||||||
|
rejected: FaState[] = [];
|
||||||
|
|
||||||
|
constructor(machine: Fa, input: string) {
|
||||||
|
this.machine = machine;
|
||||||
|
this.input = input;
|
||||||
|
this.initial();
|
||||||
|
}
|
||||||
|
|
||||||
|
private accept(state: Initializer<FaState>): boolean {
|
||||||
|
const pos = state.position ?? 0;
|
||||||
|
const st = state.state!;
|
||||||
|
return pos === this.input.length && this.machine.final_states.has(st);
|
||||||
|
}
|
||||||
|
|
||||||
|
private init_state(state: Initializer<FaState>) {
|
||||||
|
state.position ??= 0;
|
||||||
|
|
||||||
|
state.accepted = this.accept(state);
|
||||||
|
state.repr = state.state + " >" + this.input.substring(state.position);
|
||||||
|
|
||||||
|
const frozen = state as FaState;
|
||||||
|
|
||||||
|
if (frozen.accepted) this.accepted.push(frozen);
|
||||||
|
this.paths.push(frozen);
|
||||||
|
|
||||||
|
if (!this.current_states.has(frozen.state)) {
|
||||||
|
this.current_states.set(frozen.state, []);
|
||||||
|
}
|
||||||
|
this.current_states.get(frozen.state)!.push(frozen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private initial() {
|
||||||
|
const state: Initializer<FaState> = {
|
||||||
|
state: this.machine.initial_state,
|
||||||
|
position: 0,
|
||||||
|
path: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
this.init_state(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private transition(from: FaState, to: FaTransTo, consume: boolean) {
|
||||||
|
const state: Initializer<FaState> = {
|
||||||
|
state: to.state,
|
||||||
|
position: from.position + (consume ? 1 : 0),
|
||||||
|
path: from.path.concat([to]),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.init_state(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
step(): SimStepResult {
|
||||||
|
if (this.accepted.length !== 0) return "accept";
|
||||||
|
if (this.paths.length === 0) return "reject";
|
||||||
|
|
||||||
|
const paths = this.paths;
|
||||||
|
this.paths = [];
|
||||||
|
this.current_states.clear();
|
||||||
|
|
||||||
|
for (const from of paths) {
|
||||||
|
const letterMap = this.machine.transitions_components.get(from.state);
|
||||||
|
|
||||||
|
if (!letterMap) {
|
||||||
|
this.rejected.push(from);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// epsilon transitions
|
||||||
|
const eps = letterMap.get(null) ?? [];
|
||||||
|
for (const to of eps) this.transition(from, to, false);
|
||||||
|
|
||||||
|
// consuming transitions
|
||||||
|
if (from.position >= this.input.length) {
|
||||||
|
if (eps.length === 0) this.rejected.push(from);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ch = this.input.charAt(from.position);
|
||||||
|
const trs = letterMap.get(ch) ?? [];
|
||||||
|
for (const to of trs) this.transition(from, to, true);
|
||||||
|
|
||||||
|
if (eps.length === 0 && trs.length === 0) {
|
||||||
|
this.rejected.push(from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.accepted.length !== 0) return "accept";
|
||||||
|
if (this.paths.length === 0) return "reject";
|
||||||
|
return "pending";
|
||||||
|
}
|
||||||
|
}
|
||||||
146
web/root/src/simulation/pda.ts
Normal file
146
web/root/src/simulation/pda.ts
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
import type {
|
||||||
|
Pda,
|
||||||
|
PdaTransTo,
|
||||||
|
State,
|
||||||
|
Symbol
|
||||||
|
} from "../automata.ts";
|
||||||
|
import { SimStepResult } from "../simulation.ts";
|
||||||
|
|
||||||
|
export type PdaState = {
|
||||||
|
readonly state: State;
|
||||||
|
readonly stack: Symbol[];
|
||||||
|
readonly position: number;
|
||||||
|
|
||||||
|
readonly accepted: boolean;
|
||||||
|
readonly repr: string;
|
||||||
|
|
||||||
|
readonly path: readonly PdaTransTo[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type Initializer<T> = { -readonly [P in keyof T]?: T[P] | undefined };
|
||||||
|
|
||||||
|
export class PdaSim {
|
||||||
|
readonly machine: Pda;
|
||||||
|
readonly input: string;
|
||||||
|
|
||||||
|
paths: PdaState[] = [];
|
||||||
|
current_states: Map<string, PdaState[]> = new Map();
|
||||||
|
accepted: PdaState[] = [];
|
||||||
|
rejected: PdaState[] = [];
|
||||||
|
|
||||||
|
constructor(machine: Pda, input: string) {
|
||||||
|
this.machine = machine;
|
||||||
|
this.input = input;
|
||||||
|
this.initial();
|
||||||
|
}
|
||||||
|
|
||||||
|
private accept(state: Initializer<PdaState>): boolean {
|
||||||
|
const pos = state.position ?? 0;
|
||||||
|
const st = state.state!;
|
||||||
|
const stack = state.stack ?? [];
|
||||||
|
|
||||||
|
//accept by final state
|
||||||
|
if (pos === this.input.length && this.machine.final_states && this.machine.final_states.has(st)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//accept by empty stack
|
||||||
|
if (pos === this.input.length && !this.machine.final_states && stack.length === 1 && stack[0] === this.machine.initial_stack) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private init_state(state: Initializer<PdaState>) {
|
||||||
|
state.stack ??= [this.machine.initial_stack];
|
||||||
|
state.position ??= 0;
|
||||||
|
|
||||||
|
state.accepted = this.accept(state);
|
||||||
|
state.repr = state.state + " [" + state.stack.join(",") + "] >" + this.input.substring(state.position);
|
||||||
|
|
||||||
|
const frozen = state as PdaState;
|
||||||
|
|
||||||
|
if (frozen.accepted) this.accepted.push(frozen);
|
||||||
|
this.paths.push(frozen);
|
||||||
|
|
||||||
|
if (!this.current_states.has(frozen.state)) {
|
||||||
|
this.current_states.set(frozen.state, []);
|
||||||
|
}
|
||||||
|
this.current_states.get(frozen.state)!.push(frozen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private initial() {
|
||||||
|
const state: Initializer<PdaState> = {
|
||||||
|
state: this.machine.initial_state,
|
||||||
|
stack: [this.machine.initial_stack],
|
||||||
|
position: 0,
|
||||||
|
path: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
this.init_state(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private transition(from: PdaState, to: PdaTransTo, consume: boolean) {
|
||||||
|
const stackCopy = from.stack.slice(0, from.stack.length - 1); // pop off top
|
||||||
|
const nextStack = stackCopy.concat(to.stack);
|
||||||
|
if (nextStack.length == 0) {
|
||||||
|
this.rejected.push(from)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const state: Initializer<PdaState> = {
|
||||||
|
state: to.state,
|
||||||
|
stack: nextStack,
|
||||||
|
position: from.position + (consume ? 1 : 0),
|
||||||
|
path: from.path.concat([to]),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.init_state(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
step(): SimStepResult {
|
||||||
|
if (this.accepted.length !== 0) return "accept";
|
||||||
|
if (this.paths.length === 0) return "reject";
|
||||||
|
|
||||||
|
const paths = this.paths;
|
||||||
|
this.paths = [];
|
||||||
|
this.current_states.clear();
|
||||||
|
|
||||||
|
for (const from of paths) {
|
||||||
|
const top = from.stack[from.stack.length - 1];
|
||||||
|
|
||||||
|
const letterMap = this.machine.transitions_components.get(from.state)?.get(top);
|
||||||
|
if (!letterMap) {
|
||||||
|
this.rejected.push(from);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// epsilon transitions
|
||||||
|
const epsilon_transitions = letterMap.get(null) ?? [];
|
||||||
|
for (const to of epsilon_transitions) {
|
||||||
|
this.transition(from, to, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from.position >= this.input.length) {
|
||||||
|
if (epsilon_transitions.length == 0){
|
||||||
|
this.rejected.push(from);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// consuming transitions
|
||||||
|
const ch = this.input.charAt(from.position);
|
||||||
|
|
||||||
|
const transitions = letterMap.get(ch) ?? [];
|
||||||
|
for (const to of transitions) {
|
||||||
|
this.transition(from, to, true);
|
||||||
|
}
|
||||||
|
if (epsilon_transitions.length == 0 && transitions.length == 0){
|
||||||
|
this.rejected.push(from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.accepted.length !== 0) return "accept";
|
||||||
|
if (this.paths.length === 0) return "reject";
|
||||||
|
return "pending";
|
||||||
|
}
|
||||||
|
}
|
||||||
129
web/root/src/simulation/tm.ts
Normal file
129
web/root/src/simulation/tm.ts
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
import type {
|
||||||
|
State,
|
||||||
|
Symbol,
|
||||||
|
Tm,
|
||||||
|
TmTransTo
|
||||||
|
} from "../automata.ts";
|
||||||
|
import { SimStepResult } from "../simulation.ts";
|
||||||
|
|
||||||
|
|
||||||
|
export type TmState = {
|
||||||
|
readonly state: State;
|
||||||
|
readonly tape: Symbol[];
|
||||||
|
readonly head: number;
|
||||||
|
|
||||||
|
readonly accepted: boolean;
|
||||||
|
readonly repr: string;
|
||||||
|
|
||||||
|
readonly path: readonly TmTransTo[];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type Initializer<T> = { -readonly [P in keyof T]?: T[P] | undefined };
|
||||||
|
|
||||||
|
export class TmSim {
|
||||||
|
readonly machine: Tm;
|
||||||
|
paths: TmState[] = [];
|
||||||
|
readonly input: string;
|
||||||
|
|
||||||
|
current_states: Map<string, TmState[]> = new Map();
|
||||||
|
accepted: TmState[] = [];
|
||||||
|
rejected: TmState[] = [];
|
||||||
|
|
||||||
|
constructor(machine: Tm, input: string) {
|
||||||
|
this.machine = machine;
|
||||||
|
this.input = input;
|
||||||
|
this.initial();
|
||||||
|
}
|
||||||
|
|
||||||
|
private init_state(state: Initializer<TmState>) {
|
||||||
|
state.repr = state.state + " [ " + this.machine.blank_symbol + " " + state.tape!.map((s, i, _) => i == state.head ? `[${s}]` : s).join(" ") + " " + this.machine.blank_symbol + " ]";
|
||||||
|
|
||||||
|
|
||||||
|
const frozen = state as TmState;
|
||||||
|
if (frozen.accepted) this.accepted.push(frozen);
|
||||||
|
this.paths.push(frozen);
|
||||||
|
if (!this.current_states.has(frozen.state)) {
|
||||||
|
this.current_states.set(frozen.state, []);
|
||||||
|
}
|
||||||
|
this.current_states.get(frozen.state)!.push(frozen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private initial() {
|
||||||
|
const state: Initializer<TmState> = {
|
||||||
|
state: this.machine.initial_state,
|
||||||
|
accepted: this.machine.final_states.has(this.machine.initial_state),
|
||||||
|
tape: this.input.split(''),
|
||||||
|
head: 0,
|
||||||
|
|
||||||
|
path: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (state.tape!.length == 0) state.tape!.push(this.machine.blank_symbol)
|
||||||
|
|
||||||
|
this.init_state(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private transition(from: TmState, to: TmTransTo) {
|
||||||
|
const state: Initializer<TmState> = {
|
||||||
|
state: to.state,
|
||||||
|
accepted: this.machine.final_states.has(to.state),
|
||||||
|
|
||||||
|
path: from.path.concat([to]),
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (to.direction) {
|
||||||
|
case "_":
|
||||||
|
state.tape = from.tape.slice();
|
||||||
|
state.tape![from.head] = to.symbol;
|
||||||
|
state.head = from.head;
|
||||||
|
break;
|
||||||
|
case "<":
|
||||||
|
if (from.head == 0) {
|
||||||
|
state.tape = from.tape.splice(0, 0, to.symbol);
|
||||||
|
state.head = 0;
|
||||||
|
} else {
|
||||||
|
state.tape = from.tape.slice();
|
||||||
|
state.tape![from.head] = to.symbol;
|
||||||
|
state.head = from.head - 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ">":
|
||||||
|
state.head = from.head + 1;
|
||||||
|
state.tape = from.tape.slice();
|
||||||
|
state.tape![from.head] = to.symbol;
|
||||||
|
if (state.head == from.tape.length) {
|
||||||
|
state.tape!.push(this.machine.blank_symbol);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.init_state(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
step(): SimStepResult {
|
||||||
|
if (this.accepted.length != 0) return "accept";
|
||||||
|
if (this.paths.length == 0) return "reject";
|
||||||
|
|
||||||
|
const paths: TmState[] = this.paths;
|
||||||
|
this.paths = [];
|
||||||
|
this.current_states.clear();
|
||||||
|
|
||||||
|
for (const from of paths) {
|
||||||
|
const symbol = from.tape[from.head];
|
||||||
|
const transitions = this.machine.transitions_components.get(from.state)?.get(symbol) ?? [];
|
||||||
|
if (transitions.length == 0) {
|
||||||
|
this.rejected.push(from);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const to of transitions) {
|
||||||
|
this.transition(from, to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.accepted.length != 0) return "accept";
|
||||||
|
if (this.paths.length == 0) return "reject";
|
||||||
|
return "pending";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -398,7 +398,7 @@ function renderNode({
|
||||||
const lineH = 14;
|
const lineH = 14;
|
||||||
|
|
||||||
let w = 0;
|
let w = 0;
|
||||||
for (const ln of paths) w = Math.max(w, ctx.measureText(ln.toString()).width);
|
for (const ln of paths) w = Math.max(w, ctx.measureText(ln.repr).width);
|
||||||
const boxW = w + padX * 2;
|
const boxW = w + padX * 2;
|
||||||
const boxH = paths.length * lineH + padY * 2;
|
const boxH = paths.length * lineH + padY * 2;
|
||||||
|
|
||||||
|
|
@ -415,7 +415,7 @@ function renderNode({
|
||||||
ctx.textBaseline = "top";
|
ctx.textBaseline = "top";
|
||||||
for (let i = 0; i < paths.length; i++) {
|
for (let i = 0; i < paths.length; i++) {
|
||||||
ctx.fillStyle = paths[i].accepted ? t.current_node_border : t.fg_0;
|
ctx.fillStyle = paths[i].accepted ? t.current_node_border : t.fg_0;
|
||||||
ctx.fillText(paths[i].toString(), x, by + padY + i * lineH);
|
ctx.fillText(paths[i].repr, x, by + padY + i * lineH);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue