mpl-macro 0.2.0

Derive parsers from MPL's one-rule grammar files. Includes the FastParse static-codegen backend (cascade detection, first-byte dispatch, Squirrel left recursion).
Documentation
use crate::mplg::parse_mplg;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use std::fs::File;
use std::io::Read;
use std::path::Path;
use syn::{parse2, Attribute, DeriveInput, Expr, ExprLit, Lit, Meta};

pub use self::cascade::{
    analyze_char_classes, analyze_first_byte_dispatch, is_left_recursive, is_tail_loop,
    DispatchCascade,
};
pub use self::cut::{classify_rules, compute_first_sets};
pub use self::fast::generate_fast;
pub use self::parser::generate_parser;
pub use self::rules::generate_rules;
pub use self::variable::generate_variable;

mod cascade;
mod cut;
mod fast;
mod parser;
mod rules;
mod variable;

enum GrammarData {
    Mplg(Vec<u8>),
    None,
}

pub fn derive_parser(input: TokenStream) -> TokenStream {
    let input: DeriveInput = match parse2(input) {
        Ok(input) => input,
        Err(e) => return e.to_compile_error(),
    };
    let parser_ident = input.ident;
    let ident = parser_ident.to_string().replace("Parser", "");
    let rules_ident = format_ident!("{}Rules", ident);
    let variable_ident = format_ident!("{}Variable", ident);

    let grammar_data = match input.attrs.as_slice() {
        [] => Ok(GrammarData::None),
        [attr] => get_grammar_data(attr),
        _ => Err(syn::Error::new_spanned(
            &parser_ident,
            "expected at most one `#[mplg = \"...\"]` attribute",
        )
        .to_compile_error()),
    };

    match grammar_data {
        Ok(GrammarData::Mplg(data)) => {
            let lines = parse_mplg(&data)
                .unwrap()
                .into_original()
                .expect("Lines")
                .into_lines();
            let variable = generate_variable(&variable_ident, &lines);
            let rules = generate_rules(&rules_ident, &variable_ident, &lines);
            let parser = generate_parser(&parser_ident, &rules_ident, &variable_ident);

            quote! {
                #variable
                #rules
                #parser
            }
        }
        Ok(GrammarData::None) => TokenStream::new(),
        Err(e) => e,
    }
}

pub fn derive_fast_parser(input: TokenStream) -> TokenStream {
    let input: DeriveInput = match parse2(input) {
        Ok(input) => input,
        Err(e) => return e.to_compile_error(),
    };
    let parser_ident = input.ident;

    let grammar_data = match input.attrs.as_slice() {
        [] => Ok(GrammarData::None),
        [attr] => get_grammar_data(attr),
        _ => Err(syn::Error::new_spanned(
            &parser_ident,
            "expected at most one `#[mplg = \"...\"]` attribute",
        )
        .to_compile_error()),
    };

    match grammar_data {
        Ok(GrammarData::Mplg(data)) => {
            let lines = parse_mplg(&data)
                .unwrap()
                .into_original()
                .expect("Lines")
                .into_lines();
            generate_fast(&parser_ident, &lines)
        }
        Ok(GrammarData::None) => TokenStream::new(),
        Err(e) => e,
    }
}

fn get_grammar_data(attr: &Attribute) -> Result<GrammarData, TokenStream> {
    let err = || syn::Error::new_spanned(attr, "expected `#[mplg = \"...\"]`").to_compile_error();

    let Meta::NameValue(name_value) = &attr.meta else {
        return Err(err());
    };
    if !name_value.path.is_ident("mplg") {
        return Err(err());
    }
    let Expr::Lit(ExprLit {
        lit: Lit::Str(lit_str),
        ..
    }) = &name_value.value
    else {
        return Err(err());
    };

    let path = lit_str.value();
    let root = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into());
    let full_path = Path::new(&root).join(&path);
    match read_file(&full_path) {
        Ok(data) => Ok(GrammarData::Mplg(data)),
        Err(e) => panic!("{} ({:#?})", e, full_path),
    }
}

fn read_file<P: AsRef<Path>>(path: P) -> std::io::Result<Vec<u8>> {
    let mut file = File::open(path)?;
    let mut v = Vec::new();
    file.read_to_end(&mut v)?;
    Ok(v)
}