summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--src/args.rs73
-rw-r--r--src/lib.rs14
-rw-r--r--src/tree.rs105
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,
+        }
+    }
+}