mcfunction_debugger/generator/parser/
command.rs

1// Mcfunction-Debugger is a debugger for Minecraft's *.mcfunction files that does not require any
2// Minecraft mods.
3//
4// © Copyright (C) 2021-2024 Adrodoc <adrodoc55@googlemail.com> & Skagaros <skagaros@gmail.com>
5//
6// This file is part of Mcfunction-Debugger.
7//
8// Mcfunction-Debugger is free software: you can redistribute it and/or modify it under the terms of
9// the GNU General Public License as published by the Free Software Foundation, either version 3 of
10// the License, or (at your option) any later version.
11//
12// Mcfunction-Debugger is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
13// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15//
16// You should have received a copy of the GNU General Public License along with Mcfunction-Debugger.
17// If not, see <http://www.gnu.org/licenses/>.
18
19pub(crate) mod argument;
20pub mod resource_location;
21
22use crate::generator::parser::command::argument::{Argument, ArgumentParser};
23use serde::{Deserialize, Serialize};
24use std::{
25    collections::BTreeMap,
26    fmt::{Display, Write},
27    u32, usize,
28};
29
30pub struct CommandParser {
31    specs: BTreeMap<String, CommandSpec>,
32}
33
34impl CommandParser {
35    pub fn default() -> Result<CommandParser, serde_json::Error> {
36        let json = include_str!("commands.json");
37        CommandParser::from_str(json)
38    }
39
40    pub fn from_str(json: &str) -> serde_json::Result<CommandParser> {
41        let root_node: RootNode = serde_json::from_str(json)?;
42        Ok(CommandParser {
43            specs: root_node.children,
44        })
45    }
46
47    pub fn parse<'l>(&'l self, command: &'l str) -> CommandParserResult<'l> {
48        self.parse_from_specs(command, 0, &self.specs)
49    }
50
51    fn parse_from_specs<'l>(
52        &'l self,
53        command: &'l str,
54        index: usize,
55        specs: &'l BTreeMap<String, CommandSpec>,
56    ) -> CommandParserResult<'l> {
57        let parsed = Self::find_relevant_commands(command, index, specs)
58            .into_iter()
59            .map(|(name, spec)| (self.parse_from_single_spec(name, spec, command, index)))
60            .collect::<Vec<_>>();
61
62        let only_errors = parsed.iter().all(|parsed| parsed.error.is_some());
63        if only_errors {
64            // Return deepest error
65            parsed
66                .into_iter()
67                .max_by_key(|result| result.parsed_nodes.len())
68                .unwrap_or(CommandParserResult {
69                    parsed_nodes: Vec::new(),
70                    error: Some(CommandParserError {
71                        message: "Incorrect argument for command".to_string(),
72                        command,
73                        index,
74                    }),
75                })
76        } else {
77            // Return first non error
78            parsed
79                .into_iter()
80                .filter(|parsed| parsed.error.is_none())
81                .next()
82                .unwrap()
83        }
84    }
85
86    /// If the next part can be parsed as a literal, arguments should be ignored.
87    fn find_relevant_commands<'l>(
88        command: &'l str,
89        index: usize,
90        specs: &'l BTreeMap<String, CommandSpec>,
91    ) -> Vec<(&'l String, &'l CommandSpec)> {
92        let string = &command[index..];
93        let literal_len = string.find(' ').unwrap_or(string.len());
94        let literal = &string[..literal_len];
95        if let Some((name, command)) = Self::find_literal_command(literal, specs) {
96            vec![(name, command)]
97        } else {
98            specs
99                .iter()
100                .filter(|(_name, spec)| matches!(spec, CommandSpec::Argument { .. }))
101                .collect::<Vec<_>>()
102        }
103    }
104
105    fn find_literal_command<'l>(
106        literal: &str,
107        specs: &'l BTreeMap<String, CommandSpec>,
108    ) -> Option<(&'l String, &'l CommandSpec)> {
109        specs
110            .iter()
111            .find(|(name, spec)| *name == literal && matches!(spec, CommandSpec::Literal { .. }))
112    }
113
114    fn parse_from_single_spec<'l>(
115        &'l self,
116        name: &'l str,
117        spec: &'l CommandSpec,
118        command: &'l str,
119        mut index: usize,
120    ) -> CommandParserResult<'l> {
121        let mut parsed_nodes = Vec::new();
122
123        macro_rules! Ok {
124            () => {
125                CommandParserResult {
126                    parsed_nodes,
127                    error: None,
128                }
129            };
130        }
131        macro_rules! Err {
132            ($message:expr) => {
133                CommandParserResult {
134                    parsed_nodes,
135                    error: Some(CommandParserError {
136                        message: $message,
137                        command,
138                        index,
139                    }),
140                }
141            };
142        }
143
144        let parsed_node = match spec.parse(name, command, index) {
145            Ok(parsed_node) => parsed_node,
146            Err(message) => return Err!(message),
147        };
148        index += parsed_node.len();
149        parsed_nodes.push(parsed_node);
150
151        if index >= command.len() {
152            if spec.executable() {
153                return Ok!();
154            } else {
155                return Err!("Incomplete command".to_string());
156            }
157        }
158
159        const SPACE: char = ' ';
160        if !command[index..].starts_with(SPACE) {
161            return Err!(
162                "Expected whitespace to end one argument, but found trailing data".to_string()
163            );
164        }
165        index += SPACE.len_utf8();
166
167        // let mut children = spec.children();
168        let redirect = match spec.redirect() {
169            Ok(ok) => ok,
170            Err(message) => return Err!(message),
171        };
172        let children = if let Some(redirect) = redirect {
173            if let Some(redirected) = self.specs.get(redirect) {
174                parsed_nodes.push(ParsedNode::Redirect(redirect));
175                redirected.children()
176            } else {
177                return Err!(format!("Failed to resolve redirect {}", redirect));
178            }
179        } else if spec.has_children() {
180            spec.children()
181        } else if !spec.executable() {
182            // Special case for "execute run" which has no redirect to root for some reason
183            &self.specs
184        } else {
185            return Err!("Incorrect argument for command".to_string());
186        };
187        let mut result = self.parse_from_specs(command, index, children);
188        parsed_nodes.extend_from_slice(&result.parsed_nodes);
189        result.parsed_nodes = parsed_nodes;
190        result
191    }
192}
193
194#[derive(Clone, Debug, PartialEq)]
195pub struct CommandParserResult<'l> {
196    pub parsed_nodes: Vec<ParsedNode<'l>>,
197    pub error: Option<CommandParserError<'l>>,
198}
199
200#[derive(Clone, Debug, PartialEq)]
201pub struct CommandParserError<'l> {
202    pub message: String,
203    pub command: &'l str,
204    pub index: usize,
205}
206
207impl Display for CommandParserError<'_> {
208    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209        write!(f, "{}:\n{}\n", self.message, self.command)?;
210        for _ in 0..self.index {
211            f.write_char(' ')?;
212        }
213        f.write_char('^')
214    }
215}
216
217#[derive(Clone, Debug, PartialEq)]
218pub enum ParsedNode<'l> {
219    Redirect(&'l str),
220    Literal {
221        literal: &'l str,
222        index: usize,
223    },
224    Argument {
225        name: &'l str,
226        argument: Argument<'l>,
227        index: usize,
228        len: usize,
229    },
230}
231
232impl ParsedNode<'_> {
233    fn len(&self) -> usize {
234        match self {
235            ParsedNode::Redirect(_) => 0,
236            ParsedNode::Literal { literal, .. } => literal.len(),
237            ParsedNode::Argument { len, .. } => *len,
238        }
239    }
240}
241
242#[derive(Deserialize, Serialize, Debug)]
243#[serde(tag = "type", rename = "root")]
244struct RootNode {
245    children: BTreeMap<String, CommandSpec>,
246}
247
248#[derive(Debug, Deserialize, Serialize)]
249#[serde(tag = "type", rename_all = "snake_case")]
250pub enum CommandSpec {
251    Literal {
252        #[serde(flatten)]
253        node: Node,
254    },
255    Argument {
256        #[serde(flatten)]
257        node: Node,
258        #[serde(flatten)]
259        parser: ArgumentParser,
260    },
261}
262
263impl CommandSpec {
264    fn parse<'l>(
265        &self,
266        name: &'l str,
267        command: &'l str,
268        index: usize,
269    ) -> Result<ParsedNode<'l>, String> {
270        let string = &command[index..];
271        match self {
272            CommandSpec::Literal { .. } => {
273                let literal_len = string.find(' ').unwrap_or(string.len());
274                let literal = &string[..literal_len];
275                if literal == name {
276                    Ok(ParsedNode::Literal { literal, index })
277                } else {
278                    Err("Incorrect literal for command".to_string())
279                }
280            }
281            CommandSpec::Argument { parser, .. } => {
282                parser
283                    .parse(string)
284                    .map(|(argument, len)| ParsedNode::Argument {
285                        name,
286                        argument,
287                        index,
288                        len,
289                    })
290            }
291        }
292    }
293}
294
295#[derive(Debug, Deserialize, Serialize)]
296pub struct Node {
297    #[serde(default)]
298    pub children: BTreeMap<String, CommandSpec>,
299    #[serde(default)]
300    pub executable: bool,
301    #[serde(default)]
302    pub redirect: Vec<String>,
303}
304
305impl CommandSpec {
306    pub fn has_children(&self) -> bool {
307        !self.children().is_empty()
308    }
309
310    pub fn children(&self) -> &BTreeMap<String, CommandSpec> {
311        match self {
312            CommandSpec::Literal { node, .. } => &node.children,
313            CommandSpec::Argument { node, .. } => &node.children,
314        }
315    }
316
317    pub fn executable(&self) -> bool {
318        match self {
319            CommandSpec::Literal { node, .. } => node.executable,
320            CommandSpec::Argument { node, .. } => node.executable,
321        }
322    }
323
324    pub fn redirect(&self) -> Result<Option<&String>, String> {
325        let redirect = match self {
326            CommandSpec::Literal { node, .. } => &node.redirect,
327            CommandSpec::Argument { node, .. } => &node.redirect,
328        };
329        if redirect.len() > 1 {
330            Err(format!("Multi redirect is not supported: {:?}", redirect))
331        } else {
332            Ok(redirect.first())
333        }
334    }
335}
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340
341    #[test]
342    fn test_parse_json() {
343        // when:
344        let actual = &CommandParser::default().unwrap().specs;
345
346        // then:
347        assert!(
348            actual.contains_key("execute"),
349            "Expected actual to contain key 'execute': {:#?}",
350            actual
351        );
352    }
353
354    #[test]
355    fn test_serialize() {
356        // when:
357        let root = RootNode {
358            children: BTreeMap::new(),
359        };
360
361        let actual = serde_json::to_string(&root).unwrap();
362
363        // then:
364        assert_eq!(actual, r#"{"type":"root","children":{}}"#);
365    }
366}