loading machines visually now downs

This commit is contained in:
Parker TenBroeck 2026-01-09 22:55:39 -05:00
parent c57a95b7b5
commit 62cda62b31
7 changed files with 230 additions and 64 deletions

View file

@ -25,8 +25,8 @@ pub struct TransitionTo<'a> {
#[derive(Clone, Debug)]
#[allow(unused)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde_with::serde_as)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Fa<'a> {
pub initial_state: State<'a>,
pub states: HashMap<State<'a>, StateInfo>,

View file

@ -6,6 +6,7 @@ pub mod fa;
pub mod pda;
pub mod tm;
#[derive(Clone, Copy, Debug)]
pub struct Options {
pub non_deterministic: bool,

View file

@ -94,6 +94,9 @@ impl<'a> Context<'a> {
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(tag = "type"))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum Machine<'a> {
Fa(fa::Fa<'a>),
Pda(pda::Pda<'a>),

171
web/root/src/automata.ts Normal file
View file

@ -0,0 +1,171 @@
export type Machine = Fa | Pda | Tm;
export function machine_from_json(json: string): Machine {
const machine: Machine = JSON.parse(json);
machine.states = new Map(Object.entries(machine.states));
if (machine.alphabet) {
machine.alphabet = new Map(Object.entries(machine.alphabet));
}
if (machine.final_states) {
machine.final_states = new Map(Object.entries(machine.final_states));
}
// deno-lint-ignore no-explicit-any
const transitions = machine.transitions as any as [any, any];
machine.transitions = new Map();
for (const [key, value] of transitions) {
machine.transitions.set(key, value);
}
machine.edges = new Map();
switch (machine.type) {
case "fa":
{
for (const [from, tos] of machine.transitions) {
for (const to of tos) {
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:"ε",
function: to.function,
transition: to.transition,
});
}
}
}
break;
case "pda":
{
machine.symbols = new Map(Object.entries(machine.symbols));
for (const [from, tos] of machine.transitions) {
for (const to of tos) {
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+"]",
function: to.function,
transition: to.transition,
});
}
}
}
break;
case "tm":
{
machine.symbols = new Map(Object.entries(machine.symbols));
for (const [from, tos] of machine.transitions) {
for (const to of tos) {
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,
function: to.function,
transition: to.transition,
});
}
}
}
break;
}
return machine;
}
export type State = string;
export type Symbol = string;
export type Letter = string;
export type Span = [number, number];
export type StateInfo = { definition: Span };
export type LetterInfo = { definition: Span };
export type SymbolInfo = { definition: Span };
export type FaTransFrom = {
state: State;
letter: Letter|null;
};
export type FaTransTo = {
state: State;
transition: Span;
function: Span;
};
export type Edge = {
repr: string;
function: Span;
transition: Span;
};
export type Fa = {
type: "fa";
initial_state: State;
states: Map<State, StateInfo>;
alphabet: Map<Letter, LetterInfo>;
final_states: Map<State, StateInfo>;
transitions: Map<FaTransFrom, FaTransTo[]>;
edges: Map<string, Edge[]>;
};
export type PdaTransFrom = {
state: State;
letter: Letter|null;
symbol: Symbol;
};
export type PdaTransTo = {
state: State;
stack: Symbol[];
transition: Span;
function: Span;
};
export type Pda = {
type: "pda";
initial_state: State;
initial_stack: Symbol;
states: Map<State, StateInfo>;
symbols: Map<Symbol, SymbolInfo>;
alphabet: Map<Letter, LetterInfo>;
final_states: Map<State, StateInfo> | null;
transitions: Map<PdaTransFrom, PdaTransTo[]>;
edges: Map<string, Edge[]>;
};
export type TmTransFrom = {
state: State;
symbol: Symbol;
};
export type TmTransTo = {
state: State;
symbol: Symbol;
direction: "L" | "R" | "N";
transition: Span;
function: Span;
};
export type Tm = {
type: "tm";
initial_state: State;
initial_tape: Symbol;
states: Map<State, StateInfo>;
symbols: Map<Symbol, SymbolInfo>;
alphabet: Map<Letter, LetterInfo>;
final_states: Map<State, StateInfo>;
transitions: Map<TmTransFrom, TmTransTo[]>;
edges: Map<string, Edge[]>;
};

View file

@ -21,6 +21,7 @@ import wasm from "./wasm.ts"
import { terminalPlugin } from "./terminal.ts";
import { setAutomaton } from "./visualizer.ts";
import { machine_from_json } from "./automata.ts";
function tokenize(text: string) {
@ -79,7 +80,7 @@ function buildAnalysis(text: string, doc: Text) {
if (graph){
try{
setAutomaton(JSON.parse(graph))
setAutomaton(machine_from_json(graph))
}catch(e){
console.log(e);
}

View file

@ -3,11 +3,11 @@
// 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";
export const nodes = new vis.DataSet<vis.Node>();
export const edges = new vis.DataSet<vis.Edge>();
type Color = string;
type GraphTheme = {
bg_0: Color;
@ -25,8 +25,8 @@ type GraphTheme = {
edge_hover: Color;
edge_active: Color;
edge_font_size: number,
node_font_size: number,
edge_font_size: number;
node_font_size: number;
};
let _graphTheme: GraphTheme | null = null;
@ -77,7 +77,7 @@ export function updateGraphTheme() {
color: gt.fg_0,
bold: {
color: gt.fg_1,
mod: ''
mod: "",
},
},
},
@ -91,7 +91,7 @@ export function updateGraphTheme() {
bold: {
color: gt.fg_1,
size: gt.edge_font_size,
mod: ''
mod: "",
},
},
color: {
@ -101,43 +101,39 @@ export function updateGraphTheme() {
},
shadow: {
enabled: false,
}
},
},
});
}
type StateId = string;
type GraphDef = {
initial: StateId;
final_states: Set<StateId>;
states: Set<StateId>;
transitions: Record<string, string>;
};
let automaton: GraphDef = {
initial: "",
final_states: new Set(),
states: new Set(),
transitions: {},
let automaton: Machine = {
type: "fa",
alphabet: new Map(),
final_states: new Map(),
initial_state: "",
states: new Map(),
transitions: new Map(),
edges: new Map(),
};
export function clearAutomaton() {
setAutomaton({
initial: "",
final_states: new Set(),
states: new Set(),
transitions: {},
});
automaton = {
type: "fa",
alphabet: new Map(),
final_states: new Map(),
initial_state: "",
states: new Map(),
transitions: new Map(),
edges: new Map(),
};
}
export function setAutomaton(auto: GraphDef) {
export function setAutomaton(auto: Machine) {
console.log(auto);
automaton = auto;
automaton.final_states = new Set(automaton.final_states)
automaton.states = new Set(automaton.states)
// Populate nodes
for (const state of automaton.states) {
for (const state of automaton.states.keys()) {
if (nodes.get(state)) {
nodes.update({
id: state,
@ -151,40 +147,41 @@ export function setAutomaton(auto: GraphDef) {
}
}
// Populate edges
for (const [k, v] of Object.entries(automaton.transitions)) {
const to_from = k.split("#");
// // Populate edges
for (const [edge_id, transitions] of auto.edges) {
const to_from = edge_id.split("#");
const font = {
vadjust: -getGraphTheme().edge_font_size*Math.floor(((v.match(/\n/g) || '').length + 1)/2)
vadjust: -getGraphTheme().edge_font_size *
Math.floor(transitions.length / 2),
};
if (edges.get(k)) {
if (edges.get(edge_id)) {
edges.update({
id: k,
id: edge_id,
font,
from: to_from[0],
to: to_from[1],
label: v,
label: transitions.map(i => i.repr).join("\n"),
});
} else {
edges.add({
id: k,
id: edge_id,
font,
from: to_from[0],
to: to_from[1],
label: v,
label: transitions.map(i => i.repr).join("\n"),
});
}
}
for (const edge_id of edges.getIds()) {
if (auto.transitions[edge_id as string] === undefined){
edges.remove(edge_id)
if (!auto.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)) {
nodes.remove(node_id)
nodes.remove(node_id);
}
}
}
@ -278,12 +275,13 @@ function renderNode({
}: any) {
return {
drawNode() {
const t = getGraphTheme();
const r = Math.max(14, style?.size ?? 18);
const isInitial = automaton.initial === id;
const isFinal = automaton.final_states.has(id);
const isInitial = automaton.initial_state === id;
const isFinal = automaton.final_states
? automaton.final_states.has(id)
: false;
const isActive = false;
const fill = selected ? t.bg_2 : hover ? t.bg_1 : t.bg_0;

View file

@ -169,15 +169,7 @@ pub fn compile(input: &str) -> CompileResult {
let mut ctx = Context::new(input);
let result = automata::loader::parse_universal(&mut ctx);
let graph = if let Some(result) = result {
Some(match result {
loader::Machine::Fa(fa) => serde_json::to_string(&fa).unwrap(),
loader::Machine::Pda(pda) => serde_json::to_string(&pda).unwrap(),
loader::Machine::Tm(tm) => serde_json::to_string(&tm).unwrap(),
})
} else {
None
};
let graph = result.map(|result| serde_json::to_string(&result).unwrap());
use std::fmt::Write;
let log_formatted = ctx.logs_display().fold(String::new(), |mut s, e| {