use rowan::GreenNode;
use smol_str::SmolStr;
use crate::parser::grammar;
use crate::parser::lexer::{LatexFlavor, VerbCtx, lex_with};
use crate::parser::tree_builder::build_tree;
use crate::semantic::define::scan_definitions;
use crate::syntax::SyntaxNode;
#[derive(Debug, Clone)]
pub struct Parse {
pub green: GreenNode,
pub errors: Vec<SyntaxError>,
}
impl Parse {
pub fn syntax(&self) -> SyntaxNode {
SyntaxNode::new_root(self.green.clone())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SyntaxError {
pub message: String,
pub start: usize,
pub end: usize,
}
pub fn parse(input: &str) -> Parse {
parse_with_flavor(input, LatexFlavor::Document)
}
pub fn parse_with_flavor(input: &str, flavor: LatexFlavor) -> Parse {
let pass1 = parse_with(input, &VerbCtx::default(), flavor);
let ctx = verbatim_ctx(&pass1.syntax());
if ctx.is_empty() {
return pass1;
}
parse_with(input, &ctx, flavor)
}
fn parse_with(input: &str, ctx: &VerbCtx, flavor: LatexFlavor) -> Parse {
let tokens = lex_with(input, ctx, flavor);
let (events, errors) = grammar::parse(&tokens, ctx);
let green = build_tree(&tokens, &events);
Parse { green, errors }
}
fn verbatim_ctx(root: &SyntaxNode) -> VerbCtx {
let db = scan_definitions(root);
let mut ctx = VerbCtx::default();
for name in db.command_names() {
if let Some(sig) = db.command(name).filter(|sig| sig.verbatim) {
ctx.insert(SmolStr::new(name), sig.args.clone());
}
}
for name in db.environment_names() {
if let Some(sig) = db.environment(name).filter(|sig| sig.verbatim_body) {
ctx.insert_environment(SmolStr::new(name), sig.args.clone());
}
}
ctx
}
pub fn reconstruct(input: &str) -> String {
parse(input).syntax().to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reconstruct_is_identity() {
let input = "\\section{Hi}\n\nbody $x^2$ % c\n";
assert_eq!(reconstruct(input), input);
}
#[test]
fn command_wraps_its_argument_group() {
use crate::syntax::SyntaxKind;
let parse = parse(r"\a{b}");
let command = parse
.syntax()
.descendants()
.find(|n| n.kind() == SyntaxKind::COMMAND)
.expect("a COMMAND node");
assert!(
command.children().any(|n| n.kind() == SyntaxKind::GROUP),
"the argument should be a nested GROUP node"
);
assert!(parse.errors.is_empty());
}
}