diff options
-rw-r--r-- | src/args.rs | 73 | ||||
-rw-r--r-- | src/lib.rs | 14 | ||||
-rw-r--r-- | src/tree.rs | 105 |
3 files changed, 180 insertions, 12 deletions
diff --git a/src/args.rs b/src/args.rs index 7e84b48..2b8eecf 100644 --- a/src/args.rs +++ b/src/args.rs @@ -5,7 +5,9 @@ //! Argument processing. +use ::std::any::Any; use ::std::future::Future; +use ::std::io::Cursor; use ::std::pin::Pin; use crate::strcursor::StringReader; @@ -13,7 +15,8 @@ use crate::suggestion::Suggestions; use crate::suggestion::SuggestionsBuilder; // FIXME delete when implemented -pub struct CommandContext<'a, T>(::std::marker::PhantomData<(&'a str, T)>); +/// The parsing context of a command. +pub struct CommandContext<'i, S, E>(::std::marker::PhantomData<(&'i str, S, E)>); /// An argument parser. /// @@ -29,33 +32,39 @@ pub struct CommandContext<'a, T>(::std::marker::PhantomData<(&'a str, T)>); /// A very basic `bool` argument type: /// /// ``` +/// use ::std::io::Cursor; +/// /// use ::iosonism::args::ArgumentType; /// use ::iosonism::strcursor::{ReadError, StringReader}; /// /// struct BoolArgumentType; /// -/// impl<'i, R, S, E> ArgumentType<'i, R, S, E> for BoolArgumentType -/// where R: StringReader<'i>, E: ReadError<'i, R> { +/// impl<S, E> ArgumentType<S, E> for BoolArgumentType +/// where for<'i> E: ReadError<'i, Cursor<&'i str>> +/// { /// type Result = bool; -/// fn parse(&self, reader: &mut R) -> Result<bool, E> { +/// fn parse<'i>( +/// &self, +/// reader: &mut Cursor<&'i str>, +/// ) -> Result<bool, E> where E: 'i { /// reader.read_bool() /// } /// } /// ``` -pub trait ArgumentType<'i, R: StringReader<'i>, S, E> { +pub trait ArgumentType<S, E> { /// The parsed type of the argument. - type Result: Sized + 'static + ::std::any::Any; + type Result: Sized + 'static + Any; /// Parses an argument of this type, returning the parsed argument. - fn parse(&self, reader: &mut R) -> Result<Self::Result, E>; + fn parse<'i>( + &self, + reader: &mut Cursor<&'i str>, + ) -> Result<Self::Result, E> where E: 'i; /// Creates suggestions for this argument. - // The hope here is that S is lightweight enough for one to clone into the - // closure. Unfortunately making it borrowable would be a pain. - // FIXME: this API doesn't look great, can we make it better somehow? - fn list_suggestions( + fn list_suggestions<'i>( &self, - context: &CommandContext<'i, S>, + context: &CommandContext<'i, S, E>, builder: SuggestionsBuilder<'i>, ) -> Pin<Box<dyn Future<Output=Suggestions> + Send + 'i>> { let _ = context; @@ -68,3 +77,43 @@ pub trait ArgumentType<'i, R: StringReader<'i>, S, E> { Vec::new() } } + +/// Wrapper around `ArgumentType`, but with `Any`. +pub(crate) trait ArgumentTypeAny<S, E> { + /// Parses an argument of this type, returning the parsed argument. + fn parse<'i>( + &self, + reader: &mut Cursor<&'i str>, + ) -> Result<Box<dyn Any>, E> where E: 'i; + + /// Creates suggestions for this argument. + fn list_suggestions<'i>( + &self, + context: &CommandContext<'i, S, E>, + builder: SuggestionsBuilder<'i>, + ) -> Pin<Box<dyn Future<Output=Suggestions> + Send + 'i>>; + + /// Returns examples for this argument. + fn get_examples(&self) -> Vec<&str>; +} + +impl<T: ArgumentType<S, E>, S, E> ArgumentTypeAny<S, E> for T { + fn parse<'i>( + &self, + reader: &mut Cursor<&'i str>, + ) -> Result<Box<dyn Any>, E> where E: 'i { + self.parse(reader).map(|x| Box::new(x) as _) + } + + fn list_suggestions<'i>( + &self, + context: &CommandContext<'i, S, E>, + builder: SuggestionsBuilder<'i>, + ) -> Pin<Box<dyn Future<Output=Suggestions> + Send + 'i>> { + self.list_suggestions(context, builder) + } + + fn get_examples(&self) -> Vec<&str> { + self.get_examples() + } +} diff --git a/src/lib.rs b/src/lib.rs index 1557709..6921bbb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,20 @@ pub mod strcursor; pub mod suggestion; pub mod args; +pub mod tree; + +use crate::args::CommandContext; + +/// Type of a command handler. +/// +/// # Type params +/// +/// - `T`: The type returned by the command. +/// - `S`: The source type accepted by this argument type. +/// - `E`: The error type accepted by this argument type. +pub type Command<T, S, E> = Box< + dyn for<'a, 'i> Fn(&'a CommandContext<'i, S, E>) -> Result<T, E> + Send + Sync +>; #[cfg(test)] mod tests { diff --git a/src/tree.rs b/src/tree.rs new file mode 100644 index 0000000..b534754 --- /dev/null +++ b/src/tree.rs @@ -0,0 +1,105 @@ +// Copyright (c) 2021 Soni L. +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +// Documentation and comments licensed under CC BY-SA 4.0. + +//! Command syntax tree. + +use ::std::borrow::Cow; + +use crate::Command; +use crate::args::{ArgumentType, ArgumentTypeAny}; +use crate::strcursor::StringReader; +// FIXME +use crate::args::CommandContext; + +/// The kind of node. +enum NodeKind<S, E> { + /// Root node. + Root, + /// Literal node. + Literal { + /// The literal of the node. + literal: Cow<'static, str>, + }, + /// Argument node. + Argument { + /// The label of the node. + label: Cow<'static, str>, + /// The argument type. + arg: Box<dyn ArgumentTypeAny<S, E>>, + }, +} + +/// A node in the syntax tree. +pub struct CommandNode<T, S, E> { + /// The command to run for this node. + command: Option<Command<T, S, E>>, + /// Nodes. + nodes: (), + /// Literal nodes. + literals: (), + /// Argument nodes. + arguments: (), + /// The kind of node. + kind: NodeKind<S, E>, +} + +impl<T, S, E> CommandNode<T, S, E> { + /// Creates a new root node. + pub fn root() -> Self { + CommandNode { + command: None, + nodes: (), + literals: (), + arguments: (), + kind: NodeKind::Root, + } + } + + /// Creates a new literal node. + pub fn literal( + word: Cow<'static, str>, + command: Option<Command<T, S, E>>, + ) -> Self { + CommandNode { + command: command, + nodes: (), + literals: (), + arguments: (), + kind: NodeKind::Literal { + literal: word, + }, + } + } + + /// Creates a new argument node. + pub fn argument<A: ArgumentType<S, E> + 'static>( + name: Cow<'static, str>, + command: Option<Command<T, S, E>>, + argument: A, + ) -> Self { + CommandNode { + command: command, + nodes: (), + literals: (), + arguments: (), + kind: NodeKind::Argument { + label: name, + arg: Box::new(argument), + }, + } + } + + /// Returns the name of this node. + /// + /// For literal nodes, this is the literal itself. For argument nodes, this + /// is the name of the argument. + pub fn get_name(&self) -> &str { + match self.kind { + NodeKind::Root => "", + NodeKind::Literal { ref literal, .. } => literal, + NodeKind::Argument { ref label, .. } => label, + } + } +} |