diff --git a/peg_macro/src/lib.rs b/peg_macro/src/lib.rs index 7744c6f..f2500eb 100644 --- a/peg_macro/src/lib.rs +++ b/peg_macro/src/lib.rs @@ -19,8 +19,11 @@ struct Rule { enum Term { Choice(Vec), Literal(LitStr), + Optional(Box), Rule(Ident), Sequence(Vec), + Star(Box), + Plus(Box), } impl Parse for Grammar { @@ -53,23 +56,26 @@ impl Parse for Term { } } - fn parse_term(input: ParseStream) -> syn::Result { - let mut choices = vec![parse_atom(input)?]; - while input.peek(Token![/]) { - input.parse::()?; - choices.push(parse_atom(input)?); - } - if choices.len() == 1 { - Ok(choices.pop().unwrap()) - } else { - Ok(Term::Choice(choices)) + fn parse_repeat(input: ParseStream) -> syn::Result { + let mut result = parse_atom(input)?; + loop { + if input.parse::().is_ok() { + result = Term::Optional(Box::new(result)); + } else if input.parse::().is_ok() { + result = Term::Plus(Box::new(result)); + } else if input.parse::().is_ok() { + result = Term::Star(Box::new(result)); + } else { + break; + } } + Ok(result) } fn parse_sequence(input: ParseStream) -> syn::Result { - let mut terms = vec![parse_term(input)?]; - while !input.is_empty() && !input.peek(Token![;]) { - terms.push(parse_term(input)?); + let mut terms = vec![parse_repeat(input)?]; + while input.peek(Ident) || input.peek(LitStr) { + terms.push(parse_repeat(input)?); } if terms.len() == 1 { Ok(terms.pop().unwrap()) @@ -78,7 +84,20 @@ impl Parse for Term { } } - parse_sequence(input) + fn parse_choice(input: ParseStream) -> syn::Result { + let mut choices = vec![parse_sequence(input)?]; + while input.peek(Token![/]) { + input.parse::()?; + choices.push(parse_sequence(input)?); + } + if choices.len() == 1 { + Ok(choices.pop().unwrap()) + } else { + Ok(Term::Choice(choices)) + } + } + + parse_choice(input) } } @@ -94,61 +113,53 @@ impl Term { Term::Sequence(terms) => { let expr = terms .iter() - .map(|t| t.generate_wrapped_code()) + .map(|t| t.generate_code()) .reduce(|x, y| quote! { #x && #y }) .unwrap(); quote! { - let save = p.save(); - if #expr { - true - } else { - p.restore(save); - false + { + let save = p.save(); + if #expr { + true + } else { + p.restore(save); + false + } } } } Term::Choice(terms) => terms .iter() - .map(|t| t.generate_wrapped_code()) + .map(|t| t.generate_code()) .reduce(|x, y| quote! { #x || #y }) .unwrap(), - } - } - - fn generate_wrapped_code(&self) -> proc_macro2::TokenStream { - let code = self.generate_code(); - match self { - Term::Sequence(_) | Term::Choice(_) => quote! { - { #code } - }, - _ => code, - } - } -} - -#[proc_macro] -pub fn grammar_old(ts: TokenStream) -> TokenStream { - let token = ts.into_iter().next().expect("must have at least one token"); - let result = match token { - proc_macro::TokenTree::Literal(literal) => { - let s = literal.to_string(); - let c = s.chars().next().unwrap_or('\0'); - match c { - '0'..'9' => { - let val: usize = s.parse().unwrap(); - quote! { Some(crate::peg::Foo::Bar(#val)) } + Term::Optional(term) => { + let expr = term.generate_code(); + quote! { + (#expr || true) } - '"' => { - let inner = &s[1..s.len() - 1]; - quote! { Some(crate::peg::Foo::Baz(#inner.into())) } + } + Term::Star(term) => { + let expr = term.generate_code(); + quote! { + { while #expr {}; true } + } + } + Term::Plus(term) => { + let expr = term.generate_code(); + quote! { + { + let mut closure = || #expr; + if !closure() { + return false; + } + while closure() {} + true + } } - _ => quote! { None }, } } - _ => quote! { None }, - }; - - quote! { println!("Hello, world! {:?}", #result); }.into() + } } #[proc_macro] diff --git a/src/main.rs b/src/main.rs index 8e73b5b..8cf3487 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,15 +6,11 @@ mod world; use anyhow::anyhow; use labels::labels::print_labels; -use peg_macro::grammar_old; use preprocess::eval::Context; use std::{env, error::Error, fs, path::PathBuf, process::exit}; use world::World; fn main() -> Result<(), Box> { - grammar_old!(123); - grammar_old!("abc"); - let args: Vec = env::args().collect(); if args.len() != 2 { eprintln!("Usage: {} WORLD_FILE", args[0]); diff --git a/src/peg/peg.rs b/src/peg/peg.rs index 163c647..624f14a 100644 --- a/src/peg/peg.rs +++ b/src/peg/peg.rs @@ -1,10 +1,3 @@ -#[derive(Debug)] -#[allow(dead_code)] -pub enum Foo { - Bar(usize), - Baz(String), -} - pub struct ParseState { pub input: String, pub offset: usize, @@ -42,26 +35,6 @@ impl ParseState { } } -fn parse_foo(p: &mut ParseState) -> bool { - let save = p.save(); - if !parse_f(p) { - return false; - } - if !parse_oo(p) { - p.restore(save); - return false; - } - true -} - -fn parse_f(p: &mut ParseState) -> bool { - p.literal("f") -} - -fn parse_oo(p: &mut ParseState) -> bool { - p.literal("oo") -} - #[cfg(test)] mod tests { use peg_macro::grammar; @@ -72,14 +45,15 @@ mod tests { fake_csv = line "\n" line; line = item "," item; item = "foo" / "bar"; + + option = "(" "x"? ")"; + plus = "(" "x"+ ")"; + star = "(" "x"* ")"; } - #[test] - fn test_parser() { - let mut p = ParseState::new("foo"); - assert!(parse_foo(&mut p)); - let mut p = ParseState::new("fee"); - assert!(!parse_foo(&mut p)); + fn parse bool>(rule: T, s: &str) -> bool { + let mut p = ParseState::new(s); + rule(&mut p) } #[test] @@ -89,4 +63,22 @@ mod tests { let mut p = ParseState::new("foo,foo\nfoo;foo"); assert!(!fake_csv(&mut p)); } + + #[test] + fn test_repetition_suffixes() { + let zero = "()"; + assert!(parse(option, zero)); + assert!(parse(star, zero)); + assert!(!parse(plus, zero)); + + let one = "(x)"; + assert!(parse(option, one)); + assert!(parse(star, one)); + assert!(parse(plus, one)); + + let many = "(xxx)"; + assert!(!parse(option, many)); + assert!(parse(star, many)); + assert!(parse(plus, many)); + } }