Skip to content

Commit

Permalink
Has to be a better way other than splitting into a tuple
Browse files Browse the repository at this point in the history
  • Loading branch information
wrsturgeon committed Nov 5, 2023
1 parent 9d466e5 commit 1fb9256
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 73 deletions.
9 changes: 9 additions & 0 deletions automata/src/curry_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,15 @@ impl<I: Input, S: Stack, C: Ctrl<I, S>> CurryInput<I, S, C> {
}
}

/// All values in this collection, without their associated keys.
#[inline]
pub fn values_mut(&mut self) -> Box<dyn '_ + Iterator<Item = &mut Transition<I, S, C>>> {
match *self {
Self::Wildcard(ref mut etc) => Box::new(iter::once(etc)),
Self::Scrutinize(ref mut etc) => Box::new(etc.values_mut()),
}
}

/// Remove an entry by key.
/// # Panics
/// If we ask to remove a wildcard but it's a specific value, or vice-versa.
Expand Down
9 changes: 9 additions & 0 deletions automata/src/curry_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ impl<I: Input, S: Stack, C: Ctrl<I, S>> CurryStack<I, S, C> {
.chain(self.map_none.iter())
.chain(self.map_some.values())
}

/// All values in this collection, without their associated keys.
#[inline]
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut CurryInput<I, S, C>> {
self.wildcard
.iter_mut()
.chain(self.map_none.iter_mut())
.chain(self.map_some.values_mut())
}
}

impl<I: Input, S: Stack> CurryStack<I, S, usize> {
Expand Down
2 changes: 1 addition & 1 deletion automata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
#[macro_export]
macro_rules! update {
($ex:expr) => {
$crate::Update::_update_macro(stringify!($ex), $ex)
$crate::Update::_update_macro(stringify!($ex).to_owned(), $ex)
};
}

Expand Down
6 changes: 6 additions & 0 deletions automata/src/range_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ impl<I: Input, S: Stack, C: Ctrl<I, S>> RangeMap<I, S, C> {
self.entries.values()
}

/// All values in this collection, without their associated keys.
#[inline]
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Transition<I, S, C>> {
self.entries.values_mut()
}

/// Remove an entry by key.
#[inline]
pub fn remove(&mut self, key: &Range<I>) {
Expand Down
2 changes: 2 additions & 0 deletions automata/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ mod prop {
}
}

// TODO: test that running sort twice does not decrease the number of states the second time.

fn shr(lhs: Deterministic<u8, u8>, rhs: Deterministic<u8, u8>, input: Vec<u8>) -> bool {
let splittable = (0..=input.len()).any(|i| {
lhs.accept(input[..i].iter().copied()).is_ok() &&
Expand Down
2 changes: 1 addition & 1 deletion automata/src/to_src.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ impl<I: Input, S: Stack> Transition<I, S, usize> {
#[allow(clippy::todo)] // TODO: what the fuck does the last case mean?
fn to_src(&self, stack_symbol: Option<Option<&str>>) -> String {
let dst = self.dst;
let f = self.update.src;
let f = self.update.src.as_str();
match self.act {
Action::Local => format!(
r#"match state_{dst}(input, context, ({f})(acc, token))? {{
Expand Down
8 changes: 4 additions & 4 deletions automata/src/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ pub struct Update<I: Input> {
/// Representation of the type of tokens.
pub ghost: PhantomData<I>,
/// Source-code representation that's promised to compile to a call operationally identical to `ptr`.
pub src: &'static str,
pub src: String,
}

impl<I: Input> Update<I> {
/// Internals of the `update!` macro.
#[inline]
#[must_use]
pub fn _update_macro<T: ToSrc, U: ToSrc>(src: &'static str, _: fn(T, I) -> U) -> Self {
pub fn _update_macro<T: ToSrc, U: ToSrc>(src: String, _: fn(T, I) -> U) -> Self {
Self {
input_t: T::src_type(),
output_t: U::src_type(),
Expand Down Expand Up @@ -55,7 +55,7 @@ impl<I: Input> PartialOrd for Update<I> {
impl<I: Input> Ord for Update<I> {
#[inline]
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.src.cmp(other.src)
self.src.cmp(&other.src)
}
}

Expand All @@ -73,7 +73,7 @@ impl<I: Input> Clone for Update<I> {
input_t: self.input_t.clone(),
output_t: self.output_t.clone(),
ghost: self.ghost,
src: self.src,
src: self.src.clone(),
}
}
}
4 changes: 2 additions & 2 deletions examples/digit/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ fn state_0<I: Iterator<Item = (usize, u8)>>(input: &mut I, context: Option<()>,
}

#[inline]
fn state_1<I: Iterator<Item = (usize, u8)>>(input: &mut I, context: Option<()>, acc: u16) -> R<I> {
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 a token in the range [b\'0\'..=b\'9\'] but input ended"],
}),
Some((index, token)) => match (&context, &token) {
(&_, &(b'0'..=b'9')) => {
match state_0(input, context, (|_: u16, i| i - b'0')(acc, token))? {
match state_0(input, context, (|(), i| i - b'0')(acc, token))? {
(done @ (None | Some((_, _, None))), acc) => Ok((done, acc)),
(Some((idx, ctx, Some(F(f)))), out) => f(input, Some(ctx), out),
}
Expand Down
91 changes: 91 additions & 0 deletions src/call.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

//! Save the current value, run this second parser from scratch, then combine the results.

use crate::{F, FF};
use core::{iter, ops};
use inator_automata::*;
use std::collections::{BTreeMap, BTreeSet};

/// Save the current value, run this second parser from scratch, then combine the results.
pub struct Call<I: Input, S: Stack> {
/// Parser to call.
parser: Deterministic<I, S>,
/// Combine the tabled result with the result of the call.
combinator: FF,
}

/// Save the current value, run this second parser from scratch, then combine the results.
#[inline]
#[must_use]
pub fn call<I: Input, S: Stack>(parser: Deterministic<I, S>, combinator: FF) -> Call<I, S> {
Call { parser, combinator }
}

impl<I: Input, S: Stack> ops::Shr<Call<I, S>> for Deterministic<I, S> {
type Output = Self;
#[inline]
#[must_use]
#[allow(clippy::panic)]
fn shr(self, Call { parser, combinator }: Call<I, S>) -> Self::Output {
let Ok(maybe_parser_input_t) = parser.input_type() else {
panic!("Inconsistent types in the parser argument to `combine`.")
};
let Some(parser_output_t) = maybe_parser_input_t else {
panic!(
"Parser argument to `combine` has no initial states, \
so it can never parse anything.",
)
};
if parser_output_t != "()" {
panic!(
"Called `call` with a parser that doesn't start from scratch \
(it wants an input of type `{parser_output_t}`, \
but it should start from scratch with an input of type `()`)."
);
}

// From `automata/src/combinators.rs` (in the original `>>` implementation):
let s = self.generalize();
let size = s.states.len();
let Graph {
states: call_states,
initial: call_initial,
tags: call_tags,
} = parser
.generalize()
.map_indices(|i| i.checked_add(size).expect("Absurdly huge number of states"));
assert_eq!(call_initial.len(), 1);

let call_init = get!(call_states, unwrap!(unwrap!(call_initial.first())));
let init_trans = call_init.transitions.merge();
for state in s.states.iter_mut().filter(|&s| {
if s.non_accepting.is_empty() {
s.non_accepting = iter::once(todo!()).collect();
true
} else {
false
}
}) {
state.transitions = state
.transitions
.merge(call_init.transitions)
.unwrap_or_else(|e| panic!("{e}"));
}

s.states.extend(call_states);
s.tags.extend(call_tags);

// Split the accumulator into a passthrough unmodified first argument and a new modifiable second argument.
let split = F {
src: "|x| (x, ())".to_owned(),
arg_t: parser_output_t.clone(),
output_t: format!("({parser_output_t}, ())"),
};
let splat /* past tense */ = parser >> split;
}
}
60 changes: 59 additions & 1 deletion src/f.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

#![allow(clippy::module_name_repetitions)]

use inator_automata::ToSrc;
use core::{convert::identity as id, mem, ops};
use inator_automata::*;
use std::collections::BTreeSet;

/// One-argument function.
#[non_exhaustive]
Expand Down Expand Up @@ -65,3 +67,59 @@ impl FF {
}
}
}

impl<I: Input, S: Stack, C: Ctrl<I, S>> ops::Shr<F> for Graph<I, S, C> {
type Output = Self;
#[inline]
#[must_use]
#[allow(clippy::panic, clippy::todo)]
fn shr(mut self, rhs: F) -> Self::Output {
let Ok(out_t) = self.output_type() else {
panic!("Type inconsistency in the parser argument to `process`.")
};
if out_t.as_deref() != Some(&rhs.arg_t) {
panic!(
"Called `process` with a function that wants an input of type `{}`, \
but the parser {}.",
rhs.arg_t,
out_t.map_or_else(|| "never returns".to_owned(), |t| format!("returns `{t}`"))
);
}
let accepting_indices: BTreeSet<usize> = self
.states
.iter()
.enumerate()
.filter(|&(_, s)| s.non_accepting.is_empty())
.map(|(i, _)| i)
.collect();
for state in &mut self.states {
for curry in state.transitions.values_mut() {
for transition in curry.values_mut() {
let to_accepting = transition
.dst
.view()
.map(|r| r.map_or_else(|tag| *unwrap!(self.tags.get(tag)), id))
.any(|i| accepting_indices.contains(&i));
if !to_accepting {
continue;
}
{
let old_out_t =
mem::replace(&mut transition.update.output_t, rhs.output_t.clone());
assert_eq!(
old_out_t, rhs.arg_t,
"Tried to apply a function to the output of a parser, but \
at least one path in the parser produced a mismatched type: \
the post-processing function wanted an input of type {}, but \
a path to an accepting state produced a value of type {old_out_t}",
rhs.arg_t,
);
}
let src = mem::take(&mut transition.update.src);
transition.update.src = format!("|tok, inp| ({})({src}(tok, inp))", rhs.src);
}
}
}
self
}
}
61 changes: 3 additions & 58 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@
clippy::wildcard_imports
)]

/*
/// Unwrap if we're debugging but `unwrap_unchecked` if we're not.
#[cfg(any(debug_assertions, test))]
macro_rules! unwrap {
Expand Down Expand Up @@ -137,6 +136,7 @@ macro_rules! get {
}};
}

/*
/// Unwrap if we're debugging but `unwrap_unchecked` if we're not.
#[cfg(any(debug_assertions, test))]
macro_rules! get_mut {
Expand All @@ -154,7 +154,6 @@ macro_rules! get_mut {
result
}};
}
*/
/// Unreachable state, but checked if we're debugging.
#[cfg(any(debug_assertions, test))]
Expand All @@ -174,6 +173,7 @@ macro_rules! never {
}
}};
}
*/

/// One-argument function.
#[macro_export]
Expand All @@ -195,6 +195,7 @@ macro_rules! ff {

// TODO: Macro that isn't context-aware but just dumps the codegen right there

mod call;
mod f;
mod fixpoint;
mod num;
Expand Down Expand Up @@ -298,59 +299,3 @@ pub fn toss<I: Input, S: Stack>(token: I) -> Deterministic<I, S> {
pub fn toss_range<I: Input, S: Stack>(range: Range<I>) -> Deterministic<I, S> {
any_of(range, update!(|(), _| {}))
}

/// 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: 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.as_deref() != Some(&combinator.arg_t) {
panic!(
"Called `process` with a function that wants an input of type `{}`, \
but the parser {}.",
combinator.arg_t,
parser_output_t
.map_or_else(|| "never returns".to_owned(), |t| format!("returns `{t}`"))
);
}
todo!()
}

/// 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: FF,
) -> Graph<I, S, C> {
let Ok(maybe_parser_input_t) = parser.input_type() else {
panic!("Inconsistent types in the parser argument to `combine`.")
};
let Some(parser_output_t) = maybe_parser_input_t else {
panic!(
"Parser argument to `combine` has no initial states, \
so it can never parse anything.",
)
};
if parser_output_t != "()" {
panic!(
"Called `combine` with a parser that doesn't start from scratch \
(it asks for an input of type `{parser_output_t}` instead of `()`)."
);
}
// TODO: We might have to define a `Combine` struct to handle the `>>` operator
todo!()
}
Loading

0 comments on commit 1fb9256

Please sign in to comment.