lwb-parser 0.1.7

parser framwork with automatically generated ASTs for the LWB project
Documentation
use crate::codegen::generate_file_headers::write_headers;
// use crate::codegen::generate_language;
use crate::codegen::error::CodegenError;
use crate::codegen::error::CodegenError::NoExtension;
use crate::codegen::generate_from_pairs::generate_from_pairs;
use crate::codegen::generate_misc::{generate_parser, generate_root};
use crate::codegen::generate_structs::generate_structs;
use crate::codegen::generate_trait_impls::generate_trait_impls;
use crate::codegen::FormattingFile;
use crate::config::toml::{find_config_path, read_config, ReadConfigError};
use crate::config::Config;
use crate::error::display_miette_error;
use crate::language::Language;
use crate::parser::syntax_file::{convert_syntax_file_ast, ParseError, SyntaxFile};
use crate::sources::source_file::SourceFile;
use proc_macro2::TokenStream;
use quote::quote;
use std::io::Write;
use std::path::{Path, PathBuf};

pub fn create_module_files<const N: usize>(
    location: impl AsRef<Path>,
    files: [&str; N],
) -> Result<[(FormattingFile, String); N], CodegenError> {
    let mut location = location.as_ref().to_path_buf();
    location.set_extension("");

    std::fs::create_dir_all(&location)?;
    println!("cargo:rerun-if-changed={:?}", location);

    let mut res = files.map(|_| None);
    for (index, &i) in files.iter().enumerate() {
        let mut filename = location.clone();
        filename.push(i);
        filename.set_extension("rs");

        println!("cargo:rerun-if-changed={:?}", filename);
        res[index] = Some((
            FormattingFile::create(&filename)?,
            filename
                .file_stem()
                .ok_or(NoExtension)?
                .to_string_lossy()
                .into_owned(),
        ));
    }

    Ok(res.map(|i| i.unwrap()))
}

fn refs(inp: &mut (FormattingFile, String)) -> (&mut FormattingFile, &str) {
    (&mut inp.0, &inp.1)
}

pub(crate) struct Generated {
    impls: TokenStream,
    structs: TokenStream,
    from_pairs: TokenStream,
    root: TokenStream,
    parser: TokenStream,
}

fn codegen_internal(
    source: SourceFile,
    config: Config,
    imports: &[&str],
) -> Result<Generated, CodegenError> {
    let ast = SyntaxFile::try_parse(&source)?;

    let serialized_parser = bincode::serialize(&ast)?;

    let legacy_ast = convert_syntax_file_ast::convert(ast)?; // TODO make peg parser use new ast

    let mut derives = vec!["Debug", "PartialEq"];

    if config.syntax.serde {
        derives.extend(["Serialize", "Deserialize"]);
    }

    let legacy_ast = legacy_ast.simplify()?;

    let structs = generate_structs(&legacy_ast, &derives, config.syntax.non_exhaustive)?;
    let from_pairs = generate_from_pairs(&legacy_ast, config.syntax.non_exhaustive)?;
    let impls = generate_trait_impls(&legacy_ast)?;
    let root = generate_root(
        imports,
        &derives,
        config.syntax.mode.import_location(),
        config.syntax.non_exhaustive,
    )?;
    let parser = generate_parser(&serialized_parser)?;

    Ok(Generated {
        impls,
        structs,
        from_pairs,
        root,
        parser,
    })
}

#[doc(hidden)]
pub fn __codegen_tokenstream(
    source: SourceFile,
    config: Config,
    debug: bool,
) -> Result<TokenStream, CodegenError> {
    let Generated {
        impls,
        structs,
        from_pairs,
        root,
        parser,
    } = codegen_internal(source, config, &[])?;

    if debug {
        println!("{}", structs);
    }

    Ok(quote!(
        mod ast {
            #structs
        }
        mod from_pairs {
            #from_pairs
        }
        mod ast_impls {
            #impls
        }
        mod parser {
            #parser
        }

        pub use ast::*;
        pub use from_pairs::*;
        pub use ast_impls::*;
        pub use parser::*;

        #root
    ))
}

fn unwrap<T>(r: Result<T, ReadConfigError>) -> T {
    match r {
        Ok(i) => i,
        Err(e) => {
            panic!("failed to read config: {e}")
        }
    }
}

pub struct Codegen {
    config: Config,
}

impl Default for Codegen {
    fn default() -> Self {
        Self::new()
    }
}

impl Codegen {
    pub fn try_new() -> Result<Self, ReadConfigError> {
        Self::try_with_config(find_config_path())
    }

    pub fn try_with_config(path: PathBuf) -> Result<Self, ReadConfigError> {
        println!("cargo:rerun-if-changed={:?}", path);
        Ok(Self::with_config_struct(read_config(path)?))
    }

    pub fn with_config_struct(config: Config) -> Self {
        Self { config }
    }

    pub fn new() -> Self {
        unwrap(Self::try_new())
    }

    pub fn with_config(path: PathBuf) -> Self {
        unwrap(Self::try_with_config(path))
    }

    pub fn codegen(self) {
        if let Err(e) = self.try_codegen() {
            match e {
                CodegenError::ParseError(ParseError::PEG(errs)) => {
                    for e in errs.iter().rev() {
                        eprintln!("{}", display_miette_error(e));
                    }
                    panic!("failed to generate ast")
                }
                e => panic!("failed to generate ast: {e}"),
            }
        }
    }

    pub fn try_codegen(self) -> Result<(), CodegenError> {
        let mut files = create_module_files(
            &self.config.syntax.destination,
            [
                "mod.rs",
                "ast.rs",
                "from_pairs.rs",
                "ast_impls.rs",
                "parser.rs",
            ],
        )?;

        write_headers(&mut files.iter_mut().map(refs).collect::<Vec<_>>())?;

        let [ref mut f_modrs, ref mut f_ast, ref mut f_from_pairs, ref mut f_ast_trait_impls, ref mut f_serialized_parser] =
            files.map(|i| i.0);

        let write_serialized_ast = self.config.syntax.write_serialized_ast;

        let Generated {
            impls,
            structs,
            from_pairs,
            root,
            parser,
        } = codegen_internal(
            SourceFile::open(&self.config.syntax.definition)?,
            self.config,
            &["ast", "from_pairs", "ast_impls", "parser"],
        )?;

        write!(f_modrs, "{}", root)?;
        write!(f_ast, "{}", structs)?;
        write!(f_ast_trait_impls, "{}", impls)?;
        write!(f_from_pairs, "{}", from_pairs)?;

        if write_serialized_ast {
            write!(f_serialized_parser, "{}", parser)?;
        }

        Ok(())
    }
}