Skip to main content

steel_core/command/brigadier/
reader.rs

1//! Cursor-based command input reader.
2
3use std::str::FromStr;
4
5use steel_utils::java;
6
7use super::{CommandSyntaxError, CommandSyntaxErrorKind};
8
9const SYNTAX_ESCAPE: char = '\\';
10const SYNTAX_DOUBLE_QUOTE: char = '"';
11const SYNTAX_SINGLE_QUOTE: char = '\'';
12
13#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
14pub(crate) struct ReaderCursor {
15    byte: usize,
16    utf16: usize,
17}
18
19/// Reads command input while exposing Brigadier-compatible UTF-16 positions.
20#[derive(Clone, Debug)]
21pub(crate) struct StringReader<'input> {
22    input: &'input str,
23    total_length: usize,
24    cursor: ReaderCursor,
25}
26
27impl<'input> StringReader<'input> {
28    /// Creates a reader at the beginning of `input`.
29    pub(crate) fn new(input: &'input str) -> Self {
30        Self {
31            input,
32            total_length: input.encode_utf16().count(),
33            cursor: ReaderCursor::default(),
34        }
35    }
36
37    /// Returns the complete command input.
38    pub(crate) const fn input(&self) -> &'input str {
39        self.input
40    }
41
42    /// Returns the input length in UTF-16 code units.
43    pub(crate) const fn total_length(&self) -> usize {
44        self.total_length
45    }
46
47    /// Returns the current position in UTF-16 code units.
48    pub(crate) const fn cursor(&self) -> usize {
49        self.cursor.utf16
50    }
51
52    /// Returns the current position in UTF-8 bytes.
53    pub(crate) const fn byte_cursor(&self) -> usize {
54        self.cursor.byte
55    }
56
57    /// Returns the remaining length in UTF-16 code units.
58    pub(crate) const fn remaining_length(&self) -> usize {
59        self.total_length - self.cursor.utf16
60    }
61
62    /// Returns whether at least one character remains.
63    pub(crate) const fn can_read(&self) -> bool {
64        self.cursor.byte < self.input.len()
65    }
66
67    /// Returns whether `length` UTF-16 code units remain.
68    pub(crate) fn can_read_length(&self, length: usize) -> bool {
69        self.cursor
70            .utf16
71            .checked_add(length)
72            .is_some_and(|end| end <= self.total_length)
73    }
74
75    /// Returns the next Unicode scalar without advancing the reader.
76    pub(crate) fn peek(&self) -> Option<char> {
77        self.remaining().chars().next()
78    }
79
80    /// Reads the next Unicode scalar.
81    pub(crate) fn read(&mut self) -> Option<char> {
82        let character = self.peek()?;
83        self.cursor.byte += character.len_utf8();
84        self.cursor.utf16 += character.len_utf16();
85        Some(character)
86    }
87
88    /// Advances past the next Unicode scalar if one remains.
89    pub(crate) fn skip(&mut self) -> bool {
90        self.read().is_some()
91    }
92
93    /// Returns the input before the cursor.
94    pub(crate) fn read_so_far(&self) -> &'input str {
95        &self.input[..self.cursor.byte]
96    }
97
98    /// Returns the input at and after the cursor.
99    pub(crate) fn remaining(&self) -> &'input str {
100        &self.input[self.cursor.byte..]
101    }
102
103    /// Reads all remaining input.
104    pub(crate) fn read_remaining(&mut self) -> &'input str {
105        let remaining = &self.input[self.cursor.byte..];
106        self.cursor.byte = self.input.len();
107        self.cursor.utf16 = self.total_length;
108        remaining
109    }
110
111    /// Advances by an exact UTF-8 byte count while retaining Brigadier's UTF-16 cursor.
112    pub(crate) fn advance_bytes(&mut self, bytes: usize) -> bool {
113        let Some(consumed) = self.remaining().get(..bytes) else {
114            return false;
115        };
116        self.cursor.byte += bytes;
117        self.cursor.utf16 += consumed.encode_utf16().count();
118        true
119    }
120
121    /// Advances past whitespace recognized by Java's `Character.isWhitespace`.
122    pub(crate) fn skip_whitespace(&mut self) {
123        while self.peek().is_some_and(java::is_whitespace) {
124            self.skip();
125        }
126    }
127
128    /// Reads an unquoted Brigadier string.
129    pub(crate) fn read_unquoted_string(&mut self) -> &'input str {
130        let start = self.checkpoint();
131        while self.peek().is_some_and(Self::is_allowed_in_unquoted_string) {
132            self.skip();
133        }
134        &self.input[start.byte..self.cursor.byte]
135    }
136
137    /// Reads one custom argument token up to Java-compatible whitespace.
138    pub(crate) fn read_unquoted_token(&mut self) -> &'input str {
139        let start = self.checkpoint();
140        while self
141            .peek()
142            .is_some_and(|character| !java::is_whitespace(character))
143        {
144            self.skip();
145        }
146        &self.input[start.byte..self.cursor.byte]
147    }
148
149    /// Reads a single- or double-quoted Brigadier string.
150    pub(crate) fn read_quoted_string(&mut self) -> Result<String, CommandSyntaxError> {
151        let Some(terminator) = self.peek() else {
152            return Ok(String::new());
153        };
154        if !Self::is_quoted_string_start(terminator) {
155            return Err(self.error(CommandSyntaxErrorKind::ExpectedStartOfQuote));
156        }
157
158        self.skip();
159        self.read_string_until(terminator)
160    }
161
162    /// Reads a quoted or unquoted Brigadier string.
163    pub(crate) fn read_string(&mut self) -> Result<String, CommandSyntaxError> {
164        let Some(next) = self.peek() else {
165            return Ok(String::new());
166        };
167        if Self::is_quoted_string_start(next) {
168            self.skip();
169            self.read_string_until(next)
170        } else {
171            Ok(self.read_unquoted_string().to_owned())
172        }
173    }
174
175    /// Reads a signed 32-bit integer.
176    pub(crate) fn read_int(&mut self) -> Result<i32, CommandSyntaxError> {
177        self.read_number(
178            CommandSyntaxErrorKind::ExpectedInt,
179            CommandSyntaxErrorKind::InvalidInt,
180        )
181    }
182
183    /// Reads a signed 64-bit integer.
184    pub(crate) fn read_long(&mut self) -> Result<i64, CommandSyntaxError> {
185        self.read_number(
186            CommandSyntaxErrorKind::ExpectedLong,
187            CommandSyntaxErrorKind::InvalidLong,
188        )
189    }
190
191    /// Reads a 64-bit floating-point number.
192    pub(crate) fn read_double(&mut self) -> Result<f64, CommandSyntaxError> {
193        self.read_number(
194            CommandSyntaxErrorKind::ExpectedDouble,
195            CommandSyntaxErrorKind::InvalidDouble,
196        )
197    }
198
199    /// Reads a 32-bit floating-point number.
200    pub(crate) fn read_float(&mut self) -> Result<f32, CommandSyntaxError> {
201        self.read_number(
202            CommandSyntaxErrorKind::ExpectedFloat,
203            CommandSyntaxErrorKind::InvalidFloat,
204        )
205    }
206
207    /// Reads a lowercase Brigadier boolean.
208    pub(crate) fn read_boolean(&mut self) -> Result<bool, CommandSyntaxError> {
209        let start = self.checkpoint();
210        let value = self.read_string()?;
211        match value.as_str() {
212            "true" => Ok(true),
213            "false" => Ok(false),
214            "" => Err(self.error(CommandSyntaxErrorKind::ExpectedBool)),
215            _ => {
216                self.restore(start);
217                Err(self.error(CommandSyntaxErrorKind::InvalidBool(value.into())))
218            }
219        }
220    }
221
222    /// Consumes `expected` or returns a contextual syntax error.
223    pub(crate) fn expect(&mut self, expected: char) -> Result<(), CommandSyntaxError> {
224        if self.peek() != Some(expected) {
225            return Err(self.error(CommandSyntaxErrorKind::ExpectedSymbol(expected)));
226        }
227        self.skip();
228        Ok(())
229    }
230
231    pub(super) fn try_read_literal(&mut self, literal: &str) -> bool {
232        let Some(remaining) = self.remaining().strip_prefix(literal) else {
233            return false;
234        };
235        if remaining
236            .chars()
237            .next()
238            .is_some_and(|character| character != ' ')
239        {
240            return false;
241        }
242
243        self.cursor.byte += literal.len();
244        self.cursor.utf16 += literal.encode_utf16().count();
245        true
246    }
247
248    fn read_string_until(&mut self, terminator: char) -> Result<String, CommandSyntaxError> {
249        let mut result = String::new();
250        let mut escaped = false;
251
252        while self.can_read() {
253            let character_start = self.checkpoint();
254            let Some(character) = self.read() else {
255                break;
256            };
257            if escaped {
258                if character == terminator || character == SYNTAX_ESCAPE {
259                    result.push(character);
260                    escaped = false;
261                } else {
262                    self.restore(character_start);
263                    return Err(self.error(CommandSyntaxErrorKind::InvalidEscape(character)));
264                }
265            } else if character == SYNTAX_ESCAPE {
266                escaped = true;
267            } else if character == terminator {
268                return Ok(result);
269            } else {
270                result.push(character);
271            }
272        }
273
274        Err(self.error(CommandSyntaxErrorKind::ExpectedEndOfQuote))
275    }
276
277    fn read_number<T>(
278        &mut self,
279        expected: CommandSyntaxErrorKind,
280        invalid: fn(Box<str>) -> CommandSyntaxErrorKind,
281    ) -> Result<T, CommandSyntaxError>
282    where
283        T: FromStr,
284    {
285        let start = self.checkpoint();
286        while self.peek().is_some_and(Self::is_allowed_number) {
287            self.skip();
288        }
289
290        let number = &self.input[start.byte..self.cursor.byte];
291        if number.is_empty() {
292            return Err(self.error(expected));
293        }
294        if let Ok(value) = number.parse() {
295            Ok(value)
296        } else {
297            let invalid_number = Box::<str>::from(number);
298            self.restore(start);
299            Err(self.error(invalid(invalid_number)))
300        }
301    }
302
303    /// Captures the current reader position for later restoration.
304    pub(crate) const fn checkpoint(&self) -> ReaderCursor {
305        self.cursor
306    }
307
308    /// Restores a position previously returned by [`Self::checkpoint`].
309    pub(crate) const fn restore(&mut self, checkpoint: ReaderCursor) {
310        self.cursor = checkpoint;
311    }
312
313    /// Creates a syntax error at the current reader position.
314    pub(crate) fn error(&self, kind: CommandSyntaxErrorKind) -> CommandSyntaxError {
315        CommandSyntaxError::new(kind, self.input, self.cursor.utf16, self.cursor.byte)
316    }
317
318    const fn is_allowed_number(character: char) -> bool {
319        character.is_ascii_digit() || matches!(character, '.' | '-')
320    }
321
322    const fn is_quoted_string_start(character: char) -> bool {
323        matches!(character, SYNTAX_DOUBLE_QUOTE | SYNTAX_SINGLE_QUOTE)
324    }
325
326    const fn is_allowed_in_unquoted_string(character: char) -> bool {
327        character.is_ascii_alphanumeric() || matches!(character, '_' | '-' | '.' | '+')
328    }
329}