yggdrasil-shared 0.2.5

Yggdrasil Compiler core shared components
use crate::{
    codegen::Railroad,
    optimize::{InsertIgnore, RefineRules},
    parse_grammar,
};
use askama::Template;
use itertools::Itertools;

use crate::optimize::RemarkTags;
use railroad::{Diagram, Node, VerticalGrid};
use std::{
    fmt::Write,
    fs,
    fs::{create_dir_all, File},
    io::{Error, ErrorKind, Write as _},
    path::{Path, PathBuf},
};
use yggdrasil_error::{Failure, Success, Validate, Validation};
use yggdrasil_ir::{
    grammar::GrammarInfo,
    rule::GrammarRule,
    traits::{CodeGenerator, CodeOptimizer},
};

mod build_main;
mod build_readme;
mod filters;
mod grammar_ext;
mod rule_ext;

use self::{grammar_ext::GrammarExt, rule_ext::RuleExt};

#[derive(Clone, Debug)]
pub struct RustCodegen {
    pub range_type: String,
    pub rule_prefix: String,
    pub node_suffix: String,
    pub railway: Railroad,
}

impl Default for RustCodegen {
    fn default() -> Self {
        Self {
            range_type: "u32".to_string(),
            rule_prefix: "".to_string(),
            node_suffix: "Node".to_string(),
            railway: Default::default(),
        }
    }
}

#[derive(Template)]
#[template(path = "rust/main.jinja", escape = "none")]
pub struct RustWriteMain<'i> {
    grammar: &'i GrammarInfo,
    config: RustCodegen,
}

#[derive(Template)]
#[template(path = "rust/lex.jinja", escape = "none")]
pub struct RustWriteLex<'i> {
    grammar: &'i GrammarInfo,
    config: RustCodegen,
}

#[derive(Template)]
#[template(path = "rust/cst.jinja", escape = "none")]
pub struct RustWriteCST<'i> {
    grammar: &'i GrammarInfo,
    config: RustCodegen,
}

#[derive(Template)]
#[template(path = "rust/ast.jinja", escape = "none")]
pub struct RustWriteAST<'i> {
    grammar: &'i GrammarInfo,
    config: RustCodegen,
}

#[derive(Template)]
#[template(path = "rust/readme.jinja", escape = "none")]
pub struct RustWriteReadme<'i> {
    grammar: &'i GrammarInfo,
    config: RustCodegen,
    railroad: Diagram<VerticalGrid<Box<dyn Node>>>,
}

#[derive(Default)]
pub struct RustModule {
    pub main: String,
    pub lex: String,
    pub cst: String,
    pub ast: String,
    pub readme: String,
    pub railway: String,
    pub railway_min: String,
    pub ron: String,
}

impl CodeGenerator for RustCodegen {
    type Output = RustModule;

    fn generate(&mut self, info: &GrammarInfo) -> Validation<Self::Output> {
        let mut out = RustModule::default();
        let mut errors = vec![];
        out.ron = format!("{:#?}", info);
        out.main = RustWriteMain { grammar: info, config: self.clone() }.render().recover(&mut errors)?;
        out.lex = RustWriteLex { grammar: info, config: self.clone() }.render().recover(&mut errors)?;
        out.cst = RustWriteCST { grammar: info, config: self.clone() }.render().recover(&mut errors)?;
        out.ast = RustWriteAST { grammar: info, config: self.clone() }.render().recover(&mut errors)?;
        let readme = RustWriteReadme {
            grammar: info,
            config: self.clone(),
            railroad: self.railway.generate(info).recover(&mut errors)?,
        };
        out.readme = readme.render().recover(&mut errors)?;
        out.railway = readme.railway_svg();
        out.railway_min = readme.railway_min();
        Success { value: out, diagnostics: errors }
    }
}

impl RustCodegen {
    pub fn generate<P: AsRef<Path>>(&self, grammar: &str, output: P) -> Validation<PathBuf> {
        let mut errors = vec![];
        let info = parse_grammar(grammar).validate(&mut errors)?;
        let out = info.generate(RustCodegen::default()).validate(&mut errors)?;
        out.save(output).validate(&mut errors)
    }
}

impl RustModule {
    pub fn save<P: AsRef<Path>>(&self, path: P) -> std::io::Result<PathBuf> {
        let path = path.as_ref();
        if path.exists() {
            if !path.is_dir() {
                return Err(Error::new(ErrorKind::InvalidInput, "Path is not a directory"));
            }
        }
        else {
            create_dir_all(path)?
        }
        let mut main = File::create(path.join("mod.rs"))?;
        main.write_all(self.main.as_bytes())?;
        let mut cst = File::create(path.join("lexer.rs"))?;
        cst.write_all(self.lex.as_bytes())?;
        let mut cst = File::create(path.join("parse_cst.rs"))?;
        cst.write_all(self.cst.as_bytes())?;
        let mut ast = File::create(path.join("parse_ast.rs"))?;
        ast.write_all(self.ast.as_bytes())?;
        if !self.railway.is_empty() {
            let mut ast = File::create(path.join("railway.svg"))?;
            ast.write_all(self.railway.as_bytes())?;
            let mut ast = File::create(path.join("railway.min.svg"))?;
            ast.write_all(self.railway_min.as_bytes())?;
        }
        let mut ast = File::create(path.join("readme.md"))?;
        ast.write_all(self.readme.as_bytes())?;
        let mut ast = File::create(path.join("debug.ron"))?;
        ast.write_all(self.ron.as_bytes())?;
        path.canonicalize()
    }
}