changed how machines are parsed/represented

This commit is contained in:
ParkerTenBroeck 2026-01-09 20:13:09 -05:00
parent 132380e777
commit 34b20ec1fe
23 changed files with 1577 additions and 206 deletions

403
Cargo.lock generated
View file

@ -2,9 +2,28 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]] [[package]]
name = "automata" name = "automata"
version = "0.1.0" version = "0.1.0"
dependencies = [
"serde",
"serde_with",
]
[[package]] [[package]]
name = "automata-web" name = "automata-web"
@ -18,18 +37,46 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.19.1" version = "3.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
[[package]]
name = "cc"
version = "1.2.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.4" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chrono"
version = "0.4.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
dependencies = [
"iana-time-zone",
"num-traits",
"serde",
"windows-link",
]
[[package]] [[package]]
name = "console_error_panic_hook" name = "console_error_panic_hook"
version = "0.1.7" version = "0.1.7"
@ -40,6 +87,152 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "darling"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "deranged"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
dependencies = [
"powerfmt",
"serde_core",
]
[[package]]
name = "dyn-clone"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "find-msvc-tools"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "iana-time-zone"
version = "0.1.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
"serde",
]
[[package]]
name = "indexmap"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
"serde",
"serde_core",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.17" version = "1.0.17"
@ -56,18 +249,51 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "libc"
version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.6" version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.21.3" version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.103" version = "1.0.103"
@ -86,12 +312,56 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "ref-cast"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
dependencies = [
"ref-cast-impl",
]
[[package]]
name = "ref-cast-impl"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.22" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "schemars"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
dependencies = [
"dyn-clone",
"ref-cast",
"serde",
"serde_json",
]
[[package]]
name = "schemars"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2"
dependencies = [
"dyn-clone",
"ref-cast",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.228" version = "1.0.228"
@ -135,6 +405,49 @@ dependencies = [
"zmij", "zmij",
] ]
[[package]]
name = "serde_with"
version = "3.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7"
dependencies = [
"base64",
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.13.0",
"schemars 0.9.0",
"schemars 1.2.0",
"serde_core",
"serde_json",
"serde_with_macros",
"time",
]
[[package]]
name = "serde_with_macros"
version = "3.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.111" version = "2.0.111"
@ -146,6 +459,37 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "time"
version = "0.3.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
dependencies = [
"deranged",
"itoa",
"num-conv",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
[[package]]
name = "time-macros"
version = "0.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
dependencies = [
"num-conv",
"time-core",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.22" version = "1.0.22"
@ -207,6 +551,65 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]] [[package]]
name = "zmij" name = "zmij"
version = "1.0.12" version = "1.0.12"

View file

@ -4,7 +4,13 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
serde = { version = "1.0", features = ["derive"], optional = true}
serde_with = { version = "3.0", features = ["default"], optional = true}
[features]
default = []
serde = ["dep:serde", "dep:serde_with"]
[workspace] [workspace]
members = ["web"] members = ["web"]

278
src/automatan/fa.rs Normal file
View file

@ -0,0 +1,278 @@
use std::collections::HashSet;
use super::*;
use crate::loader::{
Context, DELTA_LOWER, GAMMA_UPPER, SIGMA_UPPER, Spanned,
ast::{self, Symbol as Sym},
};
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TransitionFrom<'a> {
pub state: State<'a>,
pub letter: Option<Letter<'a>>,
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TransitionTo<'a> {
pub state: State<'a>,
pub transition: Span,
pub function: Span,
}
#[derive(Clone, Debug)]
#[allow(unused)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde_with::serde_as)]
pub struct Fa<'a> {
pub initial_state: State<'a>,
pub states: HashMap<State<'a>, StateInfo>,
pub alphabet: HashMap<Letter<'a>, LetterInfo>,
pub final_states: HashMap<State<'a>, StateInfo>,
#[cfg(feature = "serde")]
#[serde_as(as = "serde_with::Seq<(_, _)>")]
pub transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>,
#[cfg(not(feature = "serde"))]
pub transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>,
}
impl<'a> Fa<'a> {
pub fn parse(
items: impl Iterator<Item = Spanned<ast::TopLevel<'a>>>,
ctx: &mut Context<'a>,
options: Options,
) -> Option<Fa<'a>> {
let mut initial_state = None;
let mut states = HashMap::new();
let mut alphabet = HashMap::new();
let mut final_states = HashMap::new();
let mut transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>> =
HashMap::new();
for Spanned(element, span) in items {
use Spanned as S;
use ast::TopLevel as TL;
match element {
TL::Item(S("Q", _), list) => {
if !states.is_empty() {
ctx.emit_error("states already set", span);
}
let Some(list) = list.expect_set(ctx) else {
continue;
};
for item in list {
let Some(ident) = item.expect_ident(ctx) else {
continue;
};
if states
.insert(State(ident), StateInfo { definition: item.1 })
.is_some()
{
ctx.emit_error("state redefined", item.1);
}
}
if list.is_empty() {
ctx.emit_error("states cannot be empty", span);
}
}
TL::Item(S("E" | SIGMA_UPPER | "sigma", _), list) => {
if !alphabet.is_empty() {
ctx.emit_error("alphabet already set", span);
}
let Some(list) = list.expect_set(ctx) else {
continue;
};
for item in list {
let Some(ident) = item.expect_ident(ctx) else {
continue;
};
if ident.chars().count() != 1 {
ctx.emit_error("letter cannot be longer than one char", item.1);
}
if alphabet
.insert(Letter(ident), LetterInfo { definition: item.1 })
.is_some()
{
ctx.emit_error("letter redefined", item.1);
}
}
if list.is_empty() {
ctx.emit_error("alphabet cannot be empty", span);
}
}
TL::Item(S("F", _), list) => {
if final_states.is_empty() {
ctx.emit_error("final states already set", span);
}
let Some(list) = list.expect_set(ctx) else {
continue;
};
for item in list {
let Some(ident) = item.expect_ident(ctx) else {
continue;
};
if states.contains_key(&State(ident)) {
if final_states
.insert(State(ident), StateInfo { definition: item.1 })
.is_none()
{
ctx.emit_error("final state redefined", item.1);
}
} else {
ctx.emit_error("final state not defined in set of states", item.1);
}
}
}
TL::Item(S("I" | "q0", _), S(src, src_d)) => match src {
ast::Item::Symbol(Sym::Ident(ident)) => {
if initial_state.is_some() {
ctx.emit_error("initial state already set", span);
}
if states.contains_key(&State(ident)) {
initial_state = Some(State(ident))
} else {
ctx.emit_error("initial state symbol not defined as a state", src_d);
}
}
_ => ctx.emit_error("expected ident", src_d),
},
TL::Item(S(name, dest_s), _) => {
ctx.emit_error(format!("unknown item {name:?}, expected 'Q' | 'E' | '{SIGMA_UPPER}' | 'sigma' | 'F' | 'T' | '{GAMMA_UPPER}' | 'gamma' | 'I' | 'q0' | 'S' | 'z0'"), dest_s);
}
TL::TransitionFunc(S((S("d" | DELTA_LOWER | "delta", _), tuple), _), list) => {
let list = list.set_weak();
let Some((state, letter)) = tuple.as_ref().expect_fa_transition_function(ctx)
else {
continue;
};
if !states.contains_key(&State(state.0)) {
ctx.emit_error("transition state not defined as state", state.1);
continue;
};
let letter: Option<Letter<'_>> = match letter.0 {
Sym::Epsilon => {
if !options.epsilon_moves {
ctx.emit_error("epsilon moves not permitted", letter.1);
}
None
}
Sym::Ident(val) => {
if !alphabet.contains_key(&Letter(val)) {
ctx.emit_error(
"transition letter not defined in alphabet",
letter.1,
);
}
Some(Letter(val))
}
};
for item in list {
let Some(next_state) = item.expect_ident(ctx) else {
continue;
};
let next_state = Spanned(next_state, item.1);
if !states.contains_key(&State(next_state.0)) {
ctx.emit_error("transition state not defined as state", next_state.1);
continue;
};
let entry: &mut _ = transitions
.entry(TransitionFrom {
letter,
state: State(state.0),
})
.or_default();
if !entry.is_empty() && !options.non_deterministic {
ctx.emit_error("transition already defined for this starting point (non determinism not permitted)", item.1);
}
if !entry.insert(TransitionTo {
state: State(next_state.0),
function: tuple.1,
transition: item.1,
}) {
ctx.emit_warning("duplicate transition", item.1);
}
}
}
TL::TransitionFunc(S((S(name, _), _), dest_s), _) => {
ctx.emit_error(
format!(
"unknown function {name:?}, expected 'd' | 'delta' | '{DELTA_LOWER}'"
),
dest_s,
);
}
TL::ProductionRule(_, _) => {
ctx.emit_error("unexpected production rule", span);
}
TL::Table() => ctx.emit_error("unexpected table", span),
}
}
if alphabet.is_empty() {
ctx.emit_error_locless("alphabet never defined");
}
if states.is_empty() {
ctx.emit_error_locless("states never defined");
}
let initial_state = match initial_state {
Some(some) => some,
None => {
if states.contains_key(&State("q0")) {
ctx.emit_warning_locless("initial state not defined, defaulting to 'q0'");
} else {
ctx.emit_error_locless("initial state not defined");
}
State("q0")
}
};
if ctx.contains_errors() {
return None;
}
Some(Fa {
initial_state,
states,
alphabet,
final_states,
transitions,
})
}
}
impl<'a> Spanned<&ast::Tuple<'a>> {
fn expect_fa_transition_function(
&self,
ctx: &mut Context<'a>,
) -> Option<(Spanned<&'a str>, Spanned<ast::Symbol<'a>>)> {
match &self.0.0[..] {
[
Spanned(ast::Item::Symbol(ast::Symbol::Ident(state)), state_span),
Spanned(ast::Item::Symbol(letter), letter_span),
] => {
return Some((Spanned(state, *state_span), Spanned(*letter, *letter_span)));
}
_ => ctx.emit_error("expected FA transition function (ident, ident|~)", self.1),
}
None
}
}

43
src/automatan/mod.rs Normal file
View file

@ -0,0 +1,43 @@
use std::collections::HashMap;
use crate::loader::Span;
pub mod fa;
pub mod pda;
pub mod tm;
#[derive(Clone, Copy, Debug)]
pub struct Options {
pub non_deterministic: bool,
pub epsilon_moves: bool,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
pub struct State<'a>(pub &'a str);
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
pub struct Symbol<'a>(pub &'a str);
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
pub struct Letter<'a>(pub &'a str);
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct StateInfo {
pub definition: Span,
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct SymbolInfo {
pub definition: Span,
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct LetterInfo {
pub definition: Span,
}

399
src/automatan/pda.rs Normal file
View file

@ -0,0 +1,399 @@
use std::collections::HashSet;
use super::*;
use crate::loader::{
Context, DELTA_LOWER, GAMMA_UPPER, SIGMA_UPPER, Spanned,
ast::{self, Symbol as Sym},
};
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TransitionFrom<'a> {
pub state: State<'a>,
pub letter: Option<Letter<'a>>,
pub symbol: Symbol<'a>,
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TransitionTo<'a> {
pub state: State<'a>,
pub stack: Vec<Symbol<'a>>,
pub transition: Span,
pub function: Span,
}
#[derive(Clone, Debug)]
#[allow(unused)]
#[cfg_attr(feature = "serde", serde_with::serde_as)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Pda<'a> {
pub initial_state: State<'a>,
pub initial_stack: Symbol<'a>,
pub states: HashMap<State<'a>, StateInfo>,
pub symbols: HashMap<Symbol<'a>, SymbolInfo>,
pub alphabet: HashMap<Letter<'a>, LetterInfo>,
pub final_states: Option<HashMap<State<'a>, StateInfo>>,
#[cfg(feature = "serde")]
#[serde_as(as = "serde_with::Seq<(_, _)>")]
pub transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>,
#[cfg(not(feature = "serde"))]
pub transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>,
}
impl<'a> Pda<'a> {
pub fn parse(
items: impl Iterator<Item = Spanned<ast::TopLevel<'a>>>,
ctx: &mut Context<'a>,
options: Options,
) -> Option<Pda<'a>> {
let mut initial_state = None;
let mut initial_stack = None;
let mut states = HashMap::new();
let mut symbols = HashMap::new();
let mut alphabet = HashMap::new();
let mut final_states = None;
let mut transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>> =
HashMap::new();
for Spanned(element, span) in items {
use Spanned as S;
use ast::TopLevel as TL;
match element {
TL::Item(S("Q", _), list) => {
if !states.is_empty() {
ctx.emit_error("states already set", span);
}
let Some(list) = list.expect_set(ctx) else {
continue;
};
for item in list {
let Some(ident) = item.expect_ident(ctx) else {
continue;
};
if states
.insert(State(ident), StateInfo { definition: item.1 })
.is_some()
{
ctx.emit_error("state redefined", item.1);
}
}
if list.is_empty() {
ctx.emit_error("states cannot be empty", span);
}
}
TL::Item(S("E" | SIGMA_UPPER | "sigma", _), list) => {
if !alphabet.is_empty() {
ctx.emit_error("alphabet already set", span);
}
let Some(list) = list.expect_set(ctx) else {
continue;
};
for item in list {
let Some(ident) = item.expect_ident(ctx) else {
continue;
};
if ident.chars().count() != 1 {
ctx.emit_error("letter cannot be longer than one char", item.1);
}
if alphabet
.insert(Letter(ident), LetterInfo { definition: item.1 })
.is_some()
{
ctx.emit_error("letter redefined", item.1);
}
}
if list.is_empty() {
ctx.emit_error("alphabet cannot be empty", span);
}
}
TL::Item(S("F", _), list) => {
if final_states.is_some() {
ctx.emit_error("final states already set", span);
}
let mut map = HashMap::new();
let Some(list) = list.expect_set(ctx) else {
continue;
};
for item in list {
let Some(ident) = item.expect_ident(ctx) else {
continue;
};
if states.contains_key(&State(ident)) {
if map
.insert(State(ident), StateInfo { definition: item.1 })
.is_none()
{
ctx.emit_error("final state redefined", item.1);
}
} else {
ctx.emit_error("final state not defined in set of states", item.1);
}
}
final_states = Some(map);
}
TL::Item(S("T" | GAMMA_UPPER | "gamma", _), list) => {
if !symbols.is_empty() {
ctx.emit_error("stack symbols already set", span);
}
let Some(list) = list.expect_set(ctx) else {
continue;
};
for item in list {
let Some(ident) = item.expect_ident(ctx) else {
continue;
};
if symbols
.insert(Symbol(ident), SymbolInfo { definition: item.1 })
.is_some()
{
ctx.emit_error("stack symbol redefined", item.1);
}
}
if list.is_empty() {
ctx.emit_error("stack symbols cannot be empty", span);
}
}
TL::Item(S("I" | "q0", _), S(src, src_d)) => match src {
ast::Item::Symbol(Sym::Ident(ident)) => {
if initial_state.is_some() {
ctx.emit_error("initial state already set", span);
}
if states.contains_key(&State(ident)) {
initial_state = Some(State(ident))
} else {
ctx.emit_error("initial state symbol not defined as a state", src_d);
}
}
_ => ctx.emit_error("expected ident", src_d),
},
TL::Item(S("S" | "z0", _), S(src, src_d)) => match src {
ast::Item::Symbol(Sym::Ident(ident)) => {
if initial_stack.is_some() {
ctx.emit_error("initial stack already set", span);
}
if symbols.contains_key(&Symbol(ident)) {
initial_stack = Some(Symbol(ident));
} else {
ctx.emit_error(
"initial stack symbol not defined as a stack symbol",
src_d,
);
}
}
_ => ctx.emit_error("expected ident", src_d),
},
TL::Item(S(name, dest_s), _) => {
ctx.emit_error(format!("unknown item {name:?}, expected 'Q' | 'E' | '{SIGMA_UPPER}' | 'sigma' | 'F' | 'T' | '{GAMMA_UPPER}' | 'gamma' | 'I' | 'q0' | 'S' | 'z0'"), dest_s);
}
TL::TransitionFunc(S((S("d" | DELTA_LOWER | "delta", _), tuple), _), list) => {
let list = list.set_weak();
let Some((state, letter, stack_symbol)) =
tuple.as_ref().expect_pda_transition_function(ctx)
else {
continue;
};
if !states.contains_key(&State(state.0)) {
ctx.emit_error("transition state not defined as state", state.1);
continue;
};
if !symbols.contains_key(&Symbol(stack_symbol.0)) {
ctx.emit_error(
"transition stack symbol not defined as stack symbol",
stack_symbol.1,
);
continue;
};
let letter: Option<Letter<'_>> = match letter.0 {
Sym::Epsilon => {
if !options.epsilon_moves {
ctx.emit_error("epsilon moves not permitted", letter.1);
}
None
}
Sym::Ident(val) => {
if !alphabet.contains_key(&Letter(val)) {
ctx.emit_error(
"transition letter not defined in alphabet",
letter.1,
);
}
Some(Letter(val))
}
};
for item in list {
let Some((next_state, stack)) = item
.expect_tuple(ctx)
.and_then(|item| item.expect_pda_transition(ctx))
else {
continue;
};
if !states.contains_key(&State(next_state.0)) {
ctx.emit_error("transition state not defined as state", next_state.1);
continue;
};
let stack: Vec<_> = stack
.iter()
.rev()
.filter_map(|symbol| {
if matches!(symbol.0, ast::Item::Symbol(Sym::Epsilon)) {
return None;
}
let ident = symbol.expect_ident(ctx)?;
if !symbols.contains_key(&Symbol(ident)) {
ctx.emit_error("transition stack symbol not defined", symbol.1);
return None;
};
Some(Symbol(ident))
})
.collect();
let entry: &mut _ = transitions
.entry(TransitionFrom {
letter,
state: State(state.0),
symbol: Symbol(stack_symbol.0),
})
.or_default();
if !entry.is_empty() && !options.non_deterministic {
ctx.emit_error("transition already defined for this starting point (non determinism not permitted)", item.1);
}
if !entry.insert(TransitionTo {
state: State(next_state.0),
stack,
function: tuple.1,
transition: item.1,
}) {
ctx.emit_warning("duplicate transition", item.1);
}
}
}
TL::TransitionFunc(S((S(name, _), _), dest_s), _) => {
ctx.emit_error(
format!(
"unknown function {name:?}, expected 'd' | 'delta' | '{DELTA_LOWER}'"
),
dest_s,
);
}
TL::ProductionRule(_, _) => {
ctx.emit_error("unexpected production rule", span);
}
TL::Table() => ctx.emit_error("unexpected table", span),
}
}
if symbols.is_empty() {
ctx.emit_error_locless("stack symbols never defined");
}
if alphabet.is_empty() {
ctx.emit_error_locless("alphabet never defined");
}
if states.is_empty() {
ctx.emit_error_locless("states never defined");
}
let initial_stack = match initial_stack {
Some(some) => some,
None => {
if symbols.contains_key(&Symbol("z0")) {
ctx.emit_warning_locless(
"initial stack symbol not defined, defaulting to 'z0'",
);
} else {
ctx.emit_error_locless("initial stack symbol not defined");
}
Symbol("z0")
}
};
let initial_state = match initial_state {
Some(some) => some,
None => {
if states.contains_key(&State("q0")) {
ctx.emit_warning_locless("initial state not defined, defaulting to 'q0'");
} else {
ctx.emit_error_locless("initial state not defined");
}
State("q0")
}
};
if ctx.contains_errors() {
return None;
}
Some(Pda {
initial_state,
initial_stack,
states,
symbols,
alphabet,
final_states,
transitions,
})
}
}
impl<'a, 'b> Spanned<&'b ast::Tuple<'a>> {
fn expect_pda_transition_function(
&self,
ctx: &mut Context<'a>,
) -> Option<(Spanned<&'a str>, Spanned<ast::Symbol<'a>>, Spanned<&'a str>)> {
match &self.0.0[..] {
[
Spanned(ast::Item::Symbol(ast::Symbol::Ident(state)), state_span),
Spanned(ast::Item::Symbol(letter), letter_span),
Spanned(ast::Item::Symbol(ast::Symbol::Ident(symbol)), symbol_span),
] => {
return Some((
Spanned(state, *state_span),
Spanned(*letter, *letter_span),
Spanned(symbol, *symbol_span),
));
}
_ => ctx.emit_error(
"expected PDA transition function (ident, ident|~, ident)",
self.1,
),
}
None
}
fn expect_pda_transition(
&self,
ctx: &mut Context<'a>,
) -> Option<(Spanned<&'a str>, &'b [Spanned<ast::Item<'a>>])> {
match &self.0.0[..] {
[
Spanned(ast::Item::Symbol(ast::Symbol::Ident(state)), state_span),
list,
] => {
return Some((Spanned(state, *state_span), list.list_weak()));
}
_ => ctx.emit_error("expected PDA transition (ident, item|[item])", self.1),
}
None
}
}

346
src/automatan/tm.rs Normal file
View file

@ -0,0 +1,346 @@
use std::collections::HashSet;
use super::*;
use crate::loader::{
Context, DELTA_LOWER, GAMMA_UPPER, SIGMA_UPPER, Spanned,
ast::{self, Symbol as Sym},
};
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TransitionFrom<'a> {
pub state: State<'a>,
pub symbol: Symbol<'a>,
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum Direction {
Left,
Right,
None,
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TransitionTo<'a> {
pub state: State<'a>,
pub symbol: Symbol<'a>,
pub direction: Direction,
pub transition: Span,
pub function: Span,
}
#[derive(Clone, Debug)]
#[allow(unused)]
#[cfg_attr(feature = "serde", serde_with::serde_as)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Tm<'a> {
pub initial_state: State<'a>,
pub initial_tape: Symbol<'a>,
pub states: HashMap<State<'a>, StateInfo>,
pub symbols: HashMap<Symbol<'a>, SymbolInfo>,
pub final_states: HashMap<State<'a>, StateInfo>,
#[cfg(feature = "serde")]
#[serde_as(as = "serde_with::Seq<(_, _)>")]
pub transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>,
#[cfg(not(feature = "serde"))]
pub transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>,
}
impl<'a> Tm<'a> {
pub fn parse(
items: impl Iterator<Item = Spanned<ast::TopLevel<'a>>>,
ctx: &mut Context<'a>,
options: Options,
) -> Option<Tm<'a>> {
let mut initial_state = None;
let mut initial_tape = None;
let mut states = HashMap::new();
let mut symbols = HashMap::new();
let mut final_states = HashMap::new();
let mut transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>> =
HashMap::new();
for Spanned(element, span) in items {
use Spanned as S;
use ast::TopLevel as TL;
match element {
TL::Item(S("Q", _), list) => {
if !states.is_empty() {
ctx.emit_error("states already set", span);
}
let Some(list) = list.expect_set(ctx) else {
continue;
};
for item in list {
let Some(ident) = item.expect_ident(ctx) else {
continue;
};
if states
.insert(State(ident), StateInfo { definition: item.1 })
.is_some()
{
ctx.emit_error("state redefined", item.1);
}
}
if list.is_empty() {
ctx.emit_error("states cannot be empty", span);
}
}
TL::Item(S("F", _), list) => {
if !final_states.is_empty() {
ctx.emit_error("final states already set", span);
}
let Some(list) = list.expect_set(ctx) else {
continue;
};
for item in list {
let Some(ident) = item.expect_ident(ctx) else {
continue;
};
if states.contains_key(&State(ident)) {
if final_states
.insert(State(ident), StateInfo { definition: item.1 })
.is_none()
{
ctx.emit_error("final state redefined", item.1);
}
} else {
ctx.emit_error("final state not defined in set of states", item.1);
}
}
}
TL::Item(S("T" | GAMMA_UPPER | "gamma", _), list) => {
if !symbols.is_empty() {
ctx.emit_error("tape symbols already set", span);
}
let Some(list) = list.expect_set(ctx) else {
continue;
};
for item in list {
let Some(ident) = item.expect_ident(ctx) else {
continue;
};
if symbols
.insert(Symbol(ident), SymbolInfo { definition: item.1 })
.is_some()
{
ctx.emit_error("tape symbol redefined", item.1);
}
}
if list.is_empty() {
ctx.emit_error("tape symbols cannot be empty", span);
}
}
TL::Item(S("I" | "q0", _), S(src, src_d)) => match src {
ast::Item::Symbol(Sym::Ident(ident)) => {
if initial_state.is_some() {
ctx.emit_error("initial state already set", span);
}
if states.contains_key(&State(ident)) {
initial_state = Some(State(ident))
} else {
ctx.emit_error("initial state symbol not defined as a state", src_d);
}
}
_ => ctx.emit_error("expected ident", src_d),
},
TL::Item(S("S" | "z0", _), S(src, src_d)) => match src {
ast::Item::Symbol(Sym::Ident(ident)) => {
if initial_tape.is_some() {
ctx.emit_error("initial tape symbol already set", span);
}
if symbols.contains_key(&Symbol(ident)) {
initial_tape = Some(Symbol(ident));
} else {
ctx.emit_error(
"initial tape symbol not defined as a tape symbol",
src_d,
);
}
}
_ => ctx.emit_error("expected ident", src_d),
},
TL::Item(S(name, dest_s), _) => {
ctx.emit_error(format!("unknown item {name:?}, expected 'Q' | 'E' | '{SIGMA_UPPER}' | 'sigma' | 'F' | 'T' | '{GAMMA_UPPER}' | 'gamma' | 'I' | 'q0' | 'S' | 'z0'"), dest_s);
}
TL::TransitionFunc(S((S("d" | DELTA_LOWER | "delta", _), tuple), _), list) => {
let list = list.set_weak();
let Some((from_state, from_tape)) =
tuple.as_ref().expect_tm_transition_function(ctx)
else {
continue;
};
if !states.contains_key(&State(from_state.0)) {
ctx.emit_error("transition state not defined as state", from_state.1);
continue;
};
if !symbols.contains_key(&Symbol(from_tape.0)) {
ctx.emit_error(
"transition tape symbol not defined as tape symbol",
from_tape.1,
);
continue;
};
for item in list {
let Some((to_state, to_tape, direction)) = item
.expect_tuple(ctx)
.and_then(|item| item.expect_tm_transition(ctx))
else {
continue;
};
if !states.contains_key(&State(to_state.0)) {
ctx.emit_error("transition state not defined as state", to_state.1);
continue;
};
let entry: &mut _ = transitions
.entry(TransitionFrom {
state: State(from_state.0),
symbol: Symbol(from_tape.0),
})
.or_default();
if !entry.is_empty() && !options.non_deterministic {
ctx.emit_error("transition already defined for this starting point (non determinism not permitted)", item.1);
}
if !entry.insert(TransitionTo {
state: State(to_state.0),
symbol: Symbol(to_tape.0),
direction: direction.0,
function: tuple.1,
transition: item.1,
}) {
ctx.emit_warning("duplicate transition", item.1);
}
}
}
TL::TransitionFunc(S((S(name, _), _), dest_s), _) => {
ctx.emit_error(
format!(
"unknown function {name:?}, expected 'd' | 'delta' | '{DELTA_LOWER}'"
),
dest_s,
);
}
TL::ProductionRule(_, _) => {
ctx.emit_error("unexpected production rule", span);
}
TL::Table() => ctx.emit_error("unexpected table", span),
}
}
if symbols.is_empty() {
ctx.emit_error_locless("tape symbols never defined");
}
if states.is_empty() {
ctx.emit_error_locless("states never defined");
}
let initial_tape = match initial_tape {
Some(some) => some,
None => {
if symbols.contains_key(&Symbol("z0")) {
ctx.emit_warning_locless("initial tape symbol not defined, defaulting to 'z0'");
} else {
ctx.emit_error_locless("initial tape symbol not defined");
}
Symbol("z0")
}
};
let initial_state = match initial_state {
Some(some) => some,
None => {
if states.contains_key(&State("q0")) {
ctx.emit_warning_locless("initial state not defined, defaulting to 'q0'");
} else {
ctx.emit_error_locless("initial state not defined");
}
State("q0")
}
};
if ctx.contains_errors() {
return None;
}
Some(Tm {
initial_state,
initial_tape,
states,
symbols,
final_states,
transitions,
})
}
}
impl<'a> Spanned<&ast::Tuple<'a>> {
fn expect_tm_transition_function(
&self,
ctx: &mut Context<'a>,
) -> Option<(Spanned<&'a str>, Spanned<&'a str>)> {
match &self.0.0[..] {
[
Spanned(ast::Item::Symbol(ast::Symbol::Ident(state)), state_span),
Spanned(ast::Item::Symbol(ast::Symbol::Ident(tape)), tape_span),
] => {
return Some((Spanned(state, *state_span), Spanned(*tape, *tape_span)));
}
_ => ctx.emit_error("expected TM transition function (ident, ident)", self.1),
}
None
}
fn expect_tm_transition(
&self,
ctx: &mut Context<'a>,
) -> Option<(Spanned<&'a str>, Spanned<&'a str>, Spanned<Direction>)> {
match &self.0.0[..] {
[
Spanned(ast::Item::Symbol(ast::Symbol::Ident(state)), state_span),
Spanned(ast::Item::Symbol(ast::Symbol::Ident(tape)), tape_span),
Spanned(ast::Item::Symbol(direction), direction_span),
] => {
let direction = match direction {
ast::Symbol::Ident("left" | "L" | "<") => Direction::Left,
ast::Symbol::Ident("right" | "R" | ">") => Direction::Right,
ast::Symbol::Epsilon | ast::Symbol::Ident("~") => Direction::None,
ast::Symbol::Ident(ident) => {
ctx.emit_error(
format!("invalid direction specified '{ident}'"),
*direction_span,
);
Direction::None
}
};
return Some((
Spanned(state, *state_span),
Spanned(*tape, *tape_span),
Spanned(direction, *direction_span),
));
}
_ => ctx.emit_error(
"expected TM transition function (ident, ident, ident)",
self.1,
),
}
None
}
}

View file

@ -1,2 +1,2 @@
pub mod automata; pub mod automatan;
pub mod loader; pub mod loader;

View file

@ -51,7 +51,10 @@ pub struct ProductionGroup<'a>(pub Vec<Spanned<Symbol<'a>>>);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum TopLevel<'a> { pub enum TopLevel<'a> {
Item(Spanned<&'a str>, Spanned<Item<'a>>), Item(Spanned<&'a str>, Spanned<Item<'a>>),
TransitionFunc(Spanned<(Spanned<&'a str>, Spanned<Tuple<'a>>)>, Spanned<Item<'a>>), TransitionFunc(
Spanned<(Spanned<&'a str>, Spanned<Tuple<'a>>)>,
Spanned<Item<'a>>,
),
ProductionRule( ProductionRule(
Spanned<ProductionGroup<'a>>, Spanned<ProductionGroup<'a>>,
Spanned<Vec<Spanned<ProductionGroup<'a>>>>, Spanned<Vec<Spanned<ProductionGroup<'a>>>>,
@ -62,12 +65,19 @@ pub enum TopLevel<'a> {
use crate::loader::Context; use crate::loader::Context;
impl<'a> Spanned<Item<'a>> { impl<'a> Spanned<Item<'a>> {
pub fn expect_symbol(&self, ctx: &mut Context<'a>) -> Option<Symbol<'a>> {
match &self.0 {
Item::Symbol(sym) => return Some(*sym),
Item::Tuple(_) => ctx.emit_error("expected ident found tuple", self.1),
Item::List(_) => ctx.emit_error("expected ident found list", self.1),
}
None
}
pub fn expect_ident(&self, ctx: &mut Context<'a>) -> Option<&'a str> { pub fn expect_ident(&self, ctx: &mut Context<'a>) -> Option<&'a str> {
match &self.0 { match &self.0 {
Item::Symbol(Symbol::Ident(ident)) => return Some(ident), Item::Symbol(Symbol::Ident(ident)) => return Some(ident),
Item::Symbol(Symbol::Epsilon) => { Item::Symbol(Symbol::Epsilon) => ctx.emit_error("expected ident found epsilon", self.1),
ctx.emit_error("expected ident found epsilon", self.1)
}
Item::Tuple(_) => ctx.emit_error("expected ident found tuple", self.1), Item::Tuple(_) => ctx.emit_error("expected ident found tuple", self.1),
Item::List(_) => ctx.emit_error("expected ident found list", self.1), Item::List(_) => ctx.emit_error("expected ident found list", self.1),
} }
@ -111,71 +121,10 @@ impl<'a> Spanned<Item<'a>> {
pub fn expect_tuple(&self, ctx: &mut Context<'a>) -> Option<Spanned<&Tuple<'a>>> { pub fn expect_tuple(&self, ctx: &mut Context<'a>) -> Option<Spanned<&Tuple<'a>>> {
match &self.0 { match &self.0 {
Item::Symbol(Symbol::Ident(_)) => ctx.emit_error("expected tuple found ident", self.1), Item::Symbol(Symbol::Ident(_)) => ctx.emit_error("expected tuple found ident", self.1),
Item::Symbol(Symbol::Epsilon) => { Item::Symbol(Symbol::Epsilon) => ctx.emit_error("expected tuple found epsilon", self.1),
ctx.emit_error("expected tuple found epsilon", self.1)
}
Item::Tuple(tuple) => return Some(Spanned(tuple, self.1)), Item::Tuple(tuple) => return Some(Spanned(tuple, self.1)),
Item::List(_) => ctx.emit_error("expected tuple found list", self.1), Item::List(_) => ctx.emit_error("expected tuple found list", self.1),
} }
None None
} }
} }
impl<'a, 'b> Spanned<&'b Tuple<'a>> {
pub fn expect_dfa_transition(&self, _: &mut Context<'a>) -> ! {
todo!()
}
pub fn expect_nfa_transition(&self, _: &mut Context<'a>) -> ! {
todo!()
}
pub fn expect_dpda_transition(&self, _: &mut Context<'a>) -> ! {
todo!()
}
pub fn expect_npda_transition_function(
&self,
ctx: &mut Context<'a>,
) -> Option<(Spanned<&'a str>, Spanned<Symbol<'a>>, Spanned<&'a str>)> {
match &self.0.0[..] {
[
Spanned(Item::Symbol(Symbol::Ident(state)), state_span),
Spanned(Item::Symbol(letter), letter_span),
Spanned(Item::Symbol(Symbol::Ident(symbol)), symbol_span),
] => {
return Some((
Spanned(state, *state_span),
Spanned(*letter, *letter_span),
Spanned(symbol, *symbol_span),
));
}
_ => ctx.emit_error(
"expected NPDA transition function (ident, ident|~, ident)",
self.1,
),
}
None
}
pub fn expect_npda_transition(
&self,
ctx: &mut Context<'a>,
) -> Option<(Spanned<&'a str>, &'b [Spanned<Item<'a>>])> {
match &self.0.0[..] {
[
Spanned(Item::Symbol(Symbol::Ident(state)), state_span),
list,
] => {
return Some((Spanned(state, *state_span), list.list_weak()));
}
_ => ctx.emit_error("expected NPDA transition (ident, item|[item])", self.1),
}
None
}
pub fn expect_tm_transition(&self, _: &Context<'a>) -> ! {
todo!()
}
pub fn expect_ntm_transition(&self, _: &Context<'a>) -> ! {
todo!()
}
}

View file

@ -69,10 +69,7 @@ pub enum Error {
impl<'a> Lexer<'a> { impl<'a> Lexer<'a> {
pub fn new(input: &'a str) -> Self { pub fn new(input: &'a str) -> Self {
Self { Self { input, position: 0 }
input,
position: 0,
}
} }
fn consume(&mut self) -> Option<char> { fn consume(&mut self) -> Option<char> {
@ -92,7 +89,6 @@ impl<'a> Lexer<'a> {
self.position -= previous.len_utf8(); self.position -= previous.len_utf8();
} }
} }
} }
fn begin_ident(c: char) -> bool { fn begin_ident(c: char) -> bool {
@ -110,12 +106,12 @@ impl<'a> std::iter::Iterator for Lexer<'a> {
while let Some(c) = self.peek() while let Some(c) = self.peek()
&& c.is_whitespace() && c.is_whitespace()
{ {
if c == '\n'{ if c == '\n' {
let start = self.position; let start = self.position;
self.consume(); self.consume();
let res = Some(Spanned(Ok(Token::LineEnd), Span(start, self.position))); let res = Some(Spanned(Ok(Token::LineEnd), Span(start, self.position)));
return res; return res;
}else{ } else {
self.consume(); self.consume();
} }
} }
@ -151,7 +147,7 @@ impl<'a> std::iter::Iterator for Lexer<'a> {
'/' => match self.consume() { '/' => match self.consume() {
Some('/') => loop { Some('/') => loop {
match self.consume(){ match self.consume() {
Some('\n') => { Some('\n') => {
self.backtrack(); self.backtrack();
break Ok(Token::Comment(&self.input[start + 2..=self.position])); break Ok(Token::Comment(&self.input[start + 2..=self.position]));
@ -166,9 +162,7 @@ impl<'a> std::iter::Iterator for Lexer<'a> {
match self.consume() { match self.consume() {
Some('*') if self.peek() == Some('/') => { Some('*') if self.peek() == Some('/') => {
self.consume(); self.consume();
break Ok(Token::Comment( break Ok(Token::Comment(&self.input[start + 2..self.position - 2]));
&self.input[start + 2..self.position - 2],
));
} }
Some(_) => {} Some(_) => {}
None => break Err(Error::UnclosedMultiLine), None => break Err(Error::UnclosedMultiLine),

View file

@ -1,4 +1,4 @@
use std::{fmt::Display}; use std::fmt::Display;
use crate::loader::Span; use crate::loader::Span;
@ -64,11 +64,11 @@ impl Logs {
}); });
} }
pub fn displayable_with<'a>(&'a self, src: &'a str) -> impl Iterator<Item = LogEntryDisplay<'a>> { pub fn displayable_with<'a>(
self.logs.iter().map(|entry| LogEntryDisplay { &'a self,
src, src: &'a str,
entry, ) -> impl Iterator<Item = LogEntryDisplay<'a>> {
}) self.logs.iter().map(|entry| LogEntryDisplay { src, entry })
} }
pub fn entries(&self) -> &[LogEntry] { pub fn entries(&self) -> &[LogEntry] {

View file

@ -1,4 +1,4 @@
use crate::{automata::npda, loader::ast::TopLevel}; use crate::{automatan::*, loader::ast::TopLevel};
pub mod ast; pub mod ast;
pub mod lexer; pub mod lexer;
@ -13,7 +13,11 @@ pub const GAMMA_UPPER: &str = "Γ";
pub const GAMMA_LOWER: &str = "γ"; pub const GAMMA_LOWER: &str = "γ";
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)] #[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
pub struct Span(pub usize, pub usize); #[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Span(
#[cfg_attr(feature = "serde", serde(rename = "start"))] pub usize,
#[cfg_attr(feature = "serde", serde(rename = "end"))] pub usize,
);
impl Span { impl Span {
pub fn join(&self, end: Span) -> Span { pub fn join(&self, end: Span) -> Span {
Span(self.0, end.1) Span(self.0, end.1)
@ -32,26 +36,28 @@ impl<T> Spanned<T> {
} }
} }
pub struct Context<'a> {
pub struct Context<'a>{
logs: log::Logs, logs: log::Logs,
src: &'a str src: &'a str,
} }
impl<'a> Context<'a>{ impl<'a> Context<'a> {
pub fn new(src: &'a str) -> Self{ pub fn new(src: &'a str) -> Self {
Self { logs: log::Logs::new(), src } Self {
logs: log::Logs::new(),
src,
}
} }
pub fn src(&self) -> &'a str{ pub fn src(&self) -> &'a str {
self.src self.src
} }
pub fn logs_display(&self) -> impl Iterator<Item = log::LogEntryDisplay<'_>>{ pub fn logs_display(&self) -> impl Iterator<Item = log::LogEntryDisplay<'_>> {
self.logs.displayable_with(self.src) self.logs.displayable_with(self.src)
} }
pub fn eof(&self) -> Span{ pub fn eof(&self) -> Span {
Span(self.src.len(), self.src.len()) Span(self.src.len(), self.src.len())
} }
@ -83,37 +89,40 @@ impl<'a> Context<'a>{
self.logs.contains_errors() self.logs.contains_errors()
} }
pub fn into_logs(self) -> log::Logs{ pub fn into_logs(self) -> log::Logs {
self.logs self.logs
} }
} }
pub enum Machine<'a> {
pub enum Machine{ Fa(fa::Fa<'a>),
Npda(npda::Npda) Pda(pda::Pda<'a>),
Tm(tm::Tm<'a>),
} }
pub fn parse_universal(ctx: &mut Context<'_>) -> Option<Machine> { pub fn parse_universal<'a>(ctx: &mut Context<'a>) -> Option<Machine<'a>> {
let mut items = parser::Parser::new(ctx).collect::<Vec<_>>().into_iter(); let mut items = parser::Parser::new(ctx).collect::<Vec<_>>().into_iter();
if ctx.logs.contains_errors(){ if ctx.logs.contains_errors() {
return None; return None;
} }
use Spanned as S; use Spanned as S;
#[derive(Debug)] #[derive(Debug)]
enum Type{ enum Type {
Dfa, Dfa,
Nfa, Nfa,
Dpda, Dpda,
Npda, Npda,
Tm, Tm,
Ntm Ntm,
} }
fn parse_type<'a>(item: Option<S<TopLevel<'a>>>, ctx: &mut Context<'a>) -> Option<Type>{ fn parse_type<'a>(item: Option<S<TopLevel<'a>>>, ctx: &mut Context<'a>) -> Option<Type> {
let (str, span) = match item{ let (str, span) = match item {
Some(S(TopLevel::Item(S("type", _), item@S(_,span)), _)) => (item.expect_ident(ctx)?, span), Some(S(TopLevel::Item(S("type", _), item @ S(_, span)), _)) => {
(item.expect_ident(ctx)?, span)
}
Some(S(_, span)) => { Some(S(_, span)) => {
ctx.emit_error("expected type=<type> as first item", span); ctx.emit_error("expected type=<type> as first item", span);
return None; return None;
@ -124,25 +133,39 @@ pub fn parse_universal(ctx: &mut Context<'_>) -> Option<Machine> {
} }
}; };
Some(match str{ Some(match str {
"dfa"|"DFA" => Type::Dfa, "dfa" | "DFA" => Type::Dfa,
"nfa"|"NFA" => Type::Nfa, "nfa" | "NFA" => Type::Nfa,
"dpda"|"DPDA" => Type::Dpda, "dpda" | "DPDA" => Type::Dpda,
"npdaA"|"NPDA" => Type::Npda, "npdaA" | "NPDA" => Type::Npda,
"tm"|"TM" => Type::Tm, "tm" | "TM" => Type::Tm,
"ntm"|"NTM" => Type::Ntm, "ntm" | "NTM" => Type::Ntm,
_ => { _ => {
ctx.emit_error("unknown type, expected 'DFA' | 'NFA' | 'DPDA' | 'NPDA' | 'TM' | 'NTM'", span); ctx.emit_error(
"unknown type, expected 'DFA' | 'NFA' | 'DPDA' | 'NPDA' | 'TM' | 'NTM'",
span,
);
return None; return None;
}, }
}) })
} }
Some(match parse_type(items.next(), ctx)?{ const D: Options = Options {
Type::Npda => Machine::Npda(npda::Npda::load_from_ast(items, ctx)?), non_deterministic: false,
ty => { epsilon_moves: false,
ctx.emit_error_locless(format!("currently unsupported type {ty:?}")); };
return None;
} const N: Options = Options {
non_deterministic: true,
epsilon_moves: true,
};
Some(match parse_type(items.next(), ctx)? {
Type::Dfa => Machine::Fa(fa::Fa::parse(items, ctx, D)?),
Type::Nfa => Machine::Fa(fa::Fa::parse(items, ctx, N)?),
Type::Dpda => Machine::Pda(pda::Pda::parse(items, ctx, D)?),
Type::Npda => Machine::Pda(pda::Pda::parse(items, ctx, N)?),
Type::Tm => Machine::Tm(tm::Tm::parse(items, ctx, D)?),
Type::Ntm => Machine::Tm(tm::Tm::parse(items, ctx, N)?),
}) })
} }

View file

@ -1,35 +0,0 @@
use automata::{automata::npda, loader::Context};
fn main() {
let input = include_str!("../example.npda");
let mut ctx = Context::new(input);
let machine = automata::loader::parse_universal(&mut ctx);
for log in ctx.logs_display(){
println!("{log}")
}
let machine = match machine{
Some(automata::loader::Machine::Npda(npda)) => {
npda
},
None => return,
};
let input = "aababaaba";
println!("running on: '{input}'");
let mut simulator = npda::Simulator::begin(input, machine);
loop {
match simulator.step() {
npda::SimulatorResult::Pending => {}
npda::SimulatorResult::Reject => {
println!("REJECTED");
break;
}
npda::SimulatorResult::Accept(npda) => {
println!("ACCEPT: {npda:?}");
break;
}
}
}
}

View file

@ -10,7 +10,7 @@ crate-type = ["cdylib", "rlib"]
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
automata = {path=".."} automata = {path="..", features = ["serde"]}
console_error_panic_hook = "0.1.7" console_error_panic_hook = "0.1.7"
wasm-bindgen = "*" wasm-bindgen = "*"
web-sys = { version = "0.3.83", features = ["Window", "Document", "HtmlElement", "Text"] } web-sys = { version = "0.3.83", features = ["Window", "Document", "HtmlElement", "Text"] }

View file

@ -78,7 +78,11 @@ function buildAnalysis(text: string, doc: Text) {
const { log, log_formatted, graph } = compile(text); const { log, log_formatted, graph } = compile(text);
if (graph){ if (graph){
setAutomaton(JSON.parse(graph)) try{
setAutomaton(JSON.parse(graph))
}catch(e){
console.log(e);
}
} }
// Build ONE Decoration set: syntax + diagnostics // Build ONE Decoration set: syntax + diagnostics

View file

@ -132,6 +132,7 @@ export function clearAutomaton() {
} }
export function setAutomaton(auto: GraphDef) { export function setAutomaton(auto: GraphDef) {
console.log(auto);
automaton = auto; automaton = auto;
automaton.final_states = new Set(automaton.final_states) automaton.final_states = new Set(automaton.final_states)
automaton.states = new Set(automaton.states) automaton.states = new Set(automaton.states)

View file

@ -149,7 +149,6 @@ pub struct CompileLog {
pub end: Option<usize>, pub end: Option<usize>,
} }
#[derive(Serialize, Debug)] #[derive(Serialize, Debug)]
pub struct Graph<'a> { pub struct Graph<'a> {
initial: &'a str, initial: &'a str,
@ -171,50 +170,11 @@ pub fn compile(input: &str) -> CompileResult {
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 = if let Some(result) = result {
match result { Some(match result {
loader::Machine::Npda(npda) => { loader::Machine::Fa(fa) => serde_json::to_string(&fa).unwrap(),
let mut transitions = HashMap::new(); loader::Machine::Pda(pda) => serde_json::to_string(&pda).unwrap(),
for ((from, symbol), to_transitions) in npda.transitions().entries(){ loader::Machine::Tm(tm) => serde_json::to_string(&tm).unwrap(),
let from = npda.get_state_name(from).unwrap_or("<INVALID>"); })
let symbol = npda.get_symbol_name(symbol).unwrap_or("<INVALID>");
for (char, to) in to_transitions.entries(){
for to in to{
let to_state = npda.get_state_name(to.state()).unwrap_or("<INVALID>");
let string: &mut String = transitions.entry(format!("{from}#{to_state}")).or_default();
if !string.is_empty(){
string.push('\n');
}
let char = char.unwrap_or('ε');
let stack = to.stack().iter().map(|s|npda.get_symbol_name(*s).unwrap_or("<INVALID>")).fold(String::new(), |mut s, b|{
if !s.is_empty(){
s.push_str(", ");
}
s.push_str(b);
s
});
write!(string, "{char}, {symbol} -> [{stack}]").unwrap();
}
}
}
let graph = Graph {
states: npda.states().map(|(_, n)| n).collect(),
initial: npda
.get_state_name(npda.initial_state())
.unwrap_or("<INVALID>"),
final_states: npda
.final_states()
.map(|i| {
i.map(|s| npda.get_state_name(s).unwrap_or("<INVALID>"))
.collect::<Vec<_>>()
})
.unwrap_or_default(),
transitions
};
Some(serde_json::to_string(&graph).unwrap())
}
}
} else { } else {
None None
}; };