#![allow(dead_code)]
#[derive(Clone, Copy, Debug)]
pub struct State<'a> {
pub code: &'a str,
pub index: usize,
}
impl<'a> State<'a> {
fn rest(&self) -> Option<&'a str> {
self.code.get(self.index..)
}
}
pub type Answer<'a, A> = Result<(State<'a>, A), String>;
pub type Parser<'a, A> = Box<dyn Fn(State<'a>) -> Answer<'a, A>>;
pub fn find(text: &str, target: &str) -> usize {
text.find(target).unwrap_or_else(|| panic!("`{}` not in `{}`.", target, text))
}
pub fn read<'a, A>(parser: Parser<'a, A>, code: &'a str) -> Result<A, String> {
match parser(State { code, index: 0 }) {
Ok((_, value)) => Ok(value),
Err(msg) => Err(msg),
}
}
pub fn head(state: State) -> Option<char> {
state.rest()?.chars().next()
}
pub fn tail(state: State) -> State {
let add = match head(state) {
Some(c) => c.len_utf8(),
None => 0,
};
State { code: state.code, index: state.index + add }
}
pub fn here_take_head(state: State) -> Answer<char> {
if let Some(got) = head(state) {
let state = State { code: state.code, index: state.index + got.len_utf8() };
Ok((state, got))
} else {
Ok((state, '\0'))
}
}
pub fn do_here_take_head<'a>() -> Parser<'a, char> {
Box::new(here_take_head)
}
pub fn there_take_head(state: State) -> Answer<char> {
let (state, _) = skip(state)?;
here_take_head(state)
}
pub fn do_there_take_head<'a>() -> Parser<'a, char> {
Box::new(there_take_head)
}
pub fn here_peek_head(state: State) -> Answer<char> {
if let Some(got) = head(state) {
return Ok((state, got));
} else {
return Ok((state, '\0'));
}
}
pub fn do_here_peek_head<'a>() -> Parser<'a, char> {
Box::new(here_peek_head)
}
pub fn there_peek_head(state: State) -> Answer<char> {
let (state, _) = skip(state)?;
here_peek_head(state)
}
pub fn do_there_peek_head<'a>() -> Parser<'a, char> {
Box::new(there_peek_head)
}
pub fn skip_while(mut state: State, cond: Box<dyn Fn(&char) -> bool>) -> Answer<bool> {
if let Some(rest) = state.rest() {
let add: usize = rest.chars().take_while(cond).map(|a| a.len_utf8()).sum();
state.index += add;
if add > 0 {
return Ok((state, true));
}
}
Ok((state, false))
}
pub fn skip_comment(mut state: State) -> Answer<bool> {
const COMMENT: &str = "//";
if let Some(rest) = state.rest() {
if let Some(line) = rest.lines().next() {
if line.starts_with(COMMENT) {
state.index += line.len();
return Ok((state, true));
}
}
}
Ok((state, false))
}
pub fn do_skip_comment<'a>() -> Parser<'a, bool> {
Box::new(skip_comment)
}
pub fn skip_spaces(mut state: State) -> Answer<bool> {
if let Some(rest) = state.rest() {
let add: usize = rest.chars().take_while(|a| a.is_whitespace()).map(|a| a.len_utf8()).sum();
state.index += add;
if add > 0 {
return Ok((state, true));
}
}
Ok((state, false))
}
pub fn do_skip_spaces<'a>() -> Parser<'a, bool> {
Box::new(skip_spaces)
}
pub fn skip(mut state: State) -> Answer<bool> {
let (new_state, mut comment) = skip_comment(state)?;
state = new_state;
let (new_state, mut spaces) = skip_spaces(state)?;
state = new_state;
if comment || spaces {
loop {
let (new_state, new_comment) = skip_comment(state)?;
state = new_state;
comment = new_comment;
let (new_state, new_spaces) = skip_spaces(state)?;
state = new_state;
spaces = new_spaces;
if !comment && !spaces {
return Ok((state, true));
}
}
}
Ok((state, false))
}
pub fn do_skip<'a>() -> Parser<'a, bool> {
Box::new(skip)
}
pub fn here_take_exact<'a>(pat: &str, state: State<'a>) -> Answer<'a, bool> {
if let Some(rest) = state.rest() {
if rest.starts_with(pat) {
let state = State { code: state.code, index: state.index + pat.len() };
return Ok((state, true));
}
}
Ok((state, false))
}
pub fn do_here_take_exact<'a>(pat: &'static str) -> Parser<'a, bool> {
Box::new(move |x| here_take_exact(pat, x))
}
pub fn there_take_exact<'a>(pat: &str, state: State<'a>) -> Answer<'a, bool> {
let (state, _) = skip(state)?;
let (state, matched) = here_take_exact(pat, state)?;
Ok((state, matched))
}
pub fn do_there_take_exact<'a>(pat: &'static str) -> Parser<'a, bool> {
Box::new(move |x| there_take_exact(pat, x))
}
pub fn here_peek_exact<'a>(pat: &str, state: State<'a>) -> Answer<'a, bool> {
if let Some(rest) = state.rest() {
if rest.starts_with(pat) {
return Ok((state, true));
}
}
Ok((state, false))
}
pub fn do_here_peek_exact<'a>(pat: &'static str) -> Parser<'a, bool> {
Box::new(move |x| here_peek_exact(pat, x))
}
pub fn force_there_take_exact<'a>(pat: &str, state: State<'a>) -> Answer<'a, ()> {
let (state, matched) = there_take_exact(pat, state)?;
if matched {
Ok((state, ()))
} else {
expected(pat, pat.len(), state)
}
}
pub fn there_peek_exact<'a>(pat: &str, state: State<'a>) -> Answer<'a, bool> {
let (state, _) = skip(state)?;
let (state, matched) = here_peek_exact(pat, state)?;
Ok((state, matched))
}
pub fn do_there_peek_exact<'a>(pat: &'static str) -> Parser<'a, bool> {
Box::new(move |x| there_peek_exact(pat, x))
}
pub fn do_force_there_take_exact<'a>(pat: &'static str) -> Parser<'a, ()> {
Box::new(move |x| force_there_take_exact(pat, x))
}
pub fn here_end(state: State) -> Answer<bool> {
Ok((state, state.index == state.code.len()))
}
pub fn do_here_end<'a>() -> Parser<'a, bool> {
Box::new(there_end)
}
pub fn there_end(state: State) -> Answer<bool> {
let (state, _) = skip(state)?;
Ok((state, state.index == state.code.len()))
}
pub fn do_there_end<'a>() -> Parser<'a, bool> {
Box::new(there_end)
}
pub fn guard<'a, A: 'a>(
head: Parser<'a, bool>,
body: Parser<'a, A>,
state: State<'a>,
) -> Answer<'a, Option<A>> {
let (state, _) = skip(state)?;
let (_, matched) = head(state)?;
if matched {
let (state, got) = body(state)?;
Ok((state, Some(got)))
} else {
Ok((state, None))
}
}
pub fn nth<'a>(parsers: &[Parser<'a, bool>], state: State<'a>) -> Answer<'a, Option<usize>> {
for (idx, parser) in parsers.iter().enumerate() {
let (state, matched) = parser(state)?;
if matched {
return Ok((state, Some(idx)));
}
}
Ok((state, None))
}
pub fn any<'a>(parsers: &[Parser<'a, bool>], state: State<'a>) -> Answer<'a, bool> {
let (state, idx) = nth(parsers, state)?;
return Ok((state, idx.is_some()));
}
pub fn attempt<'a, A: 'a>(
name: &'static str,
choices: &[Parser<'a, Option<A>>],
state: State<'a>,
) -> Answer<'a, A> {
for choice in choices {
let (state, result) = choice(state)?;
if let Some(value) = result {
return Ok((state, value));
}
}
expected(name, 1, state)
}
pub fn maybe<'a, A: 'a>(parser: Parser<'a, A>, state: State<'a>) -> Answer<'a, Option<A>> {
let result = parser(state);
match result {
Ok((state, result)) => Ok((state, Some(result))),
Err(_) => Ok((state, None)),
}
}
pub fn dry<'a, A: 'a>(parser: Parser<'a, A>, state: State<'a>) -> Answer<'a, A> {
let (_, result) = parser(state)?;
Ok((state, result))
}
pub fn until<'a, A: 'a>(
delim: Parser<'a, bool>,
parser: Parser<'a, A>,
state: State<'a>,
) -> Answer<'a, Vec<A>> {
let mut state = state;
let mut result = Vec::new();
loop {
let (new_state, delimited) = delim(state)?;
if delimited {
state = new_state;
break;
} else {
let (new_state, a) = parser(new_state)?;
state = new_state;
result.push(a);
}
}
Ok((state, result))
}
pub fn list<'a, A: 'a, B: 'a>(
parse_open: Parser<'a, bool>,
parse_sep: Parser<'a, bool>,
parse_close: Parser<'a, bool>,
parse_elem: Parser<'a, A>,
make: Box<dyn Fn(Vec<A>) -> B>,
state: State<'a>,
) -> Answer<'a, B> {
let (state, _) = parse_open(state)?;
let mut state = state;
let mut elems = Vec::new();
loop {
let (new_state, done) = parse_close(state)?;
let (new_state, _) = parse_sep(new_state)?;
if done {
state = new_state;
break;
} else {
let (new_state, elem) = parse_elem(new_state)?;
state = new_state;
elems.push(elem);
}
}
Ok((state, make(elems)))
}
fn is_letter(chr: char) -> bool {
chr.is_ascii_alphanumeric() || chr == '_' || chr == '.' || chr == '$'
}
pub fn here_name(state: State) -> Answer<String> {
let mut name: String = String::new();
let mut state = state;
while let Some(got) = head(state) {
if is_letter(got) {
name.push(got);
state = tail(state);
} else {
break;
}
}
Ok((state, name))
}
pub fn there_name(state: State) -> Answer<String> {
let (state, _) = skip(state)?;
here_name(state)
}
pub fn there_nonempty_name(state: State) -> Answer<String> {
let (state, name1) = there_name(state)?;
if !name1.is_empty() {
Ok((state, name1))
} else {
expected("name", 1, state)
}
}
pub fn expected<'a, A>(name: &str, size: usize, state: State<'a>) -> Answer<'a, A> {
Err(format!("Expected `{}`:\n{}", name, &highlight_error::highlight_error(state.index, state.index + size, state.code)))
}
pub enum Testree {
Node { lft: Box<Testree>, rgt: Box<Testree> },
Leaf { val: String },
}
pub fn testree_show(tt: &Testree) -> String {
match tt {
Testree::Node { lft, rgt } => format!("({} {})", testree_show(lft), testree_show(rgt)),
Testree::Leaf { val } => val.to_string(),
}
}
pub fn node_parser<'a>() -> Parser<'a, Option<Box<Testree>>> {
Box::new(|state| {
guard(
do_there_take_exact("("),
Box::new(|state| {
let (state, _) = force_there_take_exact("(", state)?;
let (state, lft) = testree_parser()(state)?;
let (state, rgt) = testree_parser()(state)?;
let (state, _) = force_there_take_exact(")", state)?;
Ok((state, Box::new(Testree::Node { lft, rgt })))
}),
state,
)
})
}
pub fn leaf_parser<'a>() -> Parser<'a, Option<Box<Testree>>> {
Box::new(|state| {
guard(
do_there_take_exact(""),
Box::new(|state| {
let (state, val) = there_name(state)?;
Ok((state, Box::new(Testree::Leaf { val })))
}),
state,
)
})
}
pub fn testree_parser<'a>() -> Parser<'a, Box<Testree>> {
Box::new(|state| {
let (state, tree) = attempt("Testree", &[node_parser(), leaf_parser()], state)?;
Ok((state, tree))
})
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
const RE_ANY: &str = "(?s).*";
#[derive(Debug)]
struct MockState {
code: String,
index: usize,
}
prop_compose! {
fn state_tail()(
any in RE_ANY, ch in any::<char>()
) -> MockState {
let code = format!("{}{}", ch, any);
let index = ch.len_utf8();
MockState { code, index }
}
}
proptest! {
#[test]
fn test_tail(state in state_tail()) {
let state_after = tail(State {
code: &state.code, index: 0
});
prop_assert_eq!(state.index, state_after.index);
prop_assert!(
state_after.index <= state.code.len(),
"\ncode length: {}\nindex: {}\n",
state.code.len(),
state_after.index
);
}
}
const COMMENT: &str = "//";
const RE_LINE: &str = "[^\r\n]*";
prop_compose! {
fn state_skip_comment()(
line in RE_LINE.prop_filter(
"Values must not start with `COMMENT`.", |a| !a.starts_with(COMMENT)),
will_comment in any::<bool>(),
any in RE_ANY,
) -> (MockState, bool) {
let index = if will_comment {
COMMENT.len() + line.len()
} else {
0
};
let code = if will_comment {
format!("{}{}\n{}", COMMENT, line, any)
} else {
format!("{}\n{}", line, any)
};
(MockState { code, index }, will_comment)
}
}
proptest! {
#[test]
fn test_skip_comment(state in state_skip_comment()) {
let answer = skip_comment(State {
code: &state.0.code, index: 0
}).unwrap();
let state_after = answer.0;
prop_assert_eq!(state.0.index, state_after.index);
prop_assert_eq!(state.1, answer.1);
prop_assert!(
state_after.index <= state.0.code.len(),
"\ncode length: {}\nindex: {}\n",
state.0.code.len(),
state_after.index
);
}
}
const RE_WHITESPACE: &str = "\\s+";
prop_compose! {
fn state_skip_spaces()(
any in RE_ANY.prop_filter(
"Values must not start with whitespace.", |a| a == a.trim_start()),
has_spaces in any::<bool>(),
spaces in RE_WHITESPACE,
) -> (MockState, bool) {
let index = if has_spaces {
spaces.len()
} else {
0
};
let code = if has_spaces {
format!("{}{}", spaces, any)
} else {
any
};
(MockState { code, index }, has_spaces)
}
}
proptest! {
#[test]
fn test_skip_spaces(state in state_skip_spaces()) {
let answer = skip_spaces(State {
code: &state.0.code, index: 0
}).unwrap();
let state_after = answer.0;
prop_assert_eq!(state.0.index, state_after.index);
prop_assert_eq!(state.1, answer.1);
prop_assert!(
state_after.index <= state.0.code.len(),
"\ncode length: {}\nindex: {}\n",
state.0.code.len(),
state_after.index
);
}
}
prop_compose! {
fn state_skip()(
will_skip in any::<bool>(),
any in RE_ANY.prop_filter(
"Values must not start with whitespace or be a comment.", |a| {
let a_trimmed = a.trim_start();
!a_trimmed.starts_with(COMMENT) && a == a_trimmed
}),
)(
spaces_comments in if will_skip {
prop::collection::vec((RE_WHITESPACE, RE_LINE), 0..10)
} else {
prop::collection::vec(("", ""), 0)
},
will_skip in Just(will_skip),
any in Just(any),
) -> (MockState, bool) {
let mut code: String = if will_skip {
spaces_comments
.iter()
.flat_map(|(space, comment)| [space.as_str(), COMMENT, comment.as_str(), "\n"])
.collect()
} else {
String::with_capacity(any.len())
};
let index = code.len();
code.push_str(&any);
let will_skip = code.trim_start() != code || code.starts_with(COMMENT);
(MockState { code, index }, will_skip)
}
}
proptest! {
#[test]
fn test_skip(state in state_skip()) {
let answer = skip(State {
code: &state.0.code, index: 0
}).unwrap();
let state_after = answer.0;
prop_assert_eq!(state.0.index, state_after.index);
prop_assert_eq!(state.1, answer.1);
prop_assert!(
state_after.index <= state.0.code.len(),
"\ncode length: {}\nindex: {}\n",
state.0.code.len(),
state_after.index
);
}
}
prop_compose! {
fn range(from: usize, to: usize)(from in from..to)(
to in from..to,
from in Just(from)
) -> (usize, usize) {
(from, to)
}
}
const RE_NON_EMPTY: &str = ".{1,}";
prop_compose! {
fn args_highlight()(code in RE_NON_EMPTY)(
code in Just(code.clone()),
(from, to) in range(0, code.len()).prop_filter(
"Values must be `char` boundaries.", move |(from, to)| {
code.is_char_boundary(*from) && code.is_char_boundary(*to)
})
) -> (usize, usize, String) {
(from, to, code)
}
}
}