From 7cfd6c1fcdd28543ebe92458190801ca25a4c8b9 Mon Sep 17 00:00:00 2001 From: Parker TenBroeck <51721964+ParkerTenBroeck@users.noreply.github.com> Date: Thu, 9 Apr 2026 12:22:46 -0400 Subject: [PATCH 1/3] fixed typo in DPDA Final State example --- web/root/src/examples.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/root/src/examples.ts b/web/root/src/examples.ts index dc0e723..f1126d8 100644 --- a/web/root/src/examples.ts +++ b/web/root/src/examples.ts @@ -104,7 +104,7 @@ d(qc, c) = qc new Example( "Tutorial", "DPDA Final State", - `// Accept strings over a,b of the form a^nb^k n != k n,k > 0 + `// Accept strings over a,b of the form a^nb^k where n != k and n,k > 0 type = DPDA Q = {q0, qas, qeq, qmb, qlb} // states @@ -122,7 +122,7 @@ d(qas, b, z0) = (qeq, z0) d(qas, a, A) = (qas, [A A]) d(qas, b, A) = (qlb, ~) -d(qlb, b, A) = (qeq, ~) +d(qlb, b, A) = (qlb, ~) d(qlb, b, z0) = (qeq, z0) d(qeq, b, z0) = (qmb, z0) From b26abc1cfc546f07885426bbf1b532d07ecfde39 Mon Sep 17 00:00:00 2001 From: Parker TenBroeck <51721964+ParkerTenBroeck@users.noreply.github.com> Date: Fri, 10 Apr 2026 16:36:42 -0400 Subject: [PATCH 2/3] fixed TM example and compiler error --- automata/src/automatan/tm.rs | 7 +++++++ web/root/src/examples.ts | 17 +++++++++-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/automata/src/automatan/tm.rs b/automata/src/automatan/tm.rs index fe04104..7d53b98 100644 --- a/automata/src/automatan/tm.rs +++ b/automata/src/automatan/tm.rs @@ -413,6 +413,13 @@ impl<'a, 'b> TmCompiler<'a, 'b> { .emit_error("transition state not defined as state", to_state.1); continue; }; + if !self.symbols.contains_key(&Symbol(to_tape.0)) { + self.ctx.emit_error( + "transition tape symbol not defined as tape symbol", + to_tape.1, + ); + return; + }; let entry: &mut _ = self .transitions diff --git a/web/root/src/examples.ts b/web/root/src/examples.ts index f1126d8..158dbbc 100644 --- a/web/root/src/examples.ts +++ b/web/root/src/examples.ts @@ -369,7 +369,7 @@ d(q1, b, B) = { (q1, epsilon) }`, ), new Example("TM", "a^nb^n", - `// accepts all strings on {a,b}+ of the form anbn + `// accepts all strings on {a,b}+ of the form a^n^bn type = TM Q = { q0, q1, q2, q3, q4 } // set of internal states @@ -378,18 +378,19 @@ 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(q0,a)=(q1,X,R) d(q1,a)=(q1,a,R) -d(q1,Y)=(q1,y,R) -d(q1,b)=(q2,y,L) +d(q1,Y)=(q1,Y,R) +d(q1,b)=(q2,Y,L) -d(q2,Y)=(q2,y,L) +d(q2,Y)=(q2,Y,L) d(q2,a)=(q2,a,L) -d(q2,X)=(q0,x,R) +d(q2,X)=(q0,X,R) -d(q0,Y)=(q3,y,R) -d(q3,Y)=(q3,y,R) +d(q0,Y)=(q3,Y,R) +d(q3,Y)=(q3,Y,R) d(q3,B)=(q4,B,R) + `), // new Example("CFG", "definition", From 97b7f091c36899c708c695fc7ab375ea3e84e556 Mon Sep 17 00:00:00 2001 From: ParkerTenBroeck <51721964+ParkerTenBroeck@users.noreply.github.com> Date: Sun, 19 Apr 2026 17:29:55 -0400 Subject: [PATCH 3/3] added the ability to use strings for identifiers --- automata/src/automatan/fa.rs | 18 +++--- automata/src/automatan/pda.rs | 35 +++++------- automata/src/automatan/tm.rs | 37 ++++++------- automata/src/loader/ast.rs | 35 +++++++++--- automata/src/loader/lexer.rs | 2 +- automata/src/loader/log.rs | 4 +- automata/src/loader/mod.rs | 2 +- automata/src/loader/parser.rs | 4 +- web_lib/src/lib.rs | 101 +++++++++++++++++++++++----------- 9 files changed, 142 insertions(+), 96 deletions(-) diff --git a/automata/src/automatan/fa.rs b/automata/src/automatan/fa.rs index 20e5117..e46c7c0 100644 --- a/automata/src/automatan/fa.rs +++ b/automata/src/automatan/fa.rs @@ -229,7 +229,7 @@ impl<'a, 'b> FaCompiler<'a, 'b> { return; }; for item in list { - let Some(ident) = item.expect_ident(self.ctx) else { + let Some(ident) = item.expect_ident_weak(self.ctx) else { continue; }; if let Some(previous) = self @@ -258,7 +258,7 @@ impl<'a, 'b> FaCompiler<'a, 'b> { return; }; for item in list { - let Some(ident) = item.expect_ident(self.ctx) else { + let Some(ident) = item.expect_ident_weak(self.ctx) else { continue; }; @@ -292,7 +292,7 @@ impl<'a, 'b> FaCompiler<'a, 'b> { return; }; for item in list { - let Some(ident) = item.expect_ident(self.ctx) else { + let Some(ident) = item.expect_ident_weak(self.ctx) else { continue; }; if self.states.contains_key(&State(ident)) { @@ -366,7 +366,7 @@ impl<'a, 'b> FaCompiler<'a, 'b> { }; for item in list { - let Some(next_state) = item.expect_ident(self.ctx) else { + let Some(next_state) = item.expect_ident_weak(self.ctx) else { continue; }; let next_state = Spanned(next_state, item.1); @@ -413,11 +413,11 @@ impl<'a> Spanned<&ast::Tuple<'a>> { ctx: &mut Context<'a>, ) -> Option<(Spanned<&'a str>, Spanned>)> { 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))); + [state, letter] + if let Some(state) = state.string_weak() + && let Some(letter) = letter.sym_weak() => + { + return Some((state, letter)); } _ => { _ = ctx.emit_error( diff --git a/automata/src/automatan/pda.rs b/automata/src/automatan/pda.rs index 528fd21..e7d1c0d 100644 --- a/automata/src/automatan/pda.rs +++ b/automata/src/automatan/pda.rs @@ -317,7 +317,7 @@ impl<'a, 'b> PdaCompiler<'a, 'b> { .emit_error("accept by already set", top_level) .emit_info("previously defined here", previous); } - let Some(by) = item.expect_ident(self.ctx) else { + let Some(by) = item.expect_ident_weak(self.ctx) else { return; }; @@ -343,7 +343,7 @@ impl<'a, 'b> PdaCompiler<'a, 'b> { return; }; for item in list { - let Some(ident) = item.expect_ident(self.ctx) else { + let Some(ident) = item.expect_ident_weak(self.ctx) else { continue; }; if let Some(previous) = self @@ -372,7 +372,7 @@ impl<'a, 'b> PdaCompiler<'a, 'b> { return; }; for item in list { - let Some(ident) = item.expect_ident(self.ctx) else { + let Some(ident) = item.expect_ident_weak(self.ctx) else { continue; }; if let Some(previous) = self @@ -401,7 +401,7 @@ impl<'a, 'b> PdaCompiler<'a, 'b> { return; }; for item in list { - let Some(ident) = item.expect_ident(self.ctx) else { + let Some(ident) = item.expect_ident_weak(self.ctx) else { continue; }; @@ -435,7 +435,7 @@ impl<'a, 'b> PdaCompiler<'a, 'b> { return; }; for item in list { - let Some(ident) = item.expect_ident(self.ctx) else { + let Some(ident) = item.expect_ident_weak(self.ctx) else { continue; }; if self.states.contains_key(&State(ident)) { @@ -562,7 +562,7 @@ impl<'a, 'b> PdaCompiler<'a, 'b> { if matches!(symbol.0, ast::Item::Symbol(Sym::Epsilon(_))) { return None; } - let ident = symbol.expect_ident(self.ctx)?; + let ident = symbol.expect_ident_weak(self.ctx)?; if !self.symbols.contains_key(&Symbol(ident)) { self.ctx @@ -611,16 +611,12 @@ impl<'a, 'b> Spanned<&'b ast::Tuple<'a>> { ctx: &mut Context<'a>, ) -> Option<(Spanned<&'a str>, Spanned>, 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), - )); + [state, letter, symbol] + if let Some(state) = state.string_weak() + && let Some(letter) = letter.sym_weak() + && let Some(symbol) = symbol.string_weak() => + { + return Some((state, letter, symbol)); } _ => { _ = ctx.emit_error( @@ -636,11 +632,8 @@ impl<'a, 'b> Spanned<&'b ast::Tuple<'a>> { ctx: &mut Context<'a>, ) -> Option<(Spanned<&'a str>, &'b [Spanned>])> { match &self.0.0[..] { - [ - Spanned(ast::Item::Symbol(ast::Symbol::Ident(state)), state_span), - list, - ] => { - return Some((Spanned(state, *state_span), list.list_weak())); + [state, list] if let Some(state) = state.string_weak() => { + return Some((state, list.list_weak())); } _ => _ = ctx.emit_error("expected PDA transition (state, symbol|[symbol])", self.1), } diff --git a/automata/src/automatan/tm.rs b/automata/src/automatan/tm.rs index 7d53b98..9f16b48 100644 --- a/automata/src/automatan/tm.rs +++ b/automata/src/automatan/tm.rs @@ -253,7 +253,7 @@ impl<'a, 'b> TmCompiler<'a, 'b> { return; }; for item in list { - let Some(ident) = item.expect_ident(self.ctx) else { + let Some(ident) = item.expect_ident_weak(self.ctx) else { continue; }; if let Some(previous) = self @@ -282,7 +282,7 @@ impl<'a, 'b> TmCompiler<'a, 'b> { return; }; for item in list { - let Some(ident) = item.expect_ident(self.ctx) else { + let Some(ident) = item.expect_ident_weak(self.ctx) else { continue; }; if let Some(previous) = self @@ -311,7 +311,7 @@ impl<'a, 'b> TmCompiler<'a, 'b> { return; }; for item in list { - let Some(ident) = item.expect_ident(self.ctx) else { + let Some(ident) = item.expect_ident_weak(self.ctx) else { continue; }; if self.states.contains_key(&State(ident)) { @@ -459,11 +459,11 @@ impl<'a> Spanned<&ast::Tuple<'a>> { 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))); + [state, tape] + if let Some(state) = state.string_weak() + && let Some(tape) = tape.string_weak() => + { + return Some((state, tape)); } _ => _ = ctx.emit_error("expected TM transition function (state, symbol)", self.1), } @@ -475,28 +475,25 @@ impl<'a> Spanned<&ast::Tuple<'a>> { ctx: &mut Context<'a>, ) -> Option<(Spanned<&'a str>, Spanned<&'a str>, Spanned)> { 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 { + [state, tape, direction] + if let Some(state) = state.string_weak() + && let Some(tape) = tape.string_weak() + && let Some(direction) = direction.sym_weak() => + { + let direction_span = direction.1; + let direction = match direction.0 { 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.1, ); Direction::None } }; - return Some(( - Spanned(state, *state_span), - Spanned(*tape, *tape_span), - Spanned(direction, *direction_span), - )); + return Some((state, tape, Spanned(direction, direction_span))); } _ => { _ = ctx.emit_error( diff --git a/automata/src/loader/ast.rs b/automata/src/loader/ast.rs index 8e219a1..0e6e07d 100644 --- a/automata/src/loader/ast.rs +++ b/automata/src/loader/ast.rs @@ -23,7 +23,7 @@ pub enum Symbol<'a> { #[derive(Clone, Debug)] pub enum Item<'a> { Symbol(Symbol<'a>), - String(Cow<'a, str>), + String(&'a str), Tuple(Tuple<'a>), List(List<'a>), } @@ -50,7 +50,7 @@ pub struct List<'a>(pub Vec>>, pub ListKind); pub enum ProductionUnit<'a> { Epsilon(&'a str), Ident(&'a str), - String(Cow<'a, str>), + String(&'a str), } #[derive(Clone, Debug)] @@ -73,7 +73,7 @@ pub enum TopLevel<'a> { use crate::loader::{Context, log::LogSink}; impl<'a> Spanned> { - pub fn expect_symbol(&self, ctx: &mut Context<'a>) -> Option> { + pub fn expect_symbol_weak(&self, ctx: &mut Context<'a>) -> Option> { match &self.0 { Item::Symbol(sym) => return Some(*sym), Item::Tuple(_) => _ = ctx.emit_error("expected ident found tuple", self.1), @@ -83,15 +83,15 @@ impl<'a> Spanned> { None } - pub fn expect_ident(&self, ctx: &mut Context<'a>) -> Option<&'a str> { + pub fn expect_ident_weak(&self, ctx: &mut Context<'a>) -> Option<&'a str> { match &self.0 { Item::Symbol(Symbol::Ident(ident)) => return Some(ident), + Item::String(string) => _ = return Some(*string), Item::Symbol(Symbol::Epsilon(_)) => { - _ = ctx.emit_error("expected ident found epsilon", self.1) + _ = ctx.emit_error("expected ident/string found epsilon", 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::String(_) => _ = ctx.emit_error("expected ident found string", self.1), + Item::Tuple(_) => _ = ctx.emit_error("expected ident/string found tuple", self.1), + Item::List(_) => _ = ctx.emit_error("expected ident/string found list", self.1), } None } @@ -140,6 +140,25 @@ impl<'a> Spanned> { } } + pub fn string_weak(&self) -> Option> { + match &self.0 { + Item::Symbol(Symbol::Ident(ident)) => Some(Spanned(ident, self.1)), + Item::Symbol(_) => None, + Item::String(ident) => Some(Spanned(ident, self.1)), + Item::Tuple(_) => None, + Item::List(_) => None, + } + } + + pub fn sym_weak(&self) -> Option>> { + match &self.0 { + Item::Symbol(symbol) => Some(Spanned(*symbol, self.1)), + Item::String(ident) => Some(Spanned(Symbol::Ident(ident), self.1)), + Item::Tuple(_) => None, + Item::List(_) => None, + } + } + pub fn expect_tuple(&self, ctx: &mut Context<'a>) -> Option>> { match &self.0 { Item::Symbol(Symbol::Ident(_)) => { diff --git a/automata/src/loader/lexer.rs b/automata/src/loader/lexer.rs index 9cd51c4..710552d 100644 --- a/automata/src/loader/lexer.rs +++ b/automata/src/loader/lexer.rs @@ -174,7 +174,7 @@ impl<'a> std::iter::Iterator for Lexer<'a> { match self.consume() { Some('"') => { break Ok(Token::String( - &self.input[start + 1..self.position], + &self.input[start + 1..self.position-1], StringKind::Regular, escaped, )); diff --git a/automata/src/loader/log.rs b/automata/src/loader/log.rs index 742a7fa..15b59e0 100644 --- a/automata/src/loader/log.rs +++ b/automata/src/loader/log.rs @@ -231,11 +231,11 @@ impl<'a> Display for LogEntryDisplay<'a> { } for grapheme in line.graphemes(true) { if (span.0..span.1).contains(&index) { - for _ in 0..width(grapheme){ + for _ in 0..width(grapheme) { write!(f, "~")?; } } else { - for _ in 0..width(grapheme){ + for _ in 0..width(grapheme) { write!(f, " ")?; } } diff --git a/automata/src/loader/mod.rs b/automata/src/loader/mod.rs index e189edf..0b60bcf 100644 --- a/automata/src/loader/mod.rs +++ b/automata/src/loader/mod.rs @@ -152,7 +152,7 @@ pub fn parse_universal<'a>(ctx: &mut Context<'a>) -> Option> { fn parse_type<'a>(item: Option>>, ctx: &mut Context<'a>) -> Option { let (str, span) = match item { Some(S(TopLevel::Item(S("type", _), item @ S(_, span)), _)) => { - (item.expect_ident(ctx)?, span) + (item.expect_ident_weak(ctx)?, span) } Some(S(_, span)) => { ctx.emit_error("expected type= as first item", span) diff --git a/automata/src/loader/parser.rs b/automata/src/loader/parser.rs index c546777..7f8019e 100644 --- a/automata/src/loader/parser.rs +++ b/automata/src/loader/parser.rs @@ -141,7 +141,7 @@ impl<'a, 'b> Parser<'a, 'b> { S(Tuple(items), start.join(end)) } - fn parse_as_string(&mut self, tok: S>) -> S> { + fn parse_as_string(&mut self, tok: S>) -> S<&'a str> { let (r, k, e, s) = match tok { S(T::String(r, k, e), s) => (r, k, e, s), S(t, s) => { @@ -160,7 +160,7 @@ impl<'a, 'b> Parser<'a, 'b> { S(r.into(), s) } - fn parse_string(&mut self) -> S> { + fn parse_string(&mut self) -> S<&'a str> { let tok = self.next_token(); self.parse_as_string(tok) } diff --git a/web_lib/src/lib.rs b/web_lib/src/lib.rs index 274cc96..f626d60 100644 --- a/web_lib/src/lib.rs +++ b/web_lib/src/lib.rs @@ -1,7 +1,10 @@ use std::collections::HashMap; use automata::{ - automatan::{fa::Fa, pda::Pda, tm::Tm}, delta_lower, epsilon, gamma_upper, loader::{self, Context, Machine, Span, Spanned, lexer::Lexer}, sigma_upper + automatan::{fa::Fa, pda::Pda, tm::Tm}, + delta_lower, epsilon, gamma_upper, + loader::{self, Context, Machine, Span, Spanned, lexer::Lexer}, + sigma_upper, }; use wasm_bindgen::prelude::wasm_bindgen; @@ -148,65 +151,99 @@ pub struct CompileResult { pub machine: Option, } -trait FixupSpan{ +trait FixupSpan { fn fixup(&mut self, func: impl FnMut(Span) -> Span); } -impl<'a> FixupSpan for Machine<'a>{ +impl<'a> FixupSpan for Machine<'a> { fn fixup(&mut self, func: impl FnMut(Span) -> Span) { - match self{ + match self { Machine::Fa(fa) => fa.fixup(func), Machine::Pda(pda) => pda.fixup(func), Machine::Tm(tm) => tm.fixup(func), } } } -impl<'a> FixupSpan for Fa<'a>{ +impl<'a> FixupSpan for Fa<'a> { fn fixup(&mut self, mut func: impl FnMut(Span) -> Span) { - self.alphabet.values_mut().for_each(|v| v.definition = func(v.definition)); - self.states.values_mut().for_each(|v| v.definition = func(v.definition)); - self.final_states.values_mut().for_each(|v| v.definition = func(v.definition)); - self.transitions.values_mut().flat_map(|v|v.iter_mut()).for_each(|e|{ - e.transition = func(e.transition); - e.function = func(e.function); - }); + self.alphabet + .values_mut() + .for_each(|v| v.definition = func(v.definition)); + self.states + .values_mut() + .for_each(|v| v.definition = func(v.definition)); + self.final_states + .values_mut() + .for_each(|v| v.definition = func(v.definition)); + self.transitions + .values_mut() + .flat_map(|v| v.iter_mut()) + .for_each(|e| { + e.transition = func(e.transition); + e.function = func(e.function); + }); } } -impl<'a> FixupSpan for Pda<'a>{ +impl<'a> FixupSpan for Pda<'a> { fn fixup(&mut self, mut func: impl FnMut(Span) -> Span) { - self.alphabet.values_mut().for_each(|v| v.definition = func(v.definition)); - self.states.values_mut().for_each(|v| v.definition = func(v.definition)); - self.symbols.values_mut().for_each(|v| v.definition = func(v.definition)); - self.final_states.as_mut().unwrap_or(&mut HashMap::new()).values_mut().for_each(|v| v.definition = func(v.definition)); - self.transitions.values_mut().flat_map(|v|v.iter_mut()).for_each(|e|{ - e.transition = func(e.transition); - e.function = func(e.function); - }); + self.alphabet + .values_mut() + .for_each(|v| v.definition = func(v.definition)); + self.states + .values_mut() + .for_each(|v| v.definition = func(v.definition)); + self.symbols + .values_mut() + .for_each(|v| v.definition = func(v.definition)); + self.final_states + .as_mut() + .unwrap_or(&mut HashMap::new()) + .values_mut() + .for_each(|v| v.definition = func(v.definition)); + self.transitions + .values_mut() + .flat_map(|v| v.iter_mut()) + .for_each(|e| { + e.transition = func(e.transition); + e.function = func(e.function); + }); } } -impl<'a> FixupSpan for Tm<'a>{ +impl<'a> FixupSpan for Tm<'a> { fn fixup(&mut self, mut func: impl FnMut(Span) -> Span) { - self.states.values_mut().for_each(|v| v.definition = func(v.definition)); - self.symbols.values_mut().for_each(|v| v.definition = func(v.definition)); - self.final_states.values_mut().for_each(|v| v.definition = func(v.definition)); - self.transitions.values_mut().flat_map(|v|v.iter_mut()).for_each(|e|{ - e.transition = func(e.transition); - e.function = func(e.function); - }); + self.states + .values_mut() + .for_each(|v| v.definition = func(v.definition)); + self.symbols + .values_mut() + .for_each(|v| v.definition = func(v.definition)); + self.final_states + .values_mut() + .for_each(|v| v.definition = func(v.definition)); + self.transitions + .values_mut() + .flat_map(|v| v.iter_mut()) + .for_each(|e| { + e.transition = func(e.transition); + e.function = func(e.function); + }); } } - - #[wasm_bindgen] pub fn compile(input: &str) -> CompileResult { let mut ctx = Context::new(input); let result = automata::loader::parse_universal(&mut ctx); let machine = result.map(|mut result| { - result.fixup(|span|Span(input[..span.0].chars().map(char::len_utf16).sum(), input[..span.1].chars().map(char::len_utf16).sum())); + result.fixup(|span| { + Span( + input[..span.0].chars().map(char::len_utf16).sum(), + input[..span.1].chars().map(char::len_utf16).sum(), + ) + }); serde_json::to_string(&result).unwrap() });