Skip to main content

steel_core/command/brigadier/
context.rs

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