Skip to main content

steel_core/command/brigadier/
context.rs

1//! Branch-local command parse state.
2
3use super::{
4    BrigadierRuntime, CommandRuntime, CommandSyntaxError, ContainsPrimitiveArgumentValue, NodeId,
5    PrimitiveArgumentValue, StringRange, StringReader, node::CommandRedirect,
6};
7use crate::command::{incorrectly_typed_argument, missing_argument};
8use std::sync::Arc;
9
10#[derive(Clone, Debug, PartialEq)]
11struct ParsedArgument<V> {
12    range: StringRange,
13    value: V,
14}
15
16#[derive(Clone, Debug, PartialEq)]
17struct ParsedArguments<V> {
18    values: Vec<(Box<str>, ParsedArgument<V>)>,
19}
20
21impl<V> Default for ParsedArguments<V> {
22    fn default() -> Self {
23        Self { values: Vec::new() }
24    }
25}
26
27impl<V> ParsedArguments<V> {
28    fn insert(&mut self, name: &str, range: StringRange, value: V) {
29        let argument = ParsedArgument { range, value };
30        if let Some((_, existing)) = self
31            .values
32            .iter_mut()
33            .find(|(existing_name, _)| existing_name.as_ref() == name)
34        {
35            *existing = argument;
36        } else {
37            self.values.push((name.into(), argument));
38        }
39    }
40
41    fn argument(&self, name: &str) -> Result<&V, CommandSyntaxError> {
42        self.values
43            .iter()
44            .find(|(argument_name, _)| argument_name.as_ref() == name)
45            .map(|(_, argument)| &argument.value)
46            .ok_or_else(|| missing_argument(name))
47    }
48}
49
50macro_rules! impl_get_primitive_argument_value {
51    ($name:ident, $ty:ty, $argument_value:ident) => {
52        fn $name(&self, name: &str) -> Result<$ty, CommandSyntaxError> {
53            let PrimitiveArgumentValue::$argument_value(value) =
54                self.argument(name)?.primitive_value(name)?
55            else {
56                return Err(incorrectly_typed_argument(name));
57            };
58            Ok(*value)
59        }
60    };
61}
62
63impl<V> ParsedArguments<V>
64where
65    V: ContainsPrimitiveArgumentValue,
66{
67    impl_get_primitive_argument_value!(boolean, bool, Bool);
68    impl_get_primitive_argument_value!(integer, i32, Integer);
69    impl_get_primitive_argument_value!(long, i64, Long);
70    impl_get_primitive_argument_value!(float, f32, Float);
71    impl_get_primitive_argument_value!(double, f64, Double);
72
73    fn string(&self, name: &str) -> Result<&str, CommandSyntaxError> {
74        let PrimitiveArgumentValue::String(value) = self.argument(name)?.primitive_value(name)?
75        else {
76            return Err(incorrectly_typed_argument(name));
77        };
78        Ok(value)
79    }
80}
81
82/// Parsed state available while an argument provides completions.
83pub(crate) struct ArgumentSuggestionContext<'context, S, V> {
84    source: &'context S,
85    arguments: &'context ParsedArguments<V>,
86}
87
88impl<'context, S, V> ArgumentSuggestionContext<'context, S, V> {
89    const fn new(source: &'context S, arguments: &'context ParsedArguments<V>) -> Self {
90        Self { source, arguments }
91    }
92
93    /// Returns the source requesting suggestions.
94    pub(crate) const fn source(&self) -> &S {
95        self.source
96    }
97
98    /// Returns a previously parsed argument from this context segment.
99    pub(crate) fn argument(&self, name: &str) -> Result<&V, CommandSyntaxError> {
100        self.arguments.argument(name)
101    }
102}
103
104/// A command node and the input range it consumed.
105#[derive(Clone, Copy, Debug, PartialEq, Eq)]
106pub(crate) struct ParsedCommandNode {
107    node: NodeId,
108    range: StringRange,
109}
110
111impl ParsedCommandNode {
112    /// Returns the parsed graph node.
113    pub(crate) const fn node(self) -> NodeId {
114        self.node
115    }
116
117    /// Returns the UTF-16 input range consumed by the node.
118    pub(crate) const fn range(self) -> StringRange {
119        self.range
120    }
121}
122
123/// The successful portion of one command parse branch.
124pub(crate) struct ParsedCommandContext<S, R = BrigadierRuntime>
125where
126    R: CommandRuntime<S>,
127{
128    source: Arc<S>,
129    root: NodeId,
130    arguments: ParsedArguments<R::ArgumentValue>,
131    executor: Option<Arc<R::Executor>>,
132    nodes: Vec<ParsedCommandNode>,
133    range: StringRange,
134    child: Option<Box<Self>>,
135    modifier: Option<Arc<R::Modifier>>,
136    forks: bool,
137}
138
139impl<S, R> ParsedCommandContext<S, R>
140where
141    R: CommandRuntime<S>,
142{
143    pub(super) fn new(source: Arc<S>, root: NodeId, start: usize) -> Self {
144        Self {
145            source,
146            root,
147            arguments: ParsedArguments::default(),
148            executor: None,
149            nodes: Vec::new(),
150            range: StringRange::at(start),
151            child: None,
152            modifier: None,
153            forks: false,
154        }
155    }
156
157    pub(super) fn branch(&self) -> Self {
158        Self {
159            source: Arc::clone(&self.source),
160            root: self.root,
161            arguments: self.arguments.clone(),
162            executor: self.executor.as_ref().map(Arc::clone),
163            nodes: self.nodes.clone(),
164            range: self.range,
165            child: self.child.as_ref().map(|child| Box::new(child.branch())),
166            modifier: self.modifier.as_ref().map(Arc::clone),
167            forks: self.forks,
168        }
169    }
170
171    pub(super) fn source(&self) -> &S {
172        &self.source
173    }
174
175    pub(super) fn argument_suggestion_context(
176        &self,
177    ) -> ArgumentSuggestionContext<'_, S, R::ArgumentValue> {
178        ArgumentSuggestionContext::new(&self.source, &self.arguments)
179    }
180
181    pub(super) const fn root(&self) -> NodeId {
182        self.root
183    }
184
185    pub(super) const fn source_arc(&self) -> &Arc<S> {
186        &self.source
187    }
188
189    pub(super) fn set_executor(&mut self, executor: Option<Arc<R::Executor>>) {
190        self.executor = executor;
191    }
192
193    pub(super) fn with_node(
194        &mut self,
195        node: NodeId,
196        range: StringRange,
197        redirect: Option<&CommandRedirect<S, R>>,
198    ) {
199        self.nodes.push(ParsedCommandNode { node, range });
200        self.range = StringRange::encompassing(self.range, range);
201        self.modifier = redirect
202            .and_then(|redirect| redirect.modifier.as_ref())
203            .map(Arc::clone);
204        self.forks = redirect.is_some_and(|redirect| redirect.forks);
205    }
206
207    pub(super) fn with_argument(
208        &mut self,
209        name: &str,
210        range: StringRange,
211        value: R::ArgumentValue,
212    ) {
213        self.arguments.insert(name, range, value);
214    }
215
216    pub(super) fn set_child(&mut self, child: Self) {
217        self.child = Some(Box::new(child));
218    }
219
220    pub(super) fn build(self, input: Arc<str>) -> Arc<CommandContext<S, R>> {
221        let child = self.child.map(|child| child.build(Arc::clone(&input)));
222        Arc::new(CommandContext {
223            source: self.source,
224            input,
225            root: self.root,
226            arguments: Arc::new(self.arguments),
227            executor: self.executor,
228            nodes: self.nodes.into(),
229            range: self.range,
230            child,
231            modifier: self.modifier,
232            forks: self.forks,
233        })
234    }
235
236    pub(super) fn find_suggestion_context(
237        &self,
238        cursor: usize,
239    ) -> Option<SuggestionContext<'_, S, R>> {
240        if cursor < self.range.start() {
241            return None;
242        }
243
244        if self.range.end() < cursor {
245            if let Some(child) = &self.child {
246                return child.find_suggestion_context(cursor);
247            }
248            return self.nodes.last().map_or_else(
249                || {
250                    Some(SuggestionContext {
251                        parent: self.root,
252                        start: self.range.start(),
253                        context: self,
254                    })
255                },
256                |last| {
257                    Some(SuggestionContext {
258                        parent: last.node,
259                        start: last.range.end() + 1,
260                        context: self,
261                    })
262                },
263            );
264        }
265
266        let mut previous = self.root;
267        for node in &self.nodes {
268            if node.range.start() <= cursor && cursor <= node.range.end() {
269                return Some(SuggestionContext {
270                    parent: previous,
271                    start: node.range.start(),
272                    context: self,
273                });
274            }
275            previous = node.node;
276        }
277        Some(SuggestionContext {
278            parent: previous,
279            start: self.range.start(),
280            context: self,
281        })
282    }
283
284    /// Returns all nodes consumed by this parse segment.
285    pub(crate) fn nodes(&self) -> &[ParsedCommandNode] {
286        &self.nodes
287    }
288
289    /// Returns the range covered by this parse segment.
290    pub(crate) const fn range(&self) -> StringRange {
291        self.range
292    }
293
294    /// Returns whether the last parsed node has a command callback.
295    pub(crate) const fn is_executable(&self) -> bool {
296        self.executor.is_some()
297    }
298
299    /// Returns a parsed runtime argument by name.
300    pub(crate) fn argument(&self, name: &str) -> Result<&R::ArgumentValue, CommandSyntaxError> {
301        self.arguments.argument(name)
302    }
303
304    /// Returns the context reached through a redirect.
305    pub(crate) fn child(&self) -> Option<&Self> {
306        self.child.as_deref()
307    }
308}
309
310impl<S, R> ParsedCommandContext<S, R>
311where
312    R: CommandRuntime<S>,
313    R::ArgumentValue: ContainsPrimitiveArgumentValue,
314{
315    /// Returns a parsed boolean argument.
316    pub(crate) fn boolean(&self, name: &str) -> Result<bool, CommandSyntaxError> {
317        self.arguments.boolean(name)
318    }
319
320    /// Returns a parsed integer argument.
321    pub(crate) fn integer(&self, name: &str) -> Result<i32, CommandSyntaxError> {
322        self.arguments.integer(name)
323    }
324
325    /// Returns a parsed long argument.
326    pub(crate) fn long(&self, name: &str) -> Result<i64, CommandSyntaxError> {
327        self.arguments.long(name)
328    }
329
330    /// Returns a parsed float argument.
331    pub(crate) fn float(&self, name: &str) -> Result<f32, CommandSyntaxError> {
332        self.arguments.float(name)
333    }
334
335    /// Returns a parsed double argument.
336    pub(crate) fn double(&self, name: &str) -> Result<f64, CommandSyntaxError> {
337        self.arguments.double(name)
338    }
339
340    /// Returns a parsed string argument.
341    pub(crate) fn string(&self, name: &str) -> Result<&str, CommandSyntaxError> {
342        self.arguments.string(name)
343    }
344}
345
346/// Immutable parsed input supplied to commands and redirect modifiers.
347pub(crate) struct CommandContext<S, R = BrigadierRuntime>
348where
349    R: CommandRuntime<S>,
350{
351    source: Arc<S>,
352    input: Arc<str>,
353    root: NodeId,
354    arguments: Arc<ParsedArguments<R::ArgumentValue>>,
355    executor: Option<Arc<R::Executor>>,
356    nodes: Arc<[ParsedCommandNode]>,
357    range: StringRange,
358    child: Option<Arc<Self>>,
359    modifier: Option<Arc<R::Modifier>>,
360    forks: bool,
361}
362
363impl<S, R> CommandContext<S, R>
364where
365    R: CommandRuntime<S>,
366{
367    /// Returns the source used for this execution stage.
368    pub(crate) fn source(&self) -> &S {
369        &self.source
370    }
371
372    /// Returns the complete parsed command input.
373    pub(crate) fn input(&self) -> &str {
374        &self.input
375    }
376
377    /// Returns the root at which this context segment began parsing.
378    pub(crate) const fn root(&self) -> NodeId {
379        self.root
380    }
381
382    /// Returns all nodes consumed by this context segment.
383    pub(crate) fn nodes(&self) -> &[ParsedCommandNode] {
384        &self.nodes
385    }
386
387    /// Returns the range covered by this context segment.
388    pub(crate) const fn range(&self) -> StringRange {
389        self.range
390    }
391
392    /// Returns a parsed runtime argument by name.
393    pub(crate) fn argument(&self, name: &str) -> Result<&R::ArgumentValue, CommandSyntaxError> {
394        self.arguments.argument(name)
395    }
396
397    /// Returns the context reached through a redirect.
398    pub(crate) fn child(&self) -> Option<&Self> {
399        self.child.as_deref()
400    }
401
402    pub(super) const fn child_arc(&self) -> Option<&Arc<Self>> {
403        self.child.as_ref()
404    }
405
406    pub(crate) fn executor(&self) -> Option<&R::Executor> {
407        self.executor.as_deref()
408    }
409
410    pub(crate) fn modifier(&self) -> Option<&R::Modifier> {
411        self.modifier.as_deref()
412    }
413
414    pub(crate) const fn is_forked(&self) -> bool {
415        self.forks
416    }
417
418    pub(crate) fn copy_for(&self, source: Arc<S>) -> Self {
419        Self {
420            source,
421            input: Arc::clone(&self.input),
422            root: self.root,
423            arguments: Arc::clone(&self.arguments),
424            executor: self.executor.as_ref().map(Arc::clone),
425            nodes: Arc::clone(&self.nodes),
426            range: self.range,
427            child: self.child.as_ref().map(Arc::clone),
428            modifier: self.modifier.as_ref().map(Arc::clone),
429            forks: self.forks,
430        }
431    }
432
433    #[cfg(test)]
434    pub(super) fn empty(source: S, root: NodeId) -> Self {
435        Self {
436            source: Arc::new(source),
437            input: Arc::from(""),
438            root,
439            arguments: Arc::new(ParsedArguments::default()),
440            executor: None,
441            nodes: Arc::from([]),
442            range: StringRange::at(0),
443            child: None,
444            modifier: None,
445            forks: false,
446        }
447    }
448}
449
450impl<S, R> CommandContext<S, R>
451where
452    R: CommandRuntime<S>,
453    R::ArgumentValue: ContainsPrimitiveArgumentValue,
454{
455    /// Returns a parsed boolean argument.
456    pub(crate) fn boolean(&self, name: &str) -> Result<bool, CommandSyntaxError> {
457        self.arguments.boolean(name)
458    }
459
460    /// Returns a parsed integer argument.
461    pub(crate) fn integer(&self, name: &str) -> Result<i32, CommandSyntaxError> {
462        self.arguments.integer(name)
463    }
464
465    /// Returns a parsed long argument.
466    pub(crate) fn long(&self, name: &str) -> Result<i64, CommandSyntaxError> {
467        self.arguments.long(name)
468    }
469
470    /// Returns a parsed float argument.
471    pub(crate) fn float(&self, name: &str) -> Result<f32, CommandSyntaxError> {
472        self.arguments.float(name)
473    }
474
475    /// Returns a parsed double argument.
476    pub(crate) fn double(&self, name: &str) -> Result<f64, CommandSyntaxError> {
477        self.arguments.double(name)
478    }
479
480    /// Returns a parsed string argument.
481    pub(crate) fn string(&self, name: &str) -> Result<&str, CommandSyntaxError> {
482        self.arguments.string(name)
483    }
484}
485
486pub(super) struct SuggestionContext<'context, S, R>
487where
488    R: CommandRuntime<S>,
489{
490    pub(super) parent: NodeId,
491    pub(super) start: usize,
492    pub(super) context: &'context ParsedCommandContext<S, R>,
493}
494
495/// One failed candidate node from a command parse.
496#[derive(Clone, Debug, PartialEq)]
497pub(crate) struct ParseError {
498    node: NodeId,
499    error: CommandSyntaxError,
500}
501
502impl ParseError {
503    pub(super) const fn new(node: NodeId, error: CommandSyntaxError) -> Self {
504        Self { node, error }
505    }
506
507    /// Returns the candidate node that failed.
508    pub(crate) const fn node(&self) -> NodeId {
509        self.node
510    }
511
512    /// Returns the candidate's syntax error.
513    pub(crate) const fn error(&self) -> &CommandSyntaxError {
514        &self.error
515    }
516
517    pub(super) fn into_error(self) -> CommandSyntaxError {
518        self.error
519    }
520}
521
522/// The best branch produced by parsing a command input.
523pub(crate) struct ParseResults<'input, S, R = BrigadierRuntime>
524where
525    R: CommandRuntime<S>,
526{
527    context: ParsedCommandContext<S, R>,
528    reader: StringReader<'input>,
529    errors: Vec<ParseError>,
530}
531
532impl<'input, S, R> ParseResults<'input, S, R>
533where
534    R: CommandRuntime<S>,
535{
536    pub(super) const fn new(
537        context: ParsedCommandContext<S, R>,
538        reader: StringReader<'input>,
539        errors: Vec<ParseError>,
540    ) -> Self {
541        Self {
542            context,
543            reader,
544            errors,
545        }
546    }
547
548    /// Returns the reader positioned where this branch stopped.
549    pub(crate) const fn reader(&self) -> &StringReader<'input> {
550        &self.reader
551    }
552
553    /// Returns the successfully parsed context.
554    pub(crate) const fn context(&self) -> &ParsedCommandContext<S, R> {
555        &self.context
556    }
557
558    /// Returns candidate errors from the stopping position.
559    pub(crate) fn errors(&self) -> &[ParseError] {
560        &self.errors
561    }
562
563    pub(super) fn into_parts(
564        self,
565    ) -> (
566        ParsedCommandContext<S, R>,
567        StringReader<'input>,
568        Vec<ParseError>,
569    ) {
570        (self.context, self.reader, self.errors)
571    }
572}