diff options
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | src/args.rs | 67 | ||||
-rw-r--r-- | src/lib.rs | 8 | ||||
-rw-r--r-- | src/tree.rs | 169 |
4 files changed, 165 insertions, 82 deletions
diff --git a/Cargo.toml b/Cargo.toml index c21b5d8..bbd897b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,6 @@ homepage = "https://soniex2.github.io/ganarchy/project/139eede493cff4b24a253d2e7 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +#ordered-float = "2.0" +#qcell = { version = "0.5", default-features = false, features = ["alloc"] } +anycast = "1.0" diff --git a/src/args.rs b/src/args.rs index 5b9ce72..e7d76c3 100644 --- a/src/args.rs +++ b/src/args.rs @@ -10,6 +10,7 @@ use ::std::borrow::Cow; use ::std::fmt::Display; use ::std::fmt::Formatter; use ::std::future::Future; +use ::std::hash::Hash; use ::std::io::Cursor; use ::std::marker::PhantomData; use ::std::num::ParseFloatError; @@ -19,6 +20,9 @@ use ::std::ops::RangeBounds; use ::std::pin::Pin; use ::std::str::FromStr; +use anycast::Anycast; +//use ::ordered_float::OrderedFloat; + use crate::error::RangeError; use crate::error::ReadError; use crate::strcursor::StringReader; @@ -27,7 +31,7 @@ use crate::suggestion::SuggestionsBuilder; // FIXME delete when implemented /// The parsing context of a command. -pub struct CommandContext<'i, S, E>(::std::marker::PhantomData<(&'i str, S, E)>); +pub struct CommandContext<'i, S, E>(PhantomData<(&'i str, S, E)>); /// An argument parser/validator. /// @@ -36,8 +40,8 @@ pub struct CommandContext<'i, S, E>(::std::marker::PhantomData<(&'i str, S, E)>) /// this trait. Nevertheless, Iosonism doesn't itself use threads, so a /// [workaround] can be used if one needs non-`Send + Sync` argument types. /// -/// Additionally, argument types must be `Display`. This *is* reflected in this -/// trait. +/// Additionally, argument types must be `Display`, `Eq` and `Hash`. This *is* +/// reflected in this trait. /// /// [workaround]: https://users.rust-lang.org/t/how-to-check-send-at-runtime-similar-to-how-refcell-checks-borrowing-at-runtime/68269 /// @@ -78,7 +82,7 @@ pub struct CommandContext<'i, S, E>(::std::marker::PhantomData<(&'i str, S, E)>) /// } /// } /// ``` -pub trait ArgumentType<S, E>: Display { +pub trait ArgumentType<S, E>: Display + Eq + ::std::hash::Hash { /// The parsed type of the argument. type Result: Sized + 'static + Any; @@ -105,8 +109,8 @@ pub trait ArgumentType<S, E>: Display { } } -/// Internal wrapper around `ArgumentType`, but with `Any`. -pub(crate) trait ArgumentTypeAny<S, E>: Send + Sync + Display { +/// Internal wrapper around `ArgumentType`, but with more `Any`. +pub(crate) trait ArgumentTypeAny<S, E>: Send + Sync + Display + Anycast { /// Parses an argument of this type, returning the parsed argument. fn parse<'i>( &self, @@ -122,11 +126,18 @@ pub(crate) trait ArgumentTypeAny<S, E>: Send + Sync + Display { /// Returns examples for this argument. fn get_examples(&self) -> Cow<'static, [&str]>; + + /// Compares this `ArgumentType` with another. + fn dyn_eq(&self, other: &dyn ArgumentTypeAny<S, E>) -> bool; + + /// Hashes this `ArgumentType`. + fn dyn_hash(&self, hasher: &mut dyn ::std::hash::Hasher); } -/// Any `ArgumentType` that is also `Send` and `Sync` is an `ArgumentTypeAny`. +/// Any `ArgumentType` that is also `Send`, `Sync`, `Eq` and `Hash` is an +/// `ArgumentTypeAny`. impl<T, S, E> ArgumentTypeAny<S, E> for T -where T: ArgumentType<S, E> + Send + Sync { +where T: ArgumentType<S, E> + Send + Sync + Eq + ::std::hash::Hash + Any { fn parse<'i>( &self, reader: &mut Cursor<&'i str>, @@ -145,6 +156,18 @@ where T: ArgumentType<S, E> + Send + Sync { fn get_examples(&self) -> Cow<'static, [&str]> { self.get_examples() } + + fn dyn_eq(&self, other: &dyn ArgumentTypeAny<S, E>) -> bool { + match other.any_ref().downcast_ref() { + Some(other) => self == other, + None => false, + } + } + + fn dyn_hash(&self, mut hasher: &mut dyn ::std::hash::Hasher) { + // rust-lang/rust#44015 + self.hash(&mut hasher) + } } /// Any `dyn ArgumentTypeAny` (note the `dyn`!) is an `ArgumentType`. @@ -171,6 +194,21 @@ impl<S, E> ArgumentType<S, E> for dyn ArgumentTypeAny<S, E> { } } +impl<S, E> PartialEq for dyn ArgumentTypeAny<S, E> { + fn eq(&self, other: &Self) -> bool { + self.dyn_eq(other) + } +} + +impl<S, E> Eq for dyn ArgumentTypeAny<S, E> { +} + +impl<S, E> ::std::hash::Hash for dyn ArgumentTypeAny<S, E> { + fn hash<H: ::std::hash::Hasher>(&self, hasher: &mut H) { + self.dyn_hash(hasher as &mut dyn ::std::hash::Hasher) + } +} + /// A boolean argument. // FIXME add examples/expand docs #[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash, Default)] @@ -270,8 +308,8 @@ impl<S, E, T, R> ArgumentType<S, E> for IntegerArgumentType<T, R> where for<'i> E: ReadError<'i, Cursor<&'i str>>, for<'i> E: RangeError<'i, Cursor<&'i str>, T, R>, - R: RangeBounds<T>, - T: PartialOrd<T> + FromStr<Err=ParseIntError> + Any + Display, + R: RangeBounds<T> + Eq + Hash, + T: PartialOrd<T> + FromStr<Err=ParseIntError> + Any + Display + Eq + Hash, { /// An `IntegerArgumentType` parses an integer type. type Result = T; @@ -299,8 +337,8 @@ where /// Formats this `IntegerArgumentType`. /// -/// The resulting string follows the syntax `"integer(start,end)"`, with `start` -/// and `end` being one of the below: +/// The resulting string follows the syntax `"integer(start,end)"`, with +/// `start` and `end` being one of the below: /// /// - `value` if the bound is inclusive. /// - `value*` if the bound is exclusive. @@ -328,6 +366,7 @@ impl<T: Display, R: RangeBounds<T>> Display for IntegerArgumentType<T, R> { } } +// FIXME implement Eq and Hash properly /// A float argument. #[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash, Default)] pub struct FloatArgumentType<T, R: RangeBounds<T>> { @@ -376,8 +415,8 @@ impl<S, E, T, R> ArgumentType<S, E> for FloatArgumentType<T, R> where for<'i> E: ReadError<'i, Cursor<&'i str>>, for<'i> E: RangeError<'i, Cursor<&'i str>, T, R>, - R: RangeBounds<T>, - T: PartialOrd<T> + FromStr<Err=ParseFloatError> + Any + Display, + R: RangeBounds<T> + Eq + Hash, + T: PartialOrd<T> + FromStr<Err=ParseFloatError> + Any + Display + Eq + Hash, { /// A `FloatArgumentType` parses a float type. type Result = T; diff --git a/src/lib.rs b/src/lib.rs index 1895244..116add0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,8 @@ pub mod strcursor; pub mod suggestion; pub mod tree; +use ::std::sync::Arc; + use crate::args::CommandContext; /// Type of a command handler. @@ -34,8 +36,10 @@ use crate::args::CommandContext; /// - `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 +pub type Command<Type, Source, Error> = Arc< + dyn for<'a, 'i> Fn( + &'a CommandContext<'i, Source, Error> + ) -> Result<Type, Error> + Send + Sync >; #[cfg(test)] diff --git a/src/tree.rs b/src/tree.rs index 71243be..ae408a8 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -6,100 +6,137 @@ //! Command syntax tree. use ::std::borrow::Cow; +use ::std::collections::HashMap; +use ::std::collections::HashSet; +use ::std::hash::Hash; +use ::std::sync::Arc; use crate::Command; use crate::args::{ArgumentType, ArgumentTypeAny}; use crate::strcursor::StringReader; -// FIXME +// FIXME move use crate::args::CommandContext; /// The kind of node. -enum NodeKind<S, E> { +enum NodeKind<'a, Source, Error> { /// Root node. Root, /// Literal node. Literal { /// The literal of the node. - literal: Cow<'static, str>, + literal: Cow<'a, str>, }, /// Argument node. Argument { /// The label of the node. - label: Cow<'static, str>, + label: Cow<'a, str>, /// The argument type. - arg: Box<dyn ArgumentTypeAny<S, E>>, + arg: Arc<dyn ArgumentTypeAny<Source, Error>>, }, } -/// A node in the syntax tree. -pub struct CommandNode<T, S, E> { +impl<'a, Source, Error> Clone for NodeKind<'a, Source, Error> { + fn clone(&self) -> Self { + match self { + &Self::Root => Self::Root, + &Self::Literal { + ref literal, + } => Self::Literal { + literal: Cow::clone(literal), + }, + &Self::Argument { + ref label, + ref arg, + } => Self::Argument { + label: Cow::clone(label), + arg: Arc::clone(arg), + }, + } + } +} + +/// A node of the command parse graph. +pub struct CommandNode<'a, Type, Source, Error> { /// The command to run for this node. - command: Option<Command<T, S, E>>, + command: Option<Command<Type, Source, Error>>, /// Nodes. - nodes: (), + nodes: HashMap<Cow<'a, str>, Cow<'a, CommandNode<'a, Type, Source, Error>>>, /// Literal nodes. - literals: (), + literals: HashSet<Cow<'a, str>>, /// Argument nodes. - arguments: (), + arguments: HashSet<Cow<'a, str>>, /// The kind of node. - kind: NodeKind<S, E>, + kind: NodeKind<'a, Source, Error>, } -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, - }, +impl<'a, Type, Source, Error> Clone for CommandNode<'a, Type, Source, Error> { + fn clone(&self) -> Self { + Self { + command: self.command.clone(), + nodes: self.nodes.clone(), + literals: self.literals.clone(), + arguments: self.arguments.clone(), + kind: self.kind.clone(), } } +} - /// Creates a new argument node. - pub fn argument<A: ArgumentType<S, E> + 'static + Send + Sync>( - 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, - } - } -} +//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 + Send + Sync + Eq + Hash>( +// 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, +// } +// } +//} |