vesti 0.4.1

A preprocessor that compiles into LaTeX
// Implementing ToString for Statement enum so that making full latex text easily.

use crate::error;
use crate::parser::ast::*;
use crate::parser::Parser;

pub fn make_latex_format<const IS_TEST: bool>(parser: &mut Parser) -> error::Result<String> {
    let latex = parser.parse_latex()?;
    let mut output = String::new();

    if !IS_TEST {
        output += &format!(
            "%\n%  This file was generated by vesti {}\n%\n",
            env!("CARGO_PKG_VERSION")
        );
    }

    for stmt in latex {
        output += &stmt.to_string();
    }

    Ok(output)
}

impl ToString for Statement {
    fn to_string(&self) -> String {
        match self {
            Statement::NonStopMode => String::from("\\nonstopmode\n"),
            Statement::DocumentClass { name, options } => docclass_to_string(name, options),
            Statement::Usepackage { name, options } => usepackage_to_string(name, options),
            Statement::MultiUsepackages { pkgs } => multiusepacakge_to_string(pkgs),
            Statement::DocumentStart => String::from("\\begin{document}\n"),
            Statement::DocumentEnd => String::from("\n\\end{document}\n"),
            Statement::MainText(s) => s.clone(),
            Statement::BracedStmt(latex) => format!("{{{}}}", latex_to_string(latex)),
            Statement::Fraction {
                numerator,
                denominator,
            } => fraction_to_string(numerator, denominator),
            Statement::PlainTextInMath { trim, text } => plaintext_in_math_to_string(trim, text),
            Statement::Integer(i) => i.to_string(),
            Statement::Float(f) => f.to_string(),
            Statement::RawLatex(s) => s.clone(),
            Statement::MathText { state, text } => math_text_to_string(*state, text),
            Statement::LatexFunction { name, args } => latex_function_to_string(name, args),
            Statement::Environment { name, args, text } => environment_to_string(name, args, text),
            Statement::BeginPhantomEnvironment { name, args } => {
                begin_phantom_environment_to_string(name, args)
            }
            Statement::EndPhantomEnvironment { name } => format!("\\end{{{name}}}"),
            Statement::FunctionDefine {
                style,
                name,
                args,
                trim,
                body,
            } => function_def_to_string(style, name, args, trim, body),
            Statement::EnvironmentDefine {
                is_redefine,
                name,
                args_num,
                optional_arg,
                trim,
                begin_part,
                end_part,
            } => environment_def_to_string(
                *is_redefine,
                name,
                *args_num,
                optional_arg.as_ref(),
                trim,
                begin_part,
                end_part,
            ),
        }
    }
}

fn docclass_to_string(name: &str, options: &Option<Vec<Latex>>) -> String {
    if let Some(opts) = options {
        let mut options_str = String::new();
        for o in opts {
            options_str = options_str + &latex_to_string(o) + ",";
        }
        options_str.pop();

        format!("\\documentclass[{options_str}]{{{name}}}\n")
    } else {
        format!("\\documentclass{{{name}}}\n")
    }
}

fn usepackage_to_string(name: &str, options: &Option<Vec<Latex>>) -> String {
    if let Some(opts) = options {
        let mut options_str = String::new();
        for o in opts {
            options_str = options_str + &latex_to_string(o) + ",";
        }
        options_str.pop();

        format!("\\usepackage[{options_str}]{{{name}}}\n")
    } else {
        format!("\\usepackage{{{name}}}\n")
    }
}

fn multiusepacakge_to_string(pkgs: &[Statement]) -> String {
    let mut output = String::new();
    for pkg in pkgs {
        if let Statement::Usepackage { name, options } = pkg {
            output += &usepackage_to_string(name, options);
        }
    }
    output
}

fn math_text_to_string(state: MathState, text: &[Statement]) -> String {
    let mut output = String::new();
    match state {
        MathState::Text => {
            output += "$";
            for t in text {
                output += &t.to_string();
            }
            output += "$";
        }
        MathState::Inline => {
            output += "\\[";
            for t in text {
                output += &t.to_string();
            }
            output += "\\]";
        }
    }
    output
}

fn fraction_to_string(numerator: &Latex, denominator: &Latex) -> String {
    format!(
        "\\frac{{{}}}{{{}}}",
        latex_to_string(numerator),
        latex_to_string(denominator)
    )
}

fn plaintext_in_math_to_string(trim: &TrimWhitespace, text: &Latex) -> String {
    let mut output = latex_to_string(text);
    if output.as_bytes()[output.len() - 1] == b' ' {
        output.pop();
    }

    match (trim.start, trim.end) {
        (true, true) => format!("\\text{{ {} }}", output),
        (true, false) => format!("\\text{{ {}}}", output),
        (false, true) => format!("\\text{{{} }}", output),
        (false, false) => format!("\\text{{{}}}", output),
    }
}

fn latex_function_to_string(name: &str, args: &Vec<(ArgNeed, Vec<Statement>)>) -> String {
    let mut output = format!("{}", name);
    for arg in args {
        let mut tmp = String::new();
        for t in &arg.1 {
            tmp += &t.to_string();
        }
        match arg.0 {
            ArgNeed::MainArg => output += &format!("{{{tmp}}}"),
            ArgNeed::Optional => output += &format!("[{tmp}]"),
            ArgNeed::StarArg => output.push('*'),
        }
    }
    output
}

fn begin_phantom_environment_to_string(
    name: &str,
    args: &Vec<(ArgNeed, Vec<Statement>)>,
) -> String {
    let mut output = format!("\\begin{{{name}}}");
    for arg in args {
        let mut tmp = String::new();
        for t in &arg.1 {
            tmp += &t.to_string();
        }
        match arg.0 {
            ArgNeed::MainArg => output += &format!("{{{tmp}}}"),
            ArgNeed::Optional => output += &format!("[{tmp}]"),
            ArgNeed::StarArg => output.push('*'),
        }
    }
    output
}

fn environment_to_string(
    name: &str,
    args: &Vec<(ArgNeed, Vec<Statement>)>,
    text: &Latex,
) -> String {
    let mut output = format!("\\begin{{{name}}}");
    for arg in args {
        let mut tmp = String::new();
        for t in &arg.1 {
            tmp += &t.to_string();
        }
        match arg.0 {
            ArgNeed::MainArg => output += &format!("{{{tmp}}}"),
            ArgNeed::Optional => output += &format!("[{tmp}]"),
            ArgNeed::StarArg => output.push('*'),
        }
    }
    for t in text {
        output += &t.to_string();
    }
    output += &format!("\\end{{{name}}}\n");
    output
}

fn latex_to_string(latex: &Latex) -> String {
    let mut output = String::new();
    for l in latex {
        output += &l.to_string();
    }
    output
}

fn function_def_to_string(
    style: &FunctionStyle,
    name: &str,
    args: &str,
    trim: &TrimWhitespace,
    body: &Latex,
) -> String {
    let mut output = match style {
        FunctionStyle::Plain => format!("\\def\\{name}{args}{{"),
        FunctionStyle::LongPlain => format!("\\long\\def\\{name}{args}{{"),
        FunctionStyle::OuterPlain => format!("\\outer\\def\\{name}{args}{{"),
        FunctionStyle::LongOuterPlain => format!("\\long\\outer\\def\\{name}{args}{{"),
        FunctionStyle::Expand => format!("\\edef\\{name}{args}{{"),
        FunctionStyle::LongExpand => format!("\\long\\edef\\{name}{args}{{"),
        FunctionStyle::OuterExpand => format!("\\outer\\edef\\{name}{args}{{"),
        FunctionStyle::LongOuterExpand => format!("\\long\\outer\\edef\\{name}{args}{{"),
        FunctionStyle::Global => format!("\\gdef\\{name}{args}{{"),
        FunctionStyle::LongGlobal => format!("\\long\\gdef\\{name}{args}{{"),
        FunctionStyle::OuterGlobal => format!("\\outer\\gdef\\{name}{args}{{"),
        FunctionStyle::LongOuterGlobal => format!("\\long\\outer\\gdef\\{name}{args}{{"),
        FunctionStyle::ExpandGlobal => format!("\\xdef\\{name}{args}{{"),
        FunctionStyle::LongExpandGlobal => format!("\\long\\xdef\\{name}{args}{{"),
        FunctionStyle::OuterExpandGlobal => format!("\\outer\\xdef\\{name}{args}{{"),
        FunctionStyle::LongOuterExpandGlobal => format!("\\long\\outer\\xdef\\{name}{args}{{"),
    };

    let mut tmp = String::new();
    for b in body {
        tmp += &b.to_string();
    }

    output += match (trim.start, trim.end) {
        (false, false) => tmp.as_str(),
        (true, false) => tmp.trim_start(),
        (false, true) => tmp.trim_end(),
        (true, true) => tmp.trim(),
    };
    output.push_str("}\n");

    output
}

fn environment_def_to_string(
    is_redefine: bool,
    name: &str,
    args_num: u8,
    optional_arg: Option<&Latex>,
    trim: &TrimWhitespace,
    begin_part: &Latex,
    end_part: &Latex,
) -> String {
    let mut output = if is_redefine {
        format!("\\renewenvironment{{{name}}}")
    } else {
        format!("\\newenvironment{{{name}}}")
    };

    if args_num > 0 {
        output += &format!("[{args_num}]");
        if let Some(inner) = optional_arg {
            output.push('[');
            for stmt in inner {
                output += &stmt.to_string();
            }
            output.push_str("]{");
        } else {
            output.push('{');
        }
    } else {
        output.push('{');
    }

    let mut tmp = String::new();
    for b in begin_part {
        tmp += &b.to_string();
    }
    output += match (trim.start, trim.mid) {
        (false, Some(false)) => tmp.as_str(),
        (true, Some(false)) => tmp.trim_start(),
        (false, Some(true)) => tmp.trim_end(),
        (true, Some(true)) => tmp.trim(),
        _ => unreachable!("VESTI BUG!!!!: codegen::environment_def_to_string"),
    };
    output.push_str("}{");

    tmp.clear();
    for b in end_part {
        tmp += &b.to_string();
    }
    output += match (trim.mid, trim.end) {
        (Some(false), false) => tmp.as_str(),
        (Some(true), false) => tmp.trim_start(),
        (Some(false), true) => tmp.trim_end(),
        (Some(true), true) => tmp.trim(),
        _ => unreachable!("VESTI BUG!!!!: codegen::environment_def_to_string"),
    };
    output.push_str("}\n");

    output
}