Skip to main content

steel_core/command/brigadier/
argument.rs

1//! Built-in Brigadier argument parsing.
2
3use super::{
4    ArgumentSuggestionContext, CommandSyntaxError, CommandSyntaxErrorKind, StringReader,
5    SuggestionsBuilder,
6};
7
8/// A parser and parsed-value representation stored by one command runtime.
9pub(crate) trait CommandArgumentParser<S>: PartialEq + Send + Sync + 'static {
10    /// The value retained in the parsed command context.
11    type Value: Clone + Send + Sync + 'static;
12
13    /// Parses one value from the reader at its current cursor.
14    fn parse(
15        &self,
16        reader: &mut StringReader<'_>,
17        source: &S,
18    ) -> Result<Self::Value, CommandSyntaxError>;
19
20    /// Adds completions for a partially entered value.
21    fn list_suggestions(
22        &self,
23        context: &ArgumentSuggestionContext<'_, S, Self::Value>,
24        builder: &mut SuggestionsBuilder<'_>,
25    );
26}
27
28/// The parsing mode for a string argument.
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub(crate) enum StringType {
31    Word,
32    QuotablePhrase,
33    GreedyPhrase,
34}
35
36/// A built-in Brigadier argument parser configuration.
37#[derive(Clone, Debug, PartialEq)]
38pub(crate) enum ArgumentType {
39    /// A lowercase boolean.
40    Bool,
41    /// A bounded signed 32-bit integer.
42    Integer { minimum: i32, maximum: i32 },
43    /// A bounded signed 64-bit integer.
44    Long { minimum: i64, maximum: i64 },
45    /// A bounded 32-bit floating-point number.
46    Float { minimum: f32, maximum: f32 },
47    /// A bounded 64-bit floating-point number.
48    Double { minimum: f64, maximum: f64 },
49    /// A word, quotable phrase, or greedy phrase.
50    String(StringType),
51}
52
53impl ArgumentType {
54    /// Creates a boolean argument parser.
55    pub(crate) const fn bool() -> Self {
56        Self::Bool
57    }
58
59    /// Creates a bounded integer argument parser.
60    pub(crate) const fn integer(minimum: i32, maximum: i32) -> Self {
61        Self::Integer { minimum, maximum }
62    }
63
64    /// Creates a bounded long argument parser.
65    pub(crate) const fn long(minimum: i64, maximum: i64) -> Self {
66        Self::Long { minimum, maximum }
67    }
68
69    /// Creates a bounded float argument parser.
70    pub(crate) const fn float(minimum: f32, maximum: f32) -> Self {
71        Self::Float { minimum, maximum }
72    }
73
74    /// Creates a bounded double argument parser.
75    pub(crate) const fn double(minimum: f64, maximum: f64) -> Self {
76        Self::Double { minimum, maximum }
77    }
78
79    /// Creates a single-word string argument parser.
80    pub(crate) const fn word() -> Self {
81        Self::String(StringType::Word)
82    }
83
84    /// Creates a quoted or unquoted phrase argument parser.
85    pub(crate) const fn string() -> Self {
86        Self::String(StringType::QuotablePhrase)
87    }
88
89    /// Creates an argument parser that consumes the remaining input.
90    pub(crate) const fn greedy_string() -> Self {
91        Self::String(StringType::GreedyPhrase)
92    }
93
94    pub(crate) fn parse_value(
95        &self,
96        reader: &mut StringReader<'_>,
97    ) -> Result<PrimitiveArgumentValue, CommandSyntaxError> {
98        match *self {
99            Self::Bool => reader.read_boolean().map(PrimitiveArgumentValue::Bool),
100            Self::Integer { minimum, maximum } => {
101                let start = reader.checkpoint();
102                let value = reader.read_int()?;
103                if value < minimum {
104                    reader.restore(start);
105                    return Err(reader.error(CommandSyntaxErrorKind::IntegerTooLow {
106                        found: value,
107                        minimum,
108                    }));
109                }
110                if value > maximum {
111                    reader.restore(start);
112                    return Err(reader.error(CommandSyntaxErrorKind::IntegerTooHigh {
113                        found: value,
114                        maximum,
115                    }));
116                }
117                Ok(PrimitiveArgumentValue::Integer(value))
118            }
119            Self::Long { minimum, maximum } => {
120                let start = reader.checkpoint();
121                let value = reader.read_long()?;
122                if value < minimum {
123                    reader.restore(start);
124                    return Err(reader.error(CommandSyntaxErrorKind::LongTooLow {
125                        found: value,
126                        minimum,
127                    }));
128                }
129                if value > maximum {
130                    reader.restore(start);
131                    return Err(reader.error(CommandSyntaxErrorKind::LongTooHigh {
132                        found: value,
133                        maximum,
134                    }));
135                }
136                Ok(PrimitiveArgumentValue::Long(value))
137            }
138            Self::Float { minimum, maximum } => {
139                let start = reader.checkpoint();
140                let value = reader.read_float()?;
141                if value < minimum {
142                    reader.restore(start);
143                    return Err(reader.error(CommandSyntaxErrorKind::FloatTooLow {
144                        found: value,
145                        minimum,
146                    }));
147                }
148                if value > maximum {
149                    reader.restore(start);
150                    return Err(reader.error(CommandSyntaxErrorKind::FloatTooHigh {
151                        found: value,
152                        maximum,
153                    }));
154                }
155                Ok(PrimitiveArgumentValue::Float(value))
156            }
157            Self::Double { minimum, maximum } => {
158                let start = reader.checkpoint();
159                let value = reader.read_double()?;
160                if value < minimum {
161                    reader.restore(start);
162                    return Err(reader.error(CommandSyntaxErrorKind::DoubleTooLow {
163                        found: value,
164                        minimum,
165                    }));
166                }
167                if value > maximum {
168                    reader.restore(start);
169                    return Err(reader.error(CommandSyntaxErrorKind::DoubleTooHigh {
170                        found: value,
171                        maximum,
172                    }));
173                }
174                Ok(PrimitiveArgumentValue::Double(value))
175            }
176            Self::String(StringType::Word) => Ok(PrimitiveArgumentValue::String(
177                reader.read_unquoted_string().into(),
178            )),
179            Self::String(StringType::QuotablePhrase) => reader
180                .read_string()
181                .map(String::into_boxed_str)
182                .map(PrimitiveArgumentValue::String),
183            Self::String(StringType::GreedyPhrase) => Ok(PrimitiveArgumentValue::String(
184                reader.read_remaining().into(),
185            )),
186        }
187    }
188
189    pub(crate) fn suggest(&self, builder: &mut SuggestionsBuilder<'_>) {
190        if *self != Self::Bool {
191            return;
192        }
193
194        let remaining = builder.remaining_lowercase();
195        let suggest_true = "true".starts_with(remaining);
196        let suggest_false = "false".starts_with(remaining);
197        if suggest_true {
198            builder.suggest("true");
199        }
200        if suggest_false {
201            builder.suggest("false");
202        }
203    }
204}
205
206impl<S> CommandArgumentParser<S> for ArgumentType {
207    type Value = PrimitiveArgumentValue;
208
209    fn parse(
210        &self,
211        reader: &mut StringReader<'_>,
212        _source: &S,
213    ) -> Result<Self::Value, CommandSyntaxError> {
214        self.parse_value(reader)
215    }
216
217    fn list_suggestions(
218        &self,
219        _context: &ArgumentSuggestionContext<'_, S, Self::Value>,
220        builder: &mut SuggestionsBuilder<'_>,
221    ) {
222        self.suggest(builder);
223    }
224}
225
226#[derive(Clone, Debug, PartialEq)]
227pub(crate) enum PrimitiveArgumentValue {
228    Bool(bool),
229    Integer(i32),
230    Long(i64),
231    Float(f32),
232    Double(f64),
233    String(Box<str>),
234}
235
236/// Provides primitive Brigadier accessors for a runtime's parsed value.
237pub(crate) trait ContainsPrimitiveArgumentValue {
238    /// Returns the primitive value when this runtime value contains one.
239    fn primitive_value(&self) -> Option<&PrimitiveArgumentValue>;
240}
241
242impl ContainsPrimitiveArgumentValue for PrimitiveArgumentValue {
243    fn primitive_value(&self) -> Option<&PrimitiveArgumentValue> {
244        Some(self)
245    }
246}