183 lines
5.2 KiB
Rust
183 lines
5.2 KiB
Rust
use itertools::{Itertools, Position};
|
|
use proc_macro::TokenStream;
|
|
use quote::quote;
|
|
use syn::{
|
|
Ident, LitStr, Token,
|
|
parse::{Parse, ParseStream},
|
|
parse_macro_input,
|
|
punctuated::Punctuated,
|
|
};
|
|
|
|
struct Grammar {
|
|
rules: Punctuated<Rule, Token![;]>,
|
|
}
|
|
|
|
struct Rule {
|
|
name: Ident,
|
|
definition: Term,
|
|
}
|
|
|
|
enum Term {
|
|
Literal(LitStr),
|
|
Rule(Ident),
|
|
Sequence(Vec<Term>),
|
|
}
|
|
|
|
impl Parse for Grammar {
|
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
|
Ok(Grammar {
|
|
rules: Punctuated::parse_terminated(input)?,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Parse for Rule {
|
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
|
let name = input.parse()?;
|
|
input.parse::<Token![=]>()?;
|
|
let definition = input.parse()?;
|
|
Ok(Self { name, definition })
|
|
}
|
|
}
|
|
|
|
impl Parse for Term {
|
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
|
let mut terms = vec![];
|
|
let parse_one = || {
|
|
let look = input.lookahead1();
|
|
if look.peek(Ident) {
|
|
input.parse().map(Term::Rule)
|
|
} else if look.peek(LitStr) {
|
|
input.parse().map(Term::Literal)
|
|
} else {
|
|
Err(look.error())
|
|
}
|
|
};
|
|
terms.push(parse_one()?);
|
|
while !input.is_empty() && !input.peek(Token![;]) {
|
|
terms.push(parse_one()?);
|
|
}
|
|
if terms.len() == 1 {
|
|
Ok(terms.pop().unwrap())
|
|
} else {
|
|
Ok(Term::Sequence(terms))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Term {
|
|
fn generate_code(&self) -> proc_macro2::TokenStream {
|
|
match self {
|
|
Term::Rule(ident) => quote! {
|
|
#ident(p)
|
|
},
|
|
Term::Literal(lit_str) => quote! {
|
|
p.literal(#lit_str)
|
|
},
|
|
Term::Sequence(terms) => {
|
|
let parts: Vec<_> = terms
|
|
.iter()
|
|
.with_position()
|
|
.map(|(pos, term)| {
|
|
let code = term.generate_wrapped_code();
|
|
match pos {
|
|
Position::First => quote! {
|
|
let result = #code;
|
|
if !result {
|
|
return false;
|
|
}
|
|
},
|
|
Position::Middle => quote! {
|
|
let result = #code;
|
|
if !result {
|
|
p.restore(save);
|
|
return false;
|
|
}
|
|
},
|
|
_ => code,
|
|
}
|
|
})
|
|
.collect();
|
|
quote! {
|
|
let save = p.save();
|
|
#(#parts)*
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn generate_wrapped_code(&self) -> proc_macro2::TokenStream {
|
|
let code = self.generate_code();
|
|
match self {
|
|
Term::Sequence(_) => 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)) }
|
|
}
|
|
'"' => {
|
|
let inner = &s[1..s.len() - 1];
|
|
quote! { Some(crate::peg::Foo::Baz(#inner.into())) }
|
|
}
|
|
_ => quote! { None },
|
|
}
|
|
}
|
|
_ => quote! { None },
|
|
};
|
|
|
|
quote! { println!("Hello, world! {:?}", #result); }.into()
|
|
}
|
|
|
|
#[proc_macro]
|
|
pub fn grammar(ts: TokenStream) -> TokenStream {
|
|
let input = parse_macro_input!(ts as Grammar);
|
|
let fns: Vec<_> = input
|
|
.rules
|
|
.iter()
|
|
.map(|r| {
|
|
let fn_name = &r.name;
|
|
|
|
// // Generate a string for debugging
|
|
// fn term2str(t: &Term) -> String {
|
|
// match t {
|
|
// Term::Rule(ident) => format!("rule {}", ident.to_string()),
|
|
// Term::Literal(lit_str) => format!("literal \"{}\"", lit_str.value()),
|
|
// Term::Sequence(terms) => {
|
|
// let parts: Vec<_> = terms.iter().map(term2str).collect();
|
|
// format!("sequence [{}]", parts.join(", "))
|
|
// }
|
|
// }
|
|
// }
|
|
// let text = format!("I'm generated! ({})", term2str(&r.definition));
|
|
|
|
let generated = r.definition.generate_code();
|
|
quote! {
|
|
fn #fn_name(p: &mut crate::peg::ParseState) -> bool {
|
|
// println!(#text.into());
|
|
#generated
|
|
}
|
|
}
|
|
})
|
|
.collect();
|
|
quote! {
|
|
#(#fns)*
|
|
}
|
|
.into()
|
|
}
|