Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specify edge-case behavior #55

Merged
merged 2 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions automata/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ pub enum IllFormed<I: Input, C: Ctrl<I>> {
/// Second output possibility.
possibility_2: Box<Transition<I, C>>,
},
/// In a `Curry`, a wildcard would have erased a fallback output.
WildcardFallback,
/// Can't go to two different (deterministic) states at the same time.
Superposition(usize, usize),
/// Can't call two different functions on half-constructed outputs at the same time.
Expand Down Expand Up @@ -80,7 +78,6 @@ impl<I: Input> IllFormed<I, usize> {
possibility_1: Box::new(possibility_1.convert_ctrl()),
possibility_2: Box::new(possibility_2.convert_ctrl()),
},
IllFormed::WildcardFallback => IllFormed::WildcardFallback,
IllFormed::Superposition(a, b) => IllFormed::Superposition(a, b),
IllFormed::IncompatibleCallbacks(a, b) => IllFormed::IncompatibleCallbacks(a, b),
IllFormed::IncompatibleCombinators(a, b) => IllFormed::IncompatibleCombinators(a, b),
Expand Down Expand Up @@ -140,12 +137,6 @@ impl<I: Input, C: Ctrl<I>> fmt::Display for IllFormed<I, C> {
possibility_2.to_src(),
)
}
Self::WildcardFallback => {
write!(
f,
"A wildcard match would have overwritten a fallback output",
)
}
Self::Superposition(a, b) => write!(
f,
"Tried to visit two different deterministic states \
Expand Down
13 changes: 13 additions & 0 deletions automata/src/curry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,19 @@ impl<I: Input, C: Ctrl<I>> Curry<I, C> {
} => Box::new(filter.values_mut().chain(fallback)),
}
}

/// Check if this parser ever could, at any point, involve a fallback transition.
#[inline]
#[must_use]
pub const fn involves_any_fallback(&self) -> bool {
matches!(
*self,
Self::Scrutinize {
fallback: Some(_),
..
}
)
}
}

impl<I: Input> Curry<I, usize> {
Expand Down
7 changes: 7 additions & 0 deletions automata/src/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,13 @@ impl<I: Input, C: Ctrl<I>> Graph<I, C> {
.map(|s| s.reindex(&self.states, &index_map))
.collect();
}

/// Check if this parser ever could, at any point, involve a fallback transition.
#[inline]
#[must_use]
pub fn involves_any_fallback(&self) -> bool {
self.states.iter().any(State::involves_any_fallback)
}
}

/// Use an ordering on subsets to translate each subset into a specific state.
Expand Down
7 changes: 3 additions & 4 deletions automata/src/merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,10 @@ impl<I: Input, C: Ctrl<I>> Merge for Curry<I, C> {
fn merge(self, other: Self) -> Result<Self, Self::Error> {
match (self, other) {
(Self::Wildcard(lhs), Self::Wildcard(rhs)) => Ok(Self::Wildcard(lhs.merge(rhs)?)),
(Self::Wildcard(w), Self::Scrutinize { filter, fallback })
| (Self::Scrutinize { filter, fallback }, Self::Wildcard(w)) => {
(Self::Wildcard(w), Self::Scrutinize { filter, .. })
| (Self::Scrutinize { filter, .. }, Self::Wildcard(w)) => {
match filter.0.first_key_value() {
None if fallback.is_none() => Ok(Self::Wildcard(w)),
None => Err(IllFormed::WildcardFallback),
None => Ok(Self::Wildcard(w)),
Some((k, v)) => Err(IllFormed::WildcardMask {
arg_token: Some(k.clone()),
possibility_1: Box::new(w),
Expand Down
7 changes: 7 additions & 0 deletions automata/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ impl<I: Input, C: Ctrl<I>> State<I, C> {
})
})
}

/// Check if this parser ever could, at any point, involve a fallback transition.
#[inline]
#[must_use]
pub const fn involves_any_fallback(&self) -> bool {
self.transitions.involves_any_fallback()
}
}

impl<I: Input> State<I, usize> {
Expand Down
134 changes: 108 additions & 26 deletions automata/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,6 @@
clippy::use_debug
)]

mod unit {
use crate::*;
use std::collections::BTreeMap;

#[test]
fn check_reject_wildcard_mask_fallback() {
let lhs = Curry::<(), _>::Scrutinize {
filter: RangeMap(BTreeMap::new()),
fallback: Some(Transition::Lateral {
dst: 0,
update: None,
}),
};
let rhs = Curry::<(), _>::Wildcard(Transition::Return { region: "region" });
drop(lhs.merge(rhs).unwrap_err());
}
}

#[cfg(feature = "quickcheck")]
mod prop {
use crate::*;
Expand Down Expand Up @@ -239,6 +221,9 @@ mod prop {
rhs: Deterministic<u8>,
input: Vec<u8>
) -> bool {
if lhs.involves_any_fallback() || rhs.involves_any_fallback() {
return true;
}
let Ok(union) = panic::catch_unwind(|| lhs.clone() | rhs.clone()) else {
return true;
};
Expand All @@ -249,7 +234,7 @@ mod prop {
return true;
}
let union_accept = union.accept(input.iter().copied());
match (
if !match (
lhs.accept(input.iter().copied()),
rhs.accept(input.iter().copied()),
) {
Expand All @@ -271,7 +256,19 @@ mod prop {
(Err(ParseError::BadInput(..)), Err(ParseError::BadInput(..))) => {
union_accept.is_err()
}
} {
return false;
}
let Ok(symm) = panic::catch_unwind(|| rhs | lhs) else {
return false;
};
if symm.check().is_err() {
return false;
}
if symm.determinize().is_err() {
return false;
}
union_accept == symm.accept(input.iter().copied())
}

fn sort(parser: Nondeterministic<u8>, input: Vec<u8>) -> bool {
Expand All @@ -289,6 +286,9 @@ mod prop {
}

fn shr(lhs: Deterministic<u8>, rhs: Deterministic<u8>, input: Vec<u8>) -> bool {
if lhs.involves_any_fallback() || rhs.involves_any_fallback() {
return true;
}
let splittable = (0..=input.len()).any(|i| {
lhs.accept(input[..i].iter().copied()).is_ok() &&
rhs.accept(input[i..].iter().copied()).is_ok()
Expand Down Expand Up @@ -328,7 +328,10 @@ mod reduced {
assert_eq!(dd.accept(input.iter().copied()), d.accept(input));
}

fn union(lhs: &Deterministic<u8>, rhs: &Deterministic<u8>, input: &[u8]) {
fn union(lhs: Deterministic<u8>, rhs: Deterministic<u8>, input: Vec<u8>) {
if lhs.involves_any_fallback() || rhs.involves_any_fallback() {
return;
}
let Ok(union) = panic::catch_unwind(|| lhs.clone() | rhs.clone()) else {
return;
};
Expand All @@ -339,7 +342,7 @@ mod reduced {
{
println!();
println!("LHS:");
let mut run = input.iter().copied().run(lhs);
let mut run = input.iter().copied().run(&lhs);
println!(" {run:?}");
while let Some(r) = run.next() {
println!("{r:?} {run:?}");
Expand All @@ -348,7 +351,7 @@ mod reduced {
{
println!();
println!("RHS:");
let mut run = input.iter().copied().run(rhs);
let mut run = input.iter().copied().run(&rhs);
println!(" {run:?}");
while let Some(r) = run.next() {
println!("{r:?} {run:?}");
Expand Down Expand Up @@ -384,12 +387,19 @@ mod reduced {
assert!(matches!(union_accept, Err(ParseError::BadParser(..))));
}
(Err(ParseError::BadInput(..)), Err(ParseError::BadInput(..))) => {
drop(union_accept.unwrap_err());
let _ = union_accept.as_ref().unwrap_err();
}
}
let symm = rhs | lhs;
symm.check().unwrap();
drop(symm.determinize().unwrap());
assert_eq!(union_accept, symm.accept(input));
}

fn shr(lhs: Deterministic<u8>, rhs: Deterministic<u8>, input: Vec<u8>) {
if lhs.involves_any_fallback() || rhs.involves_any_fallback() {
return;
}
let splittable = (0..=input.len()).any(|i| {
lhs.accept(input[..i].iter().copied()).is_ok()
&& rhs.accept(input[i..].iter().copied()).is_ok()
Expand Down Expand Up @@ -469,7 +479,7 @@ mod reduced {
#[test]
fn union_1() {
union(
&Graph {
Graph {
states: vec![State {
transitions: Curry::Scrutinize {
filter: RangeMap(BTreeMap::new()),
Expand All @@ -482,14 +492,50 @@ mod reduced {
}],
initial: 0,
},
&Graph {
Graph {
states: vec![State {
transitions: Curry::Wildcard(Transition::Return { region: "region" }),
non_accepting: BTreeSet::new(),
}],
initial: 0,
},
&[0],
vec![0],
);
}

#[test]
fn union_2() {
union(
Graph {
states: vec![State {
transitions: Curry::Scrutinize {
filter: RangeMap(
iter::once((
Range { first: 0, last: 0 },
Transition::Return { region: "region" },
))
.collect(),
),
fallback: None,
},
non_accepting: BTreeSet::new(),
}],
initial: 0,
},
Graph {
states: vec![State {
transitions: Curry::Scrutinize {
filter: RangeMap(BTreeMap::new()),
fallback: Some(Transition::Lateral {
dst: 0,
update: None,
}),
},
non_accepting: BTreeSet::new(),
}],
initial: 0,
},
vec![0],
);
}

Expand Down Expand Up @@ -519,4 +565,40 @@ mod reduced {
vec![0],
);
}

#[test]
fn shr_2() {
shr(
Graph {
states: vec![State {
transitions: Curry::Scrutinize {
filter: RangeMap(
iter::once((
Range { first: 0, last: 0 },
Transition::Return { region: "region" },
))
.collect(),
),
fallback: None,
},
non_accepting: BTreeSet::new(),
}],
initial: 0,
},
Graph {
states: vec![State {
transitions: Curry::Scrutinize {
filter: RangeMap(BTreeMap::new()),
fallback: Some(Transition::Lateral {
dst: 0,
update: None,
}),
},
non_accepting: BTreeSet::new(),
}],
initial: 0,
},
vec![0],
);
}
}