mirror of
https://github.com/ParkerTenBroeck/automata.git
synced 2026-06-07 05:28:45 -04:00
loading machines visually now downs
This commit is contained in:
parent
c57a95b7b5
commit
62cda62b31
7 changed files with 230 additions and 64 deletions
|
|
@ -25,8 +25,8 @@ pub struct TransitionTo<'a> {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
|
||||||
#[cfg_attr(feature = "serde", serde_with::serde_as)]
|
#[cfg_attr(feature = "serde", serde_with::serde_as)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||||
pub struct Fa<'a> {
|
pub struct Fa<'a> {
|
||||||
pub initial_state: State<'a>,
|
pub initial_state: State<'a>,
|
||||||
pub states: HashMap<State<'a>, StateInfo>,
|
pub states: HashMap<State<'a>, StateInfo>,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ pub mod fa;
|
||||||
pub mod pda;
|
pub mod pda;
|
||||||
pub mod tm;
|
pub mod tm;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
pub non_deterministic: bool,
|
pub non_deterministic: bool,
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
pub enum Machine<'a> {
|
||||||
Fa(fa::Fa<'a>),
|
Fa(fa::Fa<'a>),
|
||||||
Pda(pda::Pda<'a>),
|
Pda(pda::Pda<'a>),
|
||||||
|
|
|
||||||
171
web/root/src/automata.ts
Normal file
171
web/root/src/automata.ts
Normal 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[]>;
|
||||||
|
};
|
||||||
|
|
@ -21,6 +21,7 @@ import wasm from "./wasm.ts"
|
||||||
import { terminalPlugin } from "./terminal.ts";
|
import { terminalPlugin } from "./terminal.ts";
|
||||||
|
|
||||||
import { setAutomaton } from "./visualizer.ts";
|
import { setAutomaton } from "./visualizer.ts";
|
||||||
|
import { machine_from_json } from "./automata.ts";
|
||||||
|
|
||||||
|
|
||||||
function tokenize(text: string) {
|
function tokenize(text: string) {
|
||||||
|
|
@ -79,7 +80,7 @@ function buildAnalysis(text: string, doc: Text) {
|
||||||
|
|
||||||
if (graph){
|
if (graph){
|
||||||
try{
|
try{
|
||||||
setAutomaton(JSON.parse(graph))
|
setAutomaton(machine_from_json(graph))
|
||||||
}catch(e){
|
}catch(e){
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@
|
||||||
// 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";
|
||||||
|
|
||||||
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>();
|
||||||
|
|
||||||
|
|
||||||
type Color = string;
|
type Color = string;
|
||||||
type GraphTheme = {
|
type GraphTheme = {
|
||||||
bg_0: Color;
|
bg_0: Color;
|
||||||
|
|
@ -25,8 +25,8 @@ type GraphTheme = {
|
||||||
edge_hover: Color;
|
edge_hover: Color;
|
||||||
edge_active: Color;
|
edge_active: Color;
|
||||||
|
|
||||||
edge_font_size: number,
|
edge_font_size: number;
|
||||||
node_font_size: number,
|
node_font_size: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
let _graphTheme: GraphTheme | null = null;
|
let _graphTheme: GraphTheme | null = null;
|
||||||
|
|
@ -77,7 +77,7 @@ export function updateGraphTheme() {
|
||||||
color: gt.fg_0,
|
color: gt.fg_0,
|
||||||
bold: {
|
bold: {
|
||||||
color: gt.fg_1,
|
color: gt.fg_1,
|
||||||
mod: ''
|
mod: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -91,7 +91,7 @@ export function updateGraphTheme() {
|
||||||
bold: {
|
bold: {
|
||||||
color: gt.fg_1,
|
color: gt.fg_1,
|
||||||
size: gt.edge_font_size,
|
size: gt.edge_font_size,
|
||||||
mod: ''
|
mod: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
|
|
@ -101,43 +101,39 @@ export function updateGraphTheme() {
|
||||||
},
|
},
|
||||||
shadow: {
|
shadow: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let automaton: Machine = {
|
||||||
type StateId = string;
|
type: "fa",
|
||||||
type GraphDef = {
|
alphabet: new Map(),
|
||||||
initial: StateId;
|
final_states: new Map(),
|
||||||
final_states: Set<StateId>;
|
initial_state: "",
|
||||||
states: Set<StateId>;
|
states: new Map(),
|
||||||
transitions: Record<string, string>;
|
transitions: new Map(),
|
||||||
};
|
edges: new Map(),
|
||||||
|
|
||||||
let automaton: GraphDef = {
|
|
||||||
initial: "",
|
|
||||||
final_states: new Set(),
|
|
||||||
states: new Set(),
|
|
||||||
transitions: {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function clearAutomaton() {
|
export function clearAutomaton() {
|
||||||
setAutomaton({
|
automaton = {
|
||||||
initial: "",
|
type: "fa",
|
||||||
final_states: new Set(),
|
alphabet: new Map(),
|
||||||
states: new Set(),
|
final_states: new Map(),
|
||||||
transitions: {},
|
initial_state: "",
|
||||||
});
|
states: new Map(),
|
||||||
|
transitions: new Map(),
|
||||||
|
edges: new Map(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setAutomaton(auto: GraphDef) {
|
export function setAutomaton(auto: Machine) {
|
||||||
console.log(auto);
|
console.log(auto);
|
||||||
automaton = auto;
|
automaton = auto;
|
||||||
automaton.final_states = new Set(automaton.final_states)
|
|
||||||
automaton.states = new Set(automaton.states)
|
|
||||||
// Populate nodes
|
// Populate nodes
|
||||||
for (const state of automaton.states) {
|
for (const state of automaton.states.keys()) {
|
||||||
if (nodes.get(state)) {
|
if (nodes.get(state)) {
|
||||||
nodes.update({
|
nodes.update({
|
||||||
id: state,
|
id: state,
|
||||||
|
|
@ -151,40 +147,41 @@ export function setAutomaton(auto: GraphDef) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate edges
|
// // Populate edges
|
||||||
for (const [k, v] of Object.entries(automaton.transitions)) {
|
for (const [edge_id, transitions] of auto.edges) {
|
||||||
const to_from = k.split("#");
|
const to_from = edge_id.split("#");
|
||||||
const font = {
|
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({
|
edges.update({
|
||||||
id: k,
|
id: edge_id,
|
||||||
font,
|
font,
|
||||||
from: to_from[0],
|
from: to_from[0],
|
||||||
to: to_from[1],
|
to: to_from[1],
|
||||||
label: v,
|
label: transitions.map(i => i.repr).join("\n"),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
edges.add({
|
edges.add({
|
||||||
id: k,
|
id: edge_id,
|
||||||
font,
|
font,
|
||||||
from: to_from[0],
|
from: to_from[0],
|
||||||
to: to_from[1],
|
to: to_from[1],
|
||||||
label: v,
|
label: transitions.map(i => i.repr).join("\n"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const edge_id of edges.getIds()){
|
for (const edge_id of edges.getIds()) {
|
||||||
if (auto.transitions[edge_id as string] === undefined){
|
if (!auto.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 (!auto.states.has(node_id as string)) {
|
||||||
nodes.remove(node_id)
|
nodes.remove(node_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -278,12 +275,13 @@ function renderNode({
|
||||||
}: any) {
|
}: any) {
|
||||||
return {
|
return {
|
||||||
drawNode() {
|
drawNode() {
|
||||||
|
|
||||||
const t = getGraphTheme();
|
const t = getGraphTheme();
|
||||||
const r = Math.max(14, style?.size ?? 18);
|
const r = Math.max(14, style?.size ?? 18);
|
||||||
|
|
||||||
const isInitial = automaton.initial === id;
|
const isInitial = automaton.initial_state === id;
|
||||||
const isFinal = automaton.final_states.has(id);
|
const isFinal = automaton.final_states
|
||||||
|
? automaton.final_states.has(id)
|
||||||
|
: false;
|
||||||
const isActive = false;
|
const isActive = 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;
|
||||||
|
|
|
||||||
|
|
@ -169,15 +169,7 @@ pub fn compile(input: &str) -> CompileResult {
|
||||||
let mut ctx = Context::new(input);
|
let mut ctx = Context::new(input);
|
||||||
let result = automata::loader::parse_universal(&mut ctx);
|
let result = automata::loader::parse_universal(&mut ctx);
|
||||||
|
|
||||||
let graph = if let Some(result) = result {
|
let graph = result.map(|result| serde_json::to_string(&result).unwrap());
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
let log_formatted = ctx.logs_display().fold(String::new(), |mut s, e| {
|
let log_formatted = ctx.logs_display().fold(String::new(), |mut s, e| {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue