Skip to content

Commit

Permalink
Add an optional fallback transition to each state (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
wrsturgeon authored Nov 12, 2023
2 parents 604fdab + 16ed746 commit 4e6fdf8
Show file tree
Hide file tree
Showing 14 changed files with 121 additions and 5 deletions.
42 changes: 42 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ license = "MPL-2.0"
repository = "https://github.com/wrsturgeon/inator"
build = "build.rs"

[workspace]
members = [
"automata",
"automata/examples/matched_parentheses_codegen",
"examples/json",
]

[dependencies]
inator-automata = { path = "automata" }

Expand Down
3 changes: 3 additions & 0 deletions automata/src/combinators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ fn add_tail_call_state<I: Input, C: Ctrl<I>>(
State {
transitions: add_tail_call_curry(s.transitions, other_init, accepting_indices),
non_accepting: s.non_accepting,
fallback: s
.fallback
.map(|f| add_tail_call_transition(f, other_init, accepting_indices)),
}
}

Expand Down
1 change: 1 addition & 0 deletions automata/src/generalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ impl<I: Input, C: Ctrl<I>> State<I, C> {
State {
transitions: self.transitions.generalize(),
non_accepting: self.non_accepting,
fallback: self.fallback.map(Transition::generalize),
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions automata/src/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,12 @@ impl<I: Input, C: Ctrl<I>> Graph<I, C> {
let State {
transitions,
non_accepting,
fallback,
} = unwrap!(subsets_as_states.remove(set));
State {
transitions: fix_indices_curry(transitions, &ordering),
non_accepting,
fallback: fallback.map(|f| fix_indices_transition(f, &ordering)),
}
})
.collect(),
Expand Down Expand Up @@ -181,6 +183,7 @@ impl<I: Input, C: Ctrl<I>> Graph<I, C> {
None => State {
transitions: Curry::Scrutinize(RangeMap(BTreeMap::new())),
non_accepting: iter::once("Unexpected token".to_owned()).collect(),
fallback: None,
},
// If they successfully merged, return the merged state
Some(Ok(ok)) => ok,
Expand All @@ -193,6 +196,10 @@ impl<I: Input, C: Ctrl<I>> Graph<I, C> {
.transitions
.values()
.flat_map(|t| t.dsts().into_iter().cloned())
.chain(mega_state.fallback.as_ref().map_or_else(
|| vec![].into_iter().cloned(),
|f| f.dsts().into_iter().cloned(),
))
.collect();

// Insert the finished value (also to tell all below iterations that we've covered this case)
Expand Down
3 changes: 2 additions & 1 deletion automata/src/in_progress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ fn step<I: Input, C: Ctrl<I>>(
// Merge into a huge aggregate transition and act on that instead of individual transitions
match try_merge(states.filter_map(|s| match s.transitions.get(&token) {
Err(e) => Some(Err(e)),
Ok(opt) => opt.map(|t| Ok(t.clone())),
Ok(None) => s.fallback.as_ref().map(|t| Ok(t.clone())),
Ok(Some(t)) => Some(Ok(t.clone())),
})) {
None => Err(ParseError::BadInput(InputError::Absurd)),
Some(Err(e)) => Err(ParseError::BadParser(e)),
Expand Down
2 changes: 2 additions & 0 deletions automata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ pub fn dyck_d() -> Deterministic<char> {
.collect(),
)),
non_accepting: BTreeSet::new(),
fallback: None,
}],
initial: 0,
}
Expand Down Expand Up @@ -280,6 +281,7 @@ pub fn dyck_nd() -> Nondeterministic<char> {
.collect(),
)),
non_accepting: BTreeSet::new(),
fallback: None,
}],
initial: iter::once(0).collect(),
}
Expand Down
4 changes: 4 additions & 0 deletions automata/src/merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ impl<I: Input, C: Ctrl<I>> Merge for State<I, C> {
self.non_accepting.extend(other.non_accepting);
self.non_accepting
},
fallback: self
.fallback
.merge(other.fallback)
.map_or_else(|(a, b)| a.merge(b).map(Some), Ok)?,
})
}
}
Expand Down
10 changes: 8 additions & 2 deletions automata/src/qc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,16 @@ macro_rules! shrink_only {
}

shrink_only!(|self: &State| Box::new(
(self.transitions.clone(), self.non_accepting.clone(),)
(
self.transitions.clone(),
self.non_accepting.clone(),
self.fallback.clone()
)
.shrink()
.map(|(transitions, non_accepting)| Self {
.map(|(transitions, non_accepting, fallback)| Self {
transitions,
non_accepting,
fallback
})
));

Expand Down Expand Up @@ -190,6 +195,7 @@ impl<C: Ctrl<u8>> State<u8, C> {
Self {
transitions: Curry::arbitrary_given(n_states, g),
non_accepting: BTreeSet::arbitrary(g),
fallback: bool::arbitrary(g).then(|| Transition::arbitrary_given(n_states, g)),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions automata/src/reindex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ impl<I: Input, C: Ctrl<I>> State<I, C> {
State {
transitions: self.transitions.reindex(states, index_map),
non_accepting: self.non_accepting.clone(),
fallback: self.fallback.as_ref().map(|f| f.reindex(states, index_map)),
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion automata/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

//! State, i.e. a node in an automaton graph.

use crate::{Ctrl, Curry, IllFormed, Input};
use crate::{Ctrl, Curry, IllFormed, Input, Transition};
use core::cmp;
use std::collections::BTreeSet;

Expand All @@ -18,6 +18,8 @@ pub struct State<I: Input, C: Ctrl<I>> {
pub transitions: Curry<I, C>,
/// If input ends while in this state, should we accept?
pub non_accepting: BTreeSet<String>,
/// If no other transitions work, take this one (if any).
pub fallback: Option<Transition<I, C>>,
}

impl<I: Input, C: Ctrl<I>> State<I, C> {
Expand Down Expand Up @@ -47,6 +49,7 @@ impl<I: Input> State<I, usize> {
State {
transitions: self.transitions.convert_ctrl(),
non_accepting: self.non_accepting,
fallback: self.fallback.map(Transition::convert_ctrl),
}
}
}
Expand All @@ -57,6 +60,7 @@ impl<I: Input, C: Ctrl<I>> Clone for State<I, C> {
Self {
transitions: self.transitions.clone(),
non_accepting: self.non_accepting.clone(),
fallback: self.fallback.clone(),
}
}
}
Expand Down
31 changes: 31 additions & 0 deletions automata/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ mod reduced {
states: vec![State {
transitions: Curry::Wildcard(Transition::Return { region: "region" }),
non_accepting: BTreeSet::new(),
fallback: None,
}],
initial: 0,
},
Expand All @@ -330,18 +331,22 @@ mod reduced {
update: update!(|(), _| {}),
}),
non_accepting: BTreeSet::new(),
fallback: None,
},
State {
transitions: Curry::Wildcard(Transition::Return { region: "region" }),
non_accepting: BTreeSet::new(),
fallback: None,
},
State {
transitions: Curry::Scrutinize(RangeMap(BTreeMap::new())),
non_accepting: BTreeSet::new(),
fallback: None,
},
State {
transitions: Curry::Wildcard(Transition::Return { region: "region" }),
non_accepting: iter::once(String::new()).collect(),
fallback: None,
},
State {
transitions: Curry::Wildcard(Transition::Call {
Expand All @@ -356,11 +361,37 @@ mod reduced {
},
}),
non_accepting: BTreeSet::new(),
fallback: None,
},
],
initial: 0,
},
vec![],
);
}

#[test]
fn determinize_identity_2() {
determinize_identity(
&Graph {
states: vec![
State {
transitions: Curry::Wildcard(Transition::Return { region: "region" }),
non_accepting: BTreeSet::new(),
fallback: None,
},
State {
transitions: Curry::Scrutinize(RangeMap(BTreeMap::new())),
non_accepting: BTreeSet::new(),
fallback: Some(Transition::Lateral {
dst: 0,
update: update!(|_: u8, _| ()),
}),
},
],
initial: 1,
},
vec![],
);
}
}
6 changes: 5 additions & 1 deletion automata/src/to_src.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,10 @@ impl<I: Input> State<I, usize> {
+ "] })"
},
);
let on_unrecognized = self.fallback.as_ref().map_or_else(
|| "Err(Error::Absurd { index, token })".to_owned(),
Transition::to_src,
);
Ok(format!(
r#"
Expand All @@ -276,7 +280,7 @@ fn state_{i}<I: Iterator<Item = (usize, {token_t})>>(input: &mut I, acc: {input_
match input.next() {{
None => {on_none},
Some((index, token)) => match token {{{on_some}
_ => Err(Error::Absurd {{ index, token }}),
_ => {on_unrecognized},
}},
}}
}}"#,
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ pub fn empty<I: Input>() -> Deterministic<I> {
states: vec![State {
transitions: Curry::Scrutinize(RangeMap(BTreeMap::new())),
non_accepting: BTreeSet::new(),
fallback: None,
}],
initial: 0,
}
Expand All @@ -189,6 +190,7 @@ pub fn any_of<I: Input>(range: Range<I>, update: Update<I>) -> Deterministic<I>
State {
transitions: Curry::Scrutinize(RangeMap(BTreeMap::new())),
non_accepting: BTreeSet::new(),
fallback: None,
},
State {
non_accepting: iter::once(format!(
Expand All @@ -200,6 +202,7 @@ pub fn any_of<I: Input>(range: Range<I>, update: Update<I>) -> Deterministic<I>
transitions: Curry::Scrutinize(RangeMap(
iter::once((range, Transition::Lateral { dst: 0, update })).collect(),
)),
fallback: None,
},
],
initial: 1,
Expand Down

0 comments on commit 4e6fdf8

Please sign in to comment.