Skip to content

Commit

Permalink
Have to test via codegen
Browse files Browse the repository at this point in the history
  • Loading branch information
wrsturgeon committed Nov 4, 2023
1 parent 2c2a386 commit 647323b
Show file tree
Hide file tree
Showing 14 changed files with 207 additions and 19 deletions.
27 changes: 27 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
workspace = { members = ["examples/digit"] }
[package]
name = "inator"
authors = ["Will Sturgeon <[email protected]>"]
Expand Down
2 changes: 1 addition & 1 deletion automata/src/combinators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

//! Operations on nondeterministic finite automata returning nondeterministic finite automata.

#![allow(clippy::manual_assert, clippy::match_wild_err_arm, clippy::panic)]
#![allow(clippy::match_wild_err_arm, clippy::panic)]

use crate::{
Ctrl, CurryInput, CurryStack, Deterministic, Graph, Input, Merge, RangeMap, Stack, State,
Expand Down
1 change: 1 addition & 0 deletions automata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
clippy::implicit_return,
clippy::inline_always,
clippy::let_underscore_untyped,
clippy::manual_assert,
clippy::min_ident_chars,
clippy::missing_trait_methods,
clippy::mod_module_files,
Expand Down
11 changes: 11 additions & 0 deletions examples/digit/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "digit"
version = "0.1.0"
edition = "2021"


[build-dependencies]
inator = { path = "../.." }

[dev-dependencies]
quickcheck = "1.0.3"
7 changes: 7 additions & 0 deletions examples/digit/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use std::io;

type I = u8;

fn main() -> Result<io::Result<()>, inator::IllFormed<I, (), usize>> {
inator::digit().to_file("src/parser.rs")
}
6 changes: 6 additions & 0 deletions examples/digit/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
mod parser;

#[cfg(test)]
mod test;

fn main() {}
84 changes: 84 additions & 0 deletions examples/digit/src/parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//! Automatically generated with [inator](https://crates.io/crates/inator).

#![allow(dead_code, unused_variables)]

/// Descriptive parsing error.
#[allow(dead_code)]
#[derive(Clone, Debug, PartialEq)]
pub enum Error {
/// Token without any relevant rule.
Absurd {
/// Index of the token that caused this error.
index: usize,
/// Particular token that didn't correspond to a rule.
token: u8,
},
/// Token that would have closed a delimiter, but the delimiter wasn't open.
Unopened {
/// Index of the token that caused this error.
index: usize,
/// Type of thing that wasn't opened (e.g. parentheses).
delimiter: (),
/// What actually was open (e.g. you tried to close parentheses, but a bracket was open).
instead: Option<()>,
},
/// After parsing all input, a delimiter remains open (e.g. "(a, b, c").
Unclosed {
/// Index at which the delimiter was opened (e.g., for parentheses, the index of the relevant '(').
opened: usize,
/// Type of thing that wasn't closed (e.g. parentheses).
delimiter: (),
},
/// Ended on a user-defined non-accepting state.
UserDefined {
/// User-defined error message.
messages: &'static [&'static str],
},
}

type R<I> = Result<(Option<(usize, (), Option<F<I>>)>, ()), Error>;

#[repr(transparent)]
struct F<I>(fn(&mut I, Option<()>, ()) -> R<I>);

#[inline]
pub fn parse<I: IntoIterator<Item = u8>>(input: I) -> Result<(), Error> {
match state_1(&mut input.into_iter().enumerate(), None, Default::default())? {
(None, out) => Ok(out),
(Some((index, context, None)), out) => panic!("Some(({index:?}, {context:?}, None))"),
(Some((index, delimiter, Some(F(_)))), _) => Err(Error::Unopened {
index,
delimiter,
instead: None,
}),
}
}

#[inline]
fn state_0<I: Iterator<Item = (usize, u8)>>(input: &mut I, context: Option<()>, acc: ()) -> R<I> {
match input.next() {
None => Ok((None, acc)),
Some((index, token)) => match (&context, &token) {
_ => Err(Error::Absurd { index, token }),
},
}
}

#[inline]
fn state_1<I: Iterator<Item = (usize, u8)>>(input: &mut I, context: Option<()>, acc: ()) -> R<I> {
match input.next() {
None => Err(Error::UserDefined {
messages: &[
"Expected only a single token on [b\'0\'..=b\'9\'] but got another token after it",
],
}),
Some((index, token)) => match (&context, &token) {
(&_, &(b'0'..=b'9')) => match state_0(input, context, (|(), i| i)(acc, token))? {
(None, _) => todo!(),
(done @ Some((_, _, None)), acc) => Ok((done, acc)),
(Some((idx, ctx, Some(F(f)))), out) => f(input, Some(ctx), out),
},
_ => Err(Error::Absurd { index, token }),
},
}
}
8 changes: 8 additions & 0 deletions examples/digit/src/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use core::iter;

quickcheck::quickcheck! {
fn correct(i: u8) -> bool {
let d = b'0' + (i % 10);
crate::parser::parse(iter::once(d)) == Ok(d)
}
}
23 changes: 19 additions & 4 deletions src/f.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,38 @@

//! Function representations.

#![allow(clippy::module_name_repetitions)]

use inator_automata::ToSrc;

/// One-argument function.
pub struct F1 {
#[non_exhaustive]
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct F {
/// Source-code representation of this function.
pub src: String,
/// Argument type.
pub arg_t: String,
/// Output type.
pub output_t: String,
}

/// Two-argument function.
pub struct F2 {
#[non_exhaustive]
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct FF {
/// Source-code representation of this function.
pub src: String,
/// Type of the first argument.
pub lhs_t: String,
/// Type of the second argument.
pub rhs_t: String,
/// Output type.
pub output_t: String,
}

impl F1 {
impl F {
/// Internals of the `f!(...)` macro.
#[inline]
#[must_use]
pub fn _from_macro<Arg: ToSrc, Output: ToSrc>(src: String, _: fn(Arg) -> Output) -> Self {
Expand All @@ -35,7 +49,8 @@ impl F1 {
}
}

impl F2 {
impl FF {
/// Internals of the `ff!(...)` macro.
#[inline]
#[must_use]
pub fn _from_macro<Lhs: ToSrc, Rhs: ToSrc, Output: ToSrc>(
Expand Down
4 changes: 2 additions & 2 deletions src/fixpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub struct Fixpoint<I: Input, S: Stack> {
impl<I: Input, S: Stack> ops::Shr<Deterministic<I, S>> for Fixpoint<I, S> {
type Output = Deterministic<I, S>;
#[inline]
#[allow(clippy::arithmetic_side_effects, clippy::manual_assert, clippy::panic)]
#[allow(clippy::arithmetic_side_effects, clippy::panic)]
fn shr(self, mut rhs: Deterministic<I, S>) -> Self::Output {
if let Some(lhs) = self.etc {
rhs = lhs >> rhs;
Expand All @@ -37,7 +37,7 @@ impl<I: Input, S: Stack> ops::Shr<Deterministic<I, S>> for Fixpoint<I, S> {
impl<I: Input, S: Stack> ops::Shr<Fixpoint<I, S>> for Deterministic<I, S> {
type Output = Fixpoint<I, S>;
#[inline]
#[allow(clippy::manual_assert, clippy::panic)]
#[allow(clippy::panic)]
fn shr(self, mut rhs: Fixpoint<I, S>) -> Self::Output {
assert!(
rhs.etc.is_none(),
Expand Down
42 changes: 35 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
clippy::implicit_return,
clippy::inline_always,
clippy::let_underscore_untyped,
clippy::manual_assert,
clippy::min_ident_chars,
clippy::missing_trait_methods,
clippy::mod_module_files,
Expand Down Expand Up @@ -155,19 +156,38 @@ macro_rules! get_mut {
}
*/

/// Unreachable state, but checked if we're debugging.
#[cfg(any(debug_assertions, test))]
macro_rules! never {
() => {
unreachable!()
};
}

/// Unreachable state, but checked if we're debugging.
#[cfg(not(any(debug_assertions, test)))]
macro_rules! never {
() => {{
#[allow(unsafe_code, unused_unsafe)]
unsafe {
core::hint::unreachable_unchecked()
}
}};
}

/// One-argument function.
#[macro_export]
macro_rules! f {
($ex:expr) => {
$crate::F1::_from_macro(stringify!($ex).to_owned(), $ex)
$crate::F::_from_macro(stringify!($ex).to_owned(), $ex)
};
}

/// Two-argument function.
#[macro_export]
macro_rules! ff {
($ex:expr) => {
$crate::F2::_from_macro(stringify!($ex).to_owned(), $ex)
$crate::FF::_from_macro(stringify!($ex).to_owned(), $ex)
};
}

Expand All @@ -182,10 +202,10 @@ mod recurse;
mod test;

pub use {
f::{F1, F2},
f::{F, FF},
fixpoint::{fixpoint, Fixpoint},
inator_automata::*,
num::integer,
num::{digit, integer},
recurse::{recurse, Recurse},
};

Expand Down Expand Up @@ -278,16 +298,20 @@ pub fn toss_range<I: Input, S: Stack>(range: Range<I>) -> Deterministic<I, S> {
}

/// Run this parser, then apply this function to the result.
/// # Panics
/// FIXME
#[inline]
#[must_use]
#[allow(clippy::needless_pass_by_value, clippy::todo)] // <-- TODO
#[allow(clippy::panic)]
pub fn process<I: Input, S: Stack, C: Ctrl<I, S>>(
parser: Graph<I, S, C>,
combinator: F1,
combinator: F,
) -> Graph<I, S, C> {
let Ok(parser_output_t) = parser.output_type() else {
panic!("Inconsistent types in the parser argument to `process`.")
};
if parser_output_t != Some(combinator.arg_t) {
if parser_output_t.as_ref() != Some(&combinator.arg_t) {
panic!(
"Called `process` with a function that wants an input of type `{}`, \
but the parser {}.",
Expand All @@ -300,11 +324,15 @@ pub fn process<I: Input, S: Stack, C: Ctrl<I, S>>(
}

/// Save the current value and put it aside, run this second parser from scratch, then combine the results.
/// # Panics
/// FIXME
#[inline]
#[must_use]
#[allow(clippy::needless_pass_by_value, clippy::todo)] // <-- TODO
#[allow(clippy::panic)]
pub fn combine<I: Input, S: Stack, C: Ctrl<I, S>>(
parser: Graph<I, S, C>,
combinator: F2,
_combinator: FF,
) -> Graph<I, S, C> {
let Ok(maybe_parser_input_t) = parser.input_type() else {
panic!("Inconsistent types in the parser argument to `combine`.")
Expand Down
8 changes: 4 additions & 4 deletions src/num.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use inator_automata::*;

/// Any digit character (0, 1, 2, 3, 4, 5, 6, 7, 8, 9).
#[inline]
#[must_use]
pub fn digit<S: Stack>() -> Deterministic<u8, S> {
any_of(
Range {
Expand All @@ -23,13 +24,12 @@ pub fn digit<S: Stack>() -> Deterministic<u8, S> {

/// An unsigned integer consisting only of digits (e.g., no sign, no decimal point, no commas, etc.).
#[inline]
#[must_use]
#[allow(clippy::arithmetic_side_effects)]
pub fn integer<S: Stack>() -> Deterministic<u8, S> {
let shape = process(digit(), f!(|i: u8| usize::from(i)))
>> fixpoint("integer")
>> combine(digit(), ff!(|a: usize, b: usize| a * 10 + b))
>> recurse("integer");
match shape.determinize() {
Ok(d) => d.sort(),
Err(_) => unreachable!(),
}
shape.determinize().unwrap_or_else(|_| never!())
}
Loading

0 comments on commit 647323b

Please sign in to comment.