diff --git a/automata/examples/matched_parentheses_codegen/src/parser.rs b/automata/examples/matched_parentheses_codegen/src/parser.rs index 0f570f3..e1f62eb 100644 --- a/automata/examples/matched_parentheses_codegen/src/parser.rs +++ b/automata/examples/matched_parentheses_codegen/src/parser.rs @@ -58,7 +58,7 @@ fn state_0>( '('..='(' => { let detour = state_0(input, (), Some(("parentheses", index)))?; let postprocessed = (|(), ()| ())(acc, detour); - state_0(input, postprocessed, stack_top) + state_0(input, acc, stack_top) } ')'..=')' => match stack_top { Some((region, _)) if region == "parentheses" => Ok(acc), diff --git a/automata/src/check.rs b/automata/src/check.rs index de3950b..6baa748 100644 --- a/automata/src/check.rs +++ b/automata/src/check.rs @@ -264,7 +264,15 @@ impl> Check for Transition { #[inline] fn check(&self, n_states: NonZeroUsize) -> Result<(), IllFormed> { match *self { - Self::Lateral { ref dst, .. } | Self::Call { ref dst, .. } => dst.check(n_states), + Self::Lateral { ref dst, .. } => dst.check(n_states), + Self::Call { + ref detour, + ref dst, + .. + } => { + detour.check(n_states)?; + dst.check(n_states) + } Self::Return { .. } => Ok(()), } } diff --git a/automata/src/combinators.rs b/automata/src/combinators.rs index ebf1aec..b18d602 100644 --- a/automata/src/combinators.rs +++ b/automata/src/combinators.rs @@ -197,12 +197,16 @@ fn add_tail_call_transition>( Transition::Call { region, ref detour, - ref dst, + dst, combine, } => Transition::Call { region, detour: add_tail_call_c(detour, other_init, accepting_indices), - dst: add_tail_call_c(dst, other_init, accepting_indices), + dst: Box::new(add_tail_call_transition( + *dst, + other_init, + accepting_indices, + )), combine, }, Transition::Return { region } => Transition::Return { region }, diff --git a/automata/src/generalize.rs b/automata/src/generalize.rs index c2c0f16..922a0b9 100644 --- a/automata/src/generalize.rs +++ b/automata/src/generalize.rs @@ -76,7 +76,7 @@ impl> Transition { } => Transition::Call { region, detour: detour.view().collect(), - dst: dst.view().collect(), + dst: Box::new(dst.generalize()), combine, }, Self::Return { region } => Transition::Return { region }, diff --git a/automata/src/graph.rs b/automata/src/graph.rs index afc850a..9952bb6 100644 --- a/automata/src/graph.rs +++ b/automata/src/graph.rs @@ -382,13 +382,13 @@ fn fix_indices_transition>( }, Transition::Call { region, - detour, + ref detour, dst, combine, } => Transition::Call { region, - detour: unwrap!(ordering.binary_search(&detour)), - dst: unwrap!(ordering.binary_search(&dst)), + detour: unwrap!(ordering.binary_search(detour)), + dst: Box::new(fix_indices_transition(*dst, ordering)), combine, }, Transition::Return { region } => Transition::Return { region }, diff --git a/automata/src/in_progress.rs b/automata/src/in_progress.rs index 799c873..f5927a9 100644 --- a/automata/src/in_progress.rs +++ b/automata/src/in_progress.rs @@ -6,7 +6,7 @@ //! Execute an automaton on an input sequence. -use crate::{try_merge, Ctrl, Graph, IllFormed, Input, ToSrc}; +use crate::{try_merge, Ctrl, Graph, IllFormed, Input, ToSrc, Transition}; use core::fmt; /// Execute an automaton on an input sequence. @@ -17,7 +17,7 @@ pub struct InProgress<'graph, I: Input, C: Ctrl, In: Iterator> { /// Iterator over input tokens. pub input: In, /// Internal stack. - pub stack: Vec, + pub stack: Vec>, /// Internal state. pub ctrl: C, /// Output type as we go. @@ -90,7 +90,7 @@ fn step>( graph: &Graph, ctrl: &C, maybe_token: Option, - stack: &mut Vec, + stack: &mut Vec>, output_t: &str, ) -> Result<(Option, String), ParseError> { ctrl.view().try_fold((), |(), i| { diff --git a/automata/src/lib.rs b/automata/src/lib.rs index 201c144..1bd4e3d 100644 --- a/automata/src/lib.rs +++ b/automata/src/lib.rs @@ -233,7 +233,10 @@ pub fn dyck_d() -> Deterministic { Transition::Call { region: "parentheses", detour: 0, - dst: 0, + dst: Box::new(Transition::Lateral { + dst: 0, + update: None, + }), combine: ff!(|(), ()| ()), }, ), @@ -269,7 +272,10 @@ pub fn dyck_nd() -> Nondeterministic { Transition::Call { region: "parentheses", detour: iter::once(0).collect(), - dst: iter::once(0).collect(), + dst: Box::new(Transition::Lateral { + dst: iter::once(0).collect(), + update: None, + }), combine: ff!(|(), ()| ()), }, ), diff --git a/automata/src/map_indices.rs b/automata/src/map_indices.rs index 65ae147..e2c730e 100644 --- a/automata/src/map_indices.rs +++ b/automata/src/map_indices.rs @@ -83,7 +83,7 @@ impl> Transition { } => Self::Call { region, detour: detour.map_indices(&mut f), - dst: dst.map_indices(f), + dst: Box::new(dst.map_indices(f)), combine, }, Self::Return { region } => Self::Return { region }, diff --git a/automata/src/merge.rs b/automata/src/merge.rs index daa6ee3..4c011e7 100644 --- a/automata/src/merge.rs +++ b/automata/src/merge.rs @@ -219,9 +219,7 @@ impl> Merge for Transition { detour: l_detour .merge(r_detour) .map_err(|(a, b)| IllFormed::Superposition(a, b))?, - dst: l_dst - .merge(r_dst) - .map_err(|(a, b)| IllFormed::Superposition(a, b))?, + dst: Box::new(l_dst.merge(*r_dst)?), combine: l_combine.merge(r_combine).map_err(|(a, b)| { IllFormed::IncompatibleCombinators(Box::new(a), Box::new(b)) })?, diff --git a/automata/src/qc.rs b/automata/src/qc.rs index 38a22e7..b70922a 100644 --- a/automata/src/qc.rs +++ b/automata/src/qc.rs @@ -152,37 +152,33 @@ shrink_only!(|self: &Curry| match *self { shrink_only!(|self: &Transition| { #[allow(clippy::shadow_unrelated, unreachable_code, unused_variables)] - match self.clone() { - Self::Return { region: _ } => Box::new(iter::empty()), - Self::Lateral { dst, update } => Box::new( + match *self { + Self::Return { .. } => Box::new(iter::empty()), + Self::Lateral { + ref dst, + ref update, + } => Box::new( iter::once(Self::Return { region: "region" }).chain( - (dst, update) + (dst.clone(), update.clone()) .shrink() .map(|(dst, update)| Self::Lateral { dst, update }), ), ), Self::Call { - region: _, - detour, - dst, - combine, - } => { - Box::new( - Self::Lateral { - dst: dst.clone(), - update: None, - } - .shrink() - .chain((detour, dst, combine).shrink().map( - |(detour, dst, combine)| Self::Call { - region: "region", - detour, - dst, - combine, - }, - )), - ) - } + ref detour, + ref dst, + ref combine, + .. + } => Box::new(dst.as_ref().shrink().chain( + (detour.clone(), dst.clone(), combine.clone()).shrink().map( + |(detour, dst, combine)| Self::Call { + region: "region", + detour, + dst, + combine, + }, + ), + )), } }); @@ -255,7 +251,7 @@ impl> Transition { |n, r| Self::Call { region: "region", detour: C::arbitrary_given(n, r), - dst: C::arbitrary_given(n, r), + dst: Box::new(Transition::arbitrary_given(n, r)), combine: Arbitrary::arbitrary(r), }, |_, _| Self::Return { region: "region" }, diff --git a/automata/src/reindex.rs b/automata/src/reindex.rs index b4a1bbf..f1f1349 100644 --- a/automata/src/reindex.rs +++ b/automata/src/reindex.rs @@ -92,7 +92,7 @@ impl> Transition { } => Self::Call { region, detour: detour.clone().map_indices(update_fn), - dst: dst.clone().map_indices(update_fn), + dst: Box::new(dst.clone().map_indices(update_fn)), combine: combine.clone(), }, Self::Return { region } => Self::Return { region }, diff --git a/automata/src/test.rs b/automata/src/test.rs index 747a1af..b58d60c 100644 --- a/automata/src/test.rs +++ b/automata/src/test.rs @@ -337,6 +337,14 @@ mod prop { }; star.accept(input).is_ok() } + + fn star_star_identity(d: Deterministic, input: Vec) -> bool { + let Ok(once) = panic::catch_unwind(|| d.star()) else { + return true; + }; + let twice = once.clone().star(); + once.accept(input.iter().copied()) == twice.accept(input) + } } } @@ -506,7 +514,10 @@ mod reduced { transitions: Curry::Wildcard(Transition::Call { region: "region", detour: 0, - dst: 1, + dst: Box::new(Transition::Lateral { + dst: 1, + update: None, + }), combine: FF { src: "|(), ()| ()".to_owned(), lhs_t: "()".to_owned(), @@ -662,7 +673,10 @@ mod reduced { transitions: Curry::Wildcard(Transition::Call { region: "region", detour: 0, - dst: 0, + dst: Box::new(Transition::Lateral { + dst: 0, + update: None, + }), combine: ff!(|(), ()| ()), }), non_accepting: BTreeSet::new(), diff --git a/automata/src/to_src.rs b/automata/src/to_src.rs index 8f500cc..a5dc9ac 100644 --- a/automata/src/to_src.rs +++ b/automata/src/to_src.rs @@ -291,7 +291,9 @@ impl Curry { match *self { Self::Wildcard(ref etc) => format!( r#" - _ => {},"#, + _ => {{ + {} + }}"#, etc.to_src(), ), Self::Scrutinize { @@ -318,7 +320,9 @@ impl RangeMap { self.0.iter().fold(String::new(), |acc, (k, v)| { format!( r#"{acc} - {} => {},"#, + {} => {{ + {} + }},"#, k.to_src(), v.to_src(), ) @@ -342,15 +346,15 @@ impl Transition { Self::Call { region, detour, - dst, + ref dst, combine: FF { ref src, .. }, } => format!( - r#"{{ + "\ let detour = state_{detour}(input, (), Some(({}, index)))?; let postprocessed = ({src})(acc, detour); - state_{dst}(input, postprocessed, stack_top) - }}"#, + {}", region.to_src(), + dst.to_src(), ), Self::Return { region } => { format!( diff --git a/automata/src/transition.rs b/automata/src/transition.rs index 51aa9ed..06120d6 100644 --- a/automata/src/transition.rs +++ b/automata/src/transition.rs @@ -7,7 +7,7 @@ //! Transition in an automaton: an action and a destination state. use crate::{Ctrl, Input, InputError, Merge, ParseError, Update, FF}; -use core::{cmp, mem}; +use core::{cmp, iter, mem}; use std::collections::BTreeSet; // TODO: rename `Call` to `Open` and `Return` to `Close` @@ -137,7 +137,7 @@ impl> Transition { pub fn invoke( &self, output_t: &str, - stack: &mut Vec, + stack: &mut Vec>, ) -> Result, ParseError> { match *self { Self::Lateral { @@ -156,14 +156,17 @@ impl> Transition { ref dst, .. } => { - stack.push(dst.clone()); + stack.push(*dst.clone()); Ok(Some((detour.clone(), "()".to_owned()))) } Self::Return { .. } => { let rtn_to = stack .pop() .ok_or(ParseError::BadInput(InputError::Unopened))?; - Ok(Some((rtn_to, output_t.to_owned()))) + // Ok(Some((rtn_to, output_t.to_owned()))) + // No longer strictly small-step semantics, + // but the alternative is a nightmare + rtn_to.invoke(output_t, stack) } } } @@ -185,15 +188,15 @@ impl> Transition { /// For returns, it's nothing. #[inline] #[must_use] - pub fn dsts(&self) -> Vec<&C> { + pub fn dsts(&self) -> BTreeSet<&C> { match *self { - Self::Lateral { ref dst, .. } => vec![dst], + Self::Lateral { ref dst, .. } => iter::once(dst).collect(), Self::Call { ref detour, ref dst, .. - } => vec![detour, dst], - Self::Return { .. } => vec![], + } => iter::once(detour).chain(dst.dsts()).collect(), + Self::Return { .. } => BTreeSet::new(), } } @@ -227,7 +230,7 @@ impl Transition { } => Transition::Call { region, detour: C::from_usize(detour), - dst: C::from_usize(dst), + dst: Box::new(dst.convert_ctrl()), combine, }, Self::Return { region } => Transition::Return { region }, @@ -241,11 +244,12 @@ impl Transition> { #[allow(clippy::missing_panics_doc)] pub fn star(&mut self, init: &BTreeSet, accepting: &BTreeSet) { match *self { - Transition::Lateral { ref mut dst, .. } | Transition::Call { ref mut dst, .. } => { + Transition::Lateral { ref mut dst, .. } => { if dst.iter().any(|i| accepting.contains(i)) { *dst = unwrap!(mem::take(dst).merge(init.clone())); } } + Transition::Call { ref mut dst, .. } => dst.star(init, accepting), Transition::Return { .. } => {} } } diff --git a/examples/json/src/parser.rs b/examples/json/src/parser.rs index fdf3c95..e3316de 100644 --- a/examples/json/src/parser.rs +++ b/examples/json/src/parser.rs @@ -69,10 +69,18 @@ fn state_1>( match input.next() { None => Err(Error::UserDefined { messages: &["Expected only a single token on [b\' \'..=b\' \'] but got another token after it", "Expected only a single token on [b\'\\n\'..=b\'\\n\'] but got another token after it", "Expected only a single token on [b\'\\r\'..=b\'\\r\'] but got another token after it", "Expected only a single token on [b\'\\t\'..=b\'\\t\'] but got another token after it"] }), Some((index, token)) => match token { - b'\t'..=b'\t' => state_0(input, acc, stack_top), - b'\n'..=b'\n' => state_0(input, acc, stack_top), - b'\r'..=b'\r' => state_0(input, acc, stack_top), - b' '..=b' ' => state_0(input, acc, stack_top), + b'\t'..=b'\t' => { + state_0(input, acc, stack_top) + }, + b'\n'..=b'\n' => { + state_0(input, acc, stack_top) + }, + b'\r'..=b'\r' => { + state_0(input, acc, stack_top) + }, + b' '..=b' ' => { + state_0(input, acc, stack_top) + }, _ => Err(Error::Absurd { index, token }) }, }