use std::ops::RangeInclusive; use kw::{ANY, EOI}; use proc_macro::TokenStream; use proc_macro2::Span; use quote::quote; use syn::{ Ident, LitChar, LitStr, Token, parenthesized, parse::{Parse, ParseStream}, parse_macro_input, punctuated::Punctuated, }; struct Grammar { rules: Punctuated, } struct Rule { name: Ident, definition: Term, } #[derive(Debug)] enum Term { AnyChar, Capture(String, Box), Choice(Vec), EOI, Literal(String, bool), NegLookahead(Box), Optional(Box), Plus(Box), PosLookahead(Box), Range(RangeInclusive, bool), Rule(Ident), Sequence(Vec), Star(Box), } mod kw { syn::custom_keyword!(ANY); syn::custom_keyword!(EOI); syn::custom_keyword!(icase); } impl Parse for Grammar { fn parse(input: ParseStream) -> syn::Result { Ok(Grammar { rules: Punctuated::parse_terminated(input)?, }) } } impl Parse for Rule { fn parse(input: ParseStream) -> syn::Result { let mut icase = false; if input.parse::().is_ok() { let look = input.lookahead1(); if look.peek(kw::icase) { input.parse::()?; icase = true; } else { return Err(look.error()); } } let name = input.parse()?; input.parse::()?; let mut definition: Term = input.parse()?; if icase { definition.set_icase(); } Ok(Self { name, definition }) } } impl Parse for Term { fn parse(input: ParseStream) -> syn::Result { fn parse_range(input: ParseStream) -> syn::Result { let start_lit = input.parse::()?; input.parse::()?; let end_lit = input.parse::()?; let range = start_lit.value()..=end_lit.value(); let icase = end_lit.suffix() == "i"; Ok(Term::Range(range, icase)) } fn parse_atom(input: ParseStream) -> syn::Result { let look = input.lookahead1(); if look.peek(Ident) { if input.parse::().is_ok() { Ok(Term::AnyChar) } else if input.parse::().is_ok() { Ok(Term::EOI) } else { input.parse().map(Term::Rule) } } else if look.peek(LitStr) { let lit = input.parse::()?; let icase = lit.suffix() == "i"; Ok(Term::Literal(lit.value(), icase)) } else if look.peek(LitChar) { parse_range(input) } else if look.peek(syn::token::Paren) { let content; parenthesized!(content in input); parse_choice(&content) } else { Err(look.error()) } } 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_prefix(input: ParseStream) -> syn::Result { if input.parse::().is_ok() { parse_repeat(input).map(|x| Term::NegLookahead(x.into())) } else if input.parse::().is_ok() { parse_repeat(input).map(|x| Term::PosLookahead(x.into())) } else if input.parse::().is_ok() { let tag: Ident = input.parse()?; input.parse::()?; let expr = parse_repeat(input)?; Ok(Term::Capture(tag.to_string(), expr.into())) } else { parse_repeat(input) } } fn parse_sequence(input: ParseStream) -> syn::Result { let mut terms = vec![parse_prefix(input)?]; while !input.is_empty() && !input.peek(Token![/]) && !input.peek(Token![;]) { terms.push(parse_prefix(input)?); } if terms.len() == 1 { Ok(terms.pop().unwrap()) } else { Ok(Term::Sequence(terms)) } } 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) } } impl Term { fn generate_code(&self) -> proc_macro2::TokenStream { match self { Term::AnyChar => quote! { p.any() }, Term::Capture(name, pat) => { let tag = Ident::new(&name, Span::call_site()); let code = pat.generate_code(); quote! { { let save = p.begin_capture(Tag::#tag); if !#code { p.restore(save); false } else { p.commit_capture(save); true } } } } Term::EOI => quote! { p.eoi() }, Term::Rule(ident) => quote! { #ident(p) }, Term::Literal(lit_str, icase) => { let method = if *icase { quote! { literal_i } } else { quote! { literal } }; quote! { p.#method(#lit_str) } } Term::Sequence(terms) => { let expr = terms .iter() .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 } } } } Term::Choice(terms) => { let code = terms .iter() .map(|t| t.generate_code()) .reduce(|x, y| quote! { #x || #y }) .unwrap(); quote! { ( #code ) } } Term::Optional(term) => { let expr = term.generate_code(); quote! { ( #expr || true ) } } 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() { while closure() {} true } else { false } } } } Term::Range(range, icase) => { let (lo, hi) = (range.start(), range.end()); let method = if *icase { quote! { range_i } } else { quote! { range } }; quote! { p.#method(#lo..=#hi) } } Term::NegLookahead(term) => { let code = term.generate_code(); quote! { { let save = p.save(); if #code { p.restore(save); false } else { true } } } } Term::PosLookahead(term) => { let code = term.generate_code(); quote! { { let save = p.save(); if #code { p.restore(save); true } else { false } } } } } } fn set_icase(&mut self) { match self { Term::Literal(_, icase) | Term::Range(_, icase) => { *icase = true; } Term::Choice(terms) | Term::Sequence(terms) => { terms.iter_mut().for_each(|x| x.set_icase()); } Term::Capture(_, term) | Term::NegLookahead(term) | Term::Optional(term) | Term::Plus(term) | Term::PosLookahead(term) | Term::Star(term) => { term.set_icase(); } Term::AnyChar | Term::EOI | Term::Rule(_) => {} } } fn get_capture_names(&self) -> Vec<&str> { let mut result = vec![]; match self { Term::AnyChar | Term::EOI | Term::Literal(_, _) | Term::Range(_, _) | Term::Rule(_) => { } Term::Capture(name, term) => { result.push(name.as_str()); result.extend(term.get_capture_names()); } Term::Choice(terms) | Term::Sequence(terms) => { terms .iter() .for_each(|x| result.extend(x.get_capture_names())); } Term::NegLookahead(term) | Term::Optional(term) | Term::Plus(term) | Term::PosLookahead(term) | Term::Star(term) => { result.extend(term.get_capture_names()); } } result } } #[proc_macro] pub fn grammar(ts: TokenStream) -> TokenStream { let input = parse_macro_input!(ts as Grammar); let mut capture_names: Vec<_> = input .rules .iter() .flat_map(|r| r.definition.get_capture_names()) .collect(); capture_names.sort(); capture_names.dedup(); let tag_idents: Vec = capture_names .iter() .map(|x| Ident::new(x, proc_macro2::Span::call_site())) .collect(); let enum_tag = quote! { #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Tag { #(#tag_idents),* } }; let fns: Vec<_> = input .rules .iter() .map(|r| { let fn_name = &r.name; let generated = r.definition.generate_code(); quote! { pub fn #fn_name(p: &mut crate::peg::ParseState) -> bool { use crate::peg::backend::LowLevel; #generated } } }) .collect(); quote! { #enum_tag #(#fns)* } .into() }