Skip to main content

ratex_parser/functions/
environment.rs

1use std::collections::HashMap;
2
3use crate::environments::{EnvContext, ENVIRONMENTS};
4use crate::error::{ParseError, ParseResult};
5use crate::functions::{define_function_full, ArgType, FunctionContext, FunctionSpec};
6use crate::parse_node::ParseNode;
7
8pub fn register(map: &mut HashMap<&'static str, FunctionSpec>) {
9    define_function_full(
10        map,
11        &["\\begin", "\\end"],
12        "environment",
13        1,
14        0,
15        Some(vec![ArgType::Text]),
16        true,
17        true,
18        true,
19        false,
20        false,
21        handle_begin_end,
22    );
23
24    define_function_full(
25        map,
26        &["\\hline", "\\hdashline"],
27        "text",
28        0,
29        0,
30        None,
31        false,
32        true,
33        true,
34        false,
35        false,
36        handle_hline,
37    );
38}
39
40fn extract_env_name(name_group: &ParseNode) -> ParseResult<String> {
41    match name_group {
42        ParseNode::OrdGroup { body, .. } => {
43            let mut env_name = String::new();
44            for node in body {
45                match node {
46                    ParseNode::TextOrd { text, .. } | ParseNode::MathOrd { text, .. } => {
47                        env_name.push_str(text);
48                    }
49                    ParseNode::Atom { text, .. } => {
50                        env_name.push_str(text);
51                    }
52                    _ => {
53                        return Err(ParseError::msg(format!(
54                            "Invalid environment name character: {:?}",
55                            node.type_name()
56                        )));
57                    }
58                }
59            }
60            Ok(env_name)
61        }
62        _ => Err(ParseError::msg("Invalid environment name")),
63    }
64}
65
66fn handle_begin_end(
67    ctx: &mut FunctionContext,
68    args: Vec<ParseNode>,
69    _opt_args: Vec<Option<ParseNode>>,
70) -> ParseResult<ParseNode> {
71    let name_group = &args[0];
72    let env_name = extract_env_name(name_group)?;
73
74    if ctx.func_name == "\\begin" {
75        // Allow all registered environments to parse; display vs inline is a layout concern.
76        // (KaTeX rejects align/equation in inline $...$; we parse them and let layout decide.)
77        let env = ENVIRONMENTS
78            .get(env_name.as_str())
79            .ok_or_else(|| ParseError::msg(format!("No such environment: {}", env_name)))?;
80
81        // Parse environment arguments if any
82        let mut env_args = Vec::new();
83        for _ in 0..env.num_args {
84            let arg = ctx.parser.parse_argument_group(false, None)?;
85            match arg {
86                Some(a) => env_args.push(a),
87                None => {
88                    return Err(ParseError::msg(format!(
89                        "Expected argument to \\begin{{{}}}",
90                        env_name
91                    )));
92                }
93            }
94        }
95
96        let mut env_ctx = EnvContext {
97            mode: ctx.parser.mode,
98            env_name: env_name.clone(),
99            parser: ctx.parser,
100        };
101
102        let result = (env.handler)(&mut env_ctx, env_args, Vec::new())?;
103
104        // Expect \end
105        ctx.parser.expect("\\end", false)?;
106
107        // Parse \end{name} as a function call
108        let end_node = ctx.parser.parse_function(None, None)?;
109        let end_name = match &end_node {
110            Some(ParseNode::Environment { name, .. }) => name.clone(),
111            _ => {
112                return Err(ParseError::msg("Expected \\end after environment body"));
113            }
114        };
115
116        if end_name != env_name {
117            return Err(ParseError::msg(format!(
118                "Mismatch: \\begin{{{}}} matched by \\end{{{}}}",
119                env_name, end_name
120            )));
121        }
122
123        Ok(result)
124    } else {
125        // \end handler
126        Ok(ParseNode::Environment {
127            mode: ctx.parser.mode,
128            name: env_name,
129            name_group: Box::new(name_group.clone()),
130            loc: None,
131        })
132    }
133}
134
135fn handle_hline(
136    ctx: &mut FunctionContext,
137    _args: Vec<ParseNode>,
138    _opt_args: Vec<Option<ParseNode>>,
139) -> ParseResult<ParseNode> {
140    Err(ParseError::msg(format!(
141        "{} valid only within array environment",
142        ctx.func_name
143    )))
144}