steel_core/command/brigadier/
reader.rs1use 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#[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 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 pub(crate) const fn input(&self) -> &'input str {
39 self.input
40 }
41
42 pub(crate) const fn total_length(&self) -> usize {
44 self.total_length
45 }
46
47 pub(crate) const fn cursor(&self) -> usize {
49 self.cursor.utf16
50 }
51
52 pub(crate) const fn byte_cursor(&self) -> usize {
54 self.cursor.byte
55 }
56
57 pub(crate) const fn remaining_length(&self) -> usize {
59 self.total_length - self.cursor.utf16
60 }
61
62 pub(crate) const fn can_read(&self) -> bool {
64 self.cursor.byte < self.input.len()
65 }
66
67 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 pub(crate) fn peek(&self) -> Option<char> {
77 self.remaining().chars().next()
78 }
79
80 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 pub(crate) fn skip(&mut self) -> bool {
90 self.read().is_some()
91 }
92
93 pub(crate) fn read_so_far(&self) -> &'input str {
95 &self.input[..self.cursor.byte]
96 }
97
98 pub(crate) fn remaining(&self) -> &'input str {
100 &self.input[self.cursor.byte..]
101 }
102
103 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 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 pub(crate) fn skip_whitespace(&mut self) {
123 while self.peek().is_some_and(java::is_whitespace) {
124 self.skip();
125 }
126 }
127
128 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 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 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 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 pub(crate) fn read_int(&mut self) -> Result<i32, CommandSyntaxError> {
177 self.read_number(
178 CommandSyntaxErrorKind::ExpectedInt,
179 CommandSyntaxErrorKind::InvalidInt,
180 )
181 }
182
183 pub(crate) fn read_long(&mut self) -> Result<i64, CommandSyntaxError> {
185 self.read_number(
186 CommandSyntaxErrorKind::ExpectedLong,
187 CommandSyntaxErrorKind::InvalidLong,
188 )
189 }
190
191 pub(crate) fn read_double(&mut self) -> Result<f64, CommandSyntaxError> {
193 self.read_number(
194 CommandSyntaxErrorKind::ExpectedDouble,
195 CommandSyntaxErrorKind::InvalidDouble,
196 )
197 }
198
199 pub(crate) fn read_float(&mut self) -> Result<f32, CommandSyntaxError> {
201 self.read_number(
202 CommandSyntaxErrorKind::ExpectedFloat,
203 CommandSyntaxErrorKind::InvalidFloat,
204 )
205 }
206
207 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 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 pub(crate) const fn checkpoint(&self) -> ReaderCursor {
305 self.cursor
306 }
307
308 pub(crate) const fn restore(&mut self, checkpoint: ReaderCursor) {
310 self.cursor = checkpoint;
311 }
312
313 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}