diff --git a/Cargo.toml b/Cargo.toml index 687bcb6..41b2fab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,19 +5,16 @@ authors = ["caelunshun "] edition = "2018" [dependencies] -lieutenant-macros = { path = "macros" } - -slab = "0.4" -smallvec = "1.4" -derivative = "2.1" +thiserror = "1.0" +anyhow = "1.0" +unicase = "2.6" [dev-dependencies] criterion = "0.3" -thiserror = "1.0" - -[[bench]] -name = "dispatcher" -harness = false [workspace] members = [".", "macros"] + +[[bench]] +name = "simple" +harness = false \ No newline at end of file diff --git a/benches/dispatcher.rs b/benches/dispatcher.rs deleted file mode 100644 index 2767789..0000000 --- a/benches/dispatcher.rs +++ /dev/null @@ -1,160 +0,0 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use lieutenant::{command, CommandDispatcher, Context}; -use thiserror::Error; - -#[derive(Debug, Error)] -enum Error { - #[error("failed to parse int")] - ParseInt, -} - -impl From for Error { - fn from(_: std::num::ParseIntError) -> Self { - Error::ParseInt - } -} - -impl From for Error { - fn from(_: std::convert::Infallible) -> Self { - panic!("Cannot fail") - } -} - -fn single_command(c: &mut Criterion) { - struct State; - impl Context for State { - type Error = Error; - type Ok = (); - } - #[command(usage = "command")] - fn command(_: &mut State) -> Result<(), Error> { - // thread::sleep(time::Duration::from_millis(1)); - Ok(()) - } - - let mut dispatcher = CommandDispatcher::default(); - dispatcher.register(command).unwrap(); - - c.bench_function("dispatch single command", |b| { - b.iter(|| { - assert!(dispatcher - .dispatch(&mut State, black_box("command")) - .is_ok()); - }) - }); -} - -fn single_command_prallel(_c: &mut Criterion) { - // use std::thread; - // use thread_local::ThreadLocal; - // use futures::future; - // use std::cell::RefCell; - // use std::sync::Arc; - // use std::time::Duration; - // use smol::Task; - - // for _ in 0..2 { - // // A pending future is one that simply yields forever. - // thread::spawn(|| smol::run(future::pending::<()>())); - // } - - // #[derive(Clone, Copy)] - // struct State; - // impl Context for State { - // type Error = Error; - // type Ok = (); - // } - // #[command(usage = "command")] - // fn command_1(_: &mut State) -> Result<(), Error> { - // smol::Timer::after(Duration::from_secs(1)).await; - // Ok(()) - // } - - // let mut dispatcher = CommandDispatcher::default(); - // dispatcher.register(command_1).unwrap(); - // let dispatcher = Arc::new(dispatcher); - - // let nodes = Arc::new(ThreadLocal::new()); - // let errors = Arc::new(ThreadLocal::new()); - - // c.bench_function("paralel dispatching with a single command", |b| { - // b.iter(|| { - // let nodes = Arc::clone(&nodes); - // let errors = Arc::clone(&errors); - // let dispatcher = Arc::clone(&dispatcher); - - // Task::spawn(async move { - // let nodes: &RefCell> = nodes.get_or_default(); - // let errors: &RefCell> = errors.get_or_default(); - // dispatcher.dispatch(&mut *nodes.borrow_mut(), &mut *errors.borrow_mut(), &mut State, "command").await; - // }).detach(); - // }) - // }); -} - -fn multiple_commands(c: &mut Criterion) { - struct State; - impl Context for State { - type Error = Error; - type Ok = (); - } - #[command(usage = "command")] - fn command_1(_state: &mut State) -> Result<(), Error> { - // thread::sleep(time::Duration::from_millis(1)); - Ok(()) - } - - #[command(usage = "command ")] - fn command_2(_state: &mut State, _a: i32) -> Result<(), Error> { - // thread::sleep(time::Duration::from_millis(1)); - Ok(()) - } - - #[command(usage = "command ")] - fn command_3(_state: &mut State, _a: i32, _b: String) -> Result<(), Error> { - // thread::sleep(time::Duration::from_millis(1)); - Ok(()) - } - - #[command(usage = "command ")] - fn command_4(_state: &mut State, _a: String, _b: String) -> Result<(), Error> { - // thread::sleep(time::Duration::from_millis(1)); - Ok(()) - } - - #[command(usage = "command ")] - fn command_5(_state: &mut State, _a: i32, _b: i32, _c: i32) -> Result<(), Error> { - // thread::sleep(time::Duration::from_millis(1)); - Ok(()) - } - - let dispatcher = CommandDispatcher::default() - .with(command_1) - .with(command_2) - .with(command_3) - .with(command_4) - .with(command_5); - - c.bench_function("dispatch multiple commands", |b| { - b.iter(|| { - assert!(dispatcher.dispatch(&mut State, "command").is_ok()); - assert!(dispatcher.dispatch(&mut State, "command 4").is_ok()); - assert!(dispatcher.dispatch(&mut State, "command 4 hello").is_ok()); - assert!(dispatcher - .dispatch(&mut State, "command hello hello") - .is_ok()); - assert!(dispatcher.dispatch(&mut State, "command 4 4 4").is_ok()); - assert!(dispatcher.dispatch(&mut State, "command a a a").is_err()); - }) - }); -} - -criterion_group!(single_command_bench, single_command); -criterion_group!(single_command_parallel_bench, single_command_prallel); -criterion_group!(multiple_commands_bench, multiple_commands); - -criterion_main!( - single_command_bench, - single_command_parallel_bench, - multiple_commands_bench -); diff --git a/benches/simple.rs b/benches/simple.rs new file mode 100644 index 0000000..96649b3 --- /dev/null +++ b/benches/simple.rs @@ -0,0 +1,16 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +fn fibonacci(n: u64) -> u64 { + match n { + 0 => 1, + 1 => 1, + n => fibonacci(n-1) + fibonacci(n-2), + } +} + +fn bench_fib(c: &mut Criterion) { + c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20)))); +} + +criterion_group!(benches, bench_fib); +criterion_main!(benches); \ No newline at end of file diff --git a/macros/src/command.rs b/macros/src/command.rs deleted file mode 100644 index 2c56c0f..0000000 --- a/macros/src/command.rs +++ /dev/null @@ -1,443 +0,0 @@ -use darling::ast::GenericParamExt; -use darling::FromMeta; -use proc_macro2::{Ident, Span, TokenStream}; -use proc_macro_error::*; -use quote::quote; -use std::collections::HashMap; -use syn::spanned::Spanned; -use syn::{parse_macro_input, AttributeArgs, Block, FnArg, ItemFn, Pat, PatType, Type}; - -#[derive(Debug, FromMeta)] -struct Args { - usage: String, - #[darling(default)] - description: Option, - #[darling(default)] - priority: usize, -} - -#[derive(Debug)] -struct Usage { - arguments: Vec, -} - -#[derive(Debug)] -enum Argument { - Parameter { name: String, priority: usize }, - OptionalParameter { name: String, priority: usize }, - Literal { values: Vec }, -} - -/// The set of function parameters which should be obtained -/// through providers. -struct ProvidedParameters<'a> { - /// Mapping from parameter ident => `Provideable` type - map: HashMap<&'a Pat, &'a Type>, -} - -pub fn command( - args: proc_macro::TokenStream, - input: proc_macro::TokenStream, -) -> proc_macro::TokenStream { - let attr_args = parse_macro_input!(args as AttributeArgs); - let input = parse_macro_input!(input as ItemFn); - - let args: Args = match Args::from_list(&attr_args) { - Ok(args) => args, - Err(e) => abort_call_site!("invalid parameters passed to #[command]: {}", e; - help = "correct parameters: #[command(usage = \"/command \")]"; - ), - }; - - if let Some(asyncness) = input.sig.asyncness { - emit_error!(asyncness.span(), "command function may not be `async`"); - } - - if let Some(first_generic) = input.sig.generics.params.iter().next() { - let help = first_generic - .as_type_param() - .map(|type_param| format!("remove the parameter {}", type_param.ident)); - emit_error!( - first_generic.span(), "command functions may not have generic parameters"; - - help =? help; - ); - } - - let usage = parse_usage(&args.usage); - let parameters = collect_parameters(&usage, &input.sig.inputs.iter()); - let provided_parameters = detect_provided_parameters(&input, ¶meters); - - let ctx_type = detect_context_type(¶meters, input.sig.inputs.iter().next()); - - let command_ident = &input.sig.ident; - - let impl_header = if let Some((ctx_type, _)) = ctx_type { - quote! { - impl lieutenant::Command<#ctx_type> for #command_ident - } - } else { - quote! { - impl lieutenant::Command for #command_ident - } - }; - - let ctx_actual_type = if let Some((ty, _)) = ctx_type { - quote! { #ty } - } else { - quote! { C } - }; - - let command_spec = generate_command_spec( - &usage, - args.description, - ¶meters, - ctx_type, - &input.block, - &provided_parameters, - ); - let visibility = &input.vis; - - let tokens = quote! { - #[allow(non_camel_case_types)] - #visibility struct #command_ident; - - #impl_header { - fn build(self) -> lieutenant::CommandSpec<#ctx_actual_type> { - #command_spec - } - } - }; - tokens.into() -} - -fn parse_usage(usage: &str) -> Usage { - let mut arguments = vec![]; - - // Parse arguments by spaces. Each space-separared - // string can have one of the following meanings: - // : a required, named parameter - // [string]: an optional, named parameter - // literal|literal2...: one or more possible literal parameters - for splitted in usage.split(' ') { - let (first, middle) = splitted.split_at(1.min(splitted.len())); - let (middle, last) = middle.split_at(middle.len() - 1); - match (first, middle, last) { - ("<", param, ">") => arguments.push(Argument::Parameter { - name: param.to_owned(), - priority: 0, - }), - ("[", param, "]") => arguments.push(Argument::OptionalParameter { - name: param.to_owned(), - priority: 0, - }), - (_, _, _) => { - // Parse literals: individual values are separated by the pipe operator. - let values = splitted.split('|').map(String::from).collect::>(); - arguments.push(Argument::Literal { values }); - } - } - } - - Usage { arguments } -} - -fn collect_parameters<'a>( - usage: &Usage, - inputs: &(impl Iterator + Clone), -) -> Vec<&'a PatType> { - let mut parameters = vec![]; - for arg in &usage.arguments { - match arg { - Argument::Parameter { name, .. } | Argument::OptionalParameter { name, .. } => { - collect_parameter(name, &mut parameters, arg, inputs); - } - Argument::Literal { .. } => (), - } - } - - parameters -} - -fn collect_parameter<'a>( - name: &str, - parameters: &mut Vec<&'a PatType>, - arg: &Argument, - inputs: &(impl Iterator + Clone), -) { - // check that there is a corresponding parameter to the function - let arg_type = if let Some(arg_type) = find_corresponding_arg(name, inputs) { - arg_type - } else { - emit_call_site_error!( - "no corresponding function parameter for command parameter {}", name; - - help = "add a parameter to the function: `{}: ", name; - ); - return; - }; - validate_parameter(name, arg, arg_type); - parameters.push(arg_type); -} - -fn validate_parameter(name: &str, arg: &Argument, arg_type: &PatType) { - // If not an optional parameter, ensure the type is not an option. - // Otherwise, ensure it _is_ an Option. - if let Argument::Parameter { .. } = arg { - // not optional - validate_argument_type(&arg_type.ty, name); - if let Type::Path(path) = arg_type.ty.as_ref() { - // verify that path is not an `Option` - if path.path.is_ident(&Ident::new("Option", Span::call_site())) { - emit_error!( - path.span(), "the parameter {} is defined as an `Option`, but the usage message indicates it is a required argument", name; - - help = "change the usage instructions to make the argument optional: `<{}>`", name; - ); - } - }; - } else { - // optional - } -} - -fn validate_argument_type(ty: &Type, name: &str) { - match ty { - Type::ImplTrait(span) => emit_error!( - span.span(), "command function may not take `impl Trait`-style parameters"; - - help = "change the type of the parameter {}", name; - ), - Type::Reference(reference) => { - if reference.lifetime.clone().map(|l| l.ident.to_string()) != Some("static".to_owned()) - { - emit_error!( - reference.span(), "command function may not take non-'static references as paramters"; - - hint = "use an owned value instead by removing the '&'"; - ); - } - } - _ => (), - } -} - -fn find_corresponding_arg<'a>( - name: &str, - args: &(impl Iterator + Clone), -) -> Option<&'a PatType> { - args.clone() - .find(|arg| { - let ident = match arg { - FnArg::Receiver(x) => { - emit_error!(x.span(), "command functions may not take `self` as a parameter"; - help = "remove the `self` parameter"; - ); - return false; - } - FnArg::Typed(ty) => match ty.pat.as_ref() { - Pat::Ident(ident) => &ident.ident, - pat => { - emit_error!(pat.span(), "invalid command parameter pattern"); - return false; - } - }, - }; - - possible_parameter_idents(name).contains(&ident.to_string()) - }) - .map(|arg| match arg { - FnArg::Typed(ty) => ty, - _ => unreachable!(), - }) -} - -fn possible_parameter_idents(name: &str) -> Vec { - vec![name.to_owned(), format!("_{}", name)] -} - -/// Determines which parameters need to be obtained -/// through providers. -fn detect_provided_parameters<'a>( - input: &'a ItemFn, - collected: &'a [&'a PatType], -) -> ProvidedParameters<'a> { - // Determine which function parameters - // are neither the context type - // nor are obtained through command arguments. - let mut map = HashMap::new(); - - // Skip first parameter; it's the context parameter. - for param in input.sig.inputs.iter().skip(1) { - let param = match param { - FnArg::Typed(typ) => typ, - _ => unreachable!(), - }; - - if collected.contains(¶m) { - continue; - } - - map.insert(param.pat.as_ref(), param.ty.as_ref()); - } - - ProvidedParameters { map } -} - -fn detect_context_type<'a>( - parameter_types: &[&PatType], - first_arg: Option<&'a FnArg>, -) -> Option<(&'a Type, &'a Pat)> { - first_arg - .map(|first_arg| { - let first_arg = match first_arg { - FnArg::Typed(arg) => arg, - _ => unreachable!(), - }; - - // check if any parameter types are this first argument - if parameter_types - .iter() - .any(|param| param.pat == first_arg.pat) - { - None - } else { - Some((first_arg.ty.as_ref(), first_arg.pat.as_ref())) - } - }) - .flatten() - .map(|(ty, pat)| { - let ty = match ty { - Type::Reference(reference) => &reference.elem, - x => abort!(x.span(), "context input must be a reference"; - - help = "change the type of the first function parameter to be a mutable reference"; - ), - }; - - (ty.as_ref(), pat) - }) -} - -fn generate_command_spec( - usage: &Usage, - description: Option, - parameters: &[&PatType], - ctx_type: Option<(&Type, &Pat)>, - block: &Block, - provided: &ProvidedParameters, -) -> TokenStream { - // let mut statements = vec![]; - - let ctx_param = match ctx_type { - Some((t, _)) => quote! { #t }, - None => quote! { C }, - }; - - let ctx_ident = match ctx_type { - Some((_, id)) => quote! { #id }, - None => quote! { __LIEUTENANT_CTX__ }, - }; - - let mut arguments = vec![]; - - let mut i = 0; - for argument in &usage.arguments { - let argument = match argument { - Argument::Parameter { name, priority } - | Argument::OptionalParameter { name, priority } => { - let argument_type = parameters[i]; - - let ty = &argument_type.ty; - i += 1; - - quote! { - lieutenant::Argument::Parser { - name: #name.into(), - satisfies: <#ty as lieutenant::ArgumentKind<#ctx_param>>::satisfies, - argument_type: std::any::TypeId::of::<#ty>(), - priority: #priority, - } - } - } - Argument::Literal { values } => { - quote! { - lieutenant::Argument::Literal { - values: [#(#values),*].iter().copied().map(std::borrow::Cow::from).collect(), - } - } - } - }; - - arguments.push(quote! { - arguments.push(#argument); - }); - } - - let mut parse_args = vec![]; - - let args_ident = Ident::new("__LIEUTENANT_ARGS__", Span::call_site()); - - // Add arguments to parse_args - let mut i = 0; - for argument in usage.arguments.iter() { - match argument { - Argument::Parameter { .. } | Argument::OptionalParameter { .. } => { - let parameter = parameters[i]; - let ident = ¶meter.pat; - let ty = ¶meter.ty; - let ctx_ident = match ctx_type { - Some((_, ident)) => quote! { #ident }, - None => quote! { _ctx }, - }; - - parse_args.push(quote! { - let #ident = <#ty as lieutenant::ArgumentKind<#ctx_param>>::parse(#ctx_ident, &mut #args_ident)?; - }); - - i += 1; - } - Argument::Literal { values } => parse_args.push(quote! { - let head = #args_ident.advance_until(" "); - debug_assert!([#(#values),*].contains(&head)); - }), - } - } - - // Add provided parameters to parse_args - for (provided_ident, provided_typ) in &provided.map { - parse_args.push(quote! { - let #provided_ident = <<#provided_typ as lieutenant::Provideable<#ctx_param>>::Provider - as std::default::Default>::default().provide(#ctx_ident)?; - }); - } - - let ctx_type = match ctx_type { - Some((t, _)) => quote! { #ctx_ident: &mut #t }, - None => quote! { #ctx_ident: &mut C }, - }; - - let description = match description { - Some(description) => quote! { Some(#description.into()) }, - None => quote! { None }, - }; - - let arguments_len = arguments.len(); - - let res = quote! { - let mut arguments = Vec::with_capacity(#arguments_len); - #(#arguments)* - - lieutenant::CommandSpec { - arguments, - description: #description, - exec: |#ctx_type, #args_ident| { - let mut #args_ident = lieutenant::Input::new(#args_ident); - use lieutenant::{ArgumentKind as _, Provider as _}; - #(#parse_args)* - #block - }, - } - }; - res -} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 5432664..8b13789 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,22 +1 @@ -use proc_macro_error::proc_macro_error; -mod command; -mod provider; - -#[proc_macro_error] -#[proc_macro_attribute] -pub fn command( - args: proc_macro::TokenStream, - input: proc_macro::TokenStream, -) -> proc_macro::TokenStream { - command::command(args, input) -} - -#[proc_macro_error] -#[proc_macro_attribute] -pub fn provider( - args: proc_macro::TokenStream, - input: proc_macro::TokenStream, -) -> proc_macro::TokenStream { - provider::provider(args, input) -} diff --git a/macros/src/provider.rs b/macros/src/provider.rs deleted file mode 100644 index 891807d..0000000 --- a/macros/src/provider.rs +++ /dev/null @@ -1,230 +0,0 @@ -use proc_macro2::TokenStream; -use proc_macro_error::*; -use quote::quote; -use syn::spanned::Spanned; -use syn::{parse_macro_input, GenericArgument, PathArguments, ReturnType, Type}; -use syn::{FnArg, ItemFn, Pat}; - -/// Detected context type. -#[derive(Copy, Clone)] -enum ContextType<'a> { - /// Concrete type - Known { - /// Type of the context - typ: &'a Type, - /// Ident of the context variable - ident: &'a Pat, - }, - /// Unknown - make the provider generic over contexts - Generic, -} - -/// Detected `Output` type. -#[derive(Clone)] -struct OutputType<'a> { - /// The Ok type - ok: &'a Type, - /// The error type - error: TokenStream, - /// Whether the error type is defined by the block - error_defined: bool, -} - -pub fn provider( - _args: proc_macro::TokenStream, - input: proc_macro::TokenStream, -) -> proc_macro::TokenStream { - let input = parse_macro_input!(input as ItemFn); - - verify_input(&input); - - let ctx_type = detect_context_type(&input); - let output_type = detect_output(&input); - - let tokens = generate_provider(&input, ctx_type, output_type); - tokens.into() -} - -/// Checks that the input function doesn't -/// have illegal conditions. -fn verify_input(input: &ItemFn) { - // no generics - if let Some(first_param) = input.sig.generics.params.iter().next() { - let span = first_param.span(); - - emit_error!( - span, "provider function may not have generic paramters"; - - help = "remove the generic parameter" - ); - } - - // must not be async - if let Some(asyncness) = input.sig.asyncness { - let span = asyncness.span(); - let name = &input.sig.ident; - - emit_error!( - span, "provider function must not be `async`"; - - help = "remove the `async` keyword: `fn {}`", name - ); - } -} - -/// Detects the context type of the input function. -fn detect_context_type(input: &ItemFn) -> ContextType { - // Context is first argument. - if let Some(first_arg) = input.sig.inputs.iter().next() { - let first_arg = match first_arg { - FnArg::Receiver(rec) => { - let span = rec.span(); - abort!( - span, "provider function may not take `self` parameter"; - - help = "remove the `self` parameter" - ); - } - FnArg::Typed(typ) => typ, - }; - - let typ = match first_arg.ty.as_ref() { - Type::Reference(r) => { - if let Some(mutability) = r.mutability { - let span = mutability.span(); - - emit_error!( - span, "provider context parameter cannot be mutable"; - - help = "remove the `mut`" - ); - } - - r.elem.as_ref() - } - typ => abort!( - typ.span(), - "provider context type must be an immutable reference" - ), - }; - - let ident = first_arg.pat.as_ref(); - - ContextType::Known { typ, ident } - } else { - // No first argument - context type can be any Context - ContextType::Generic - } -} - -/// Detects the provider's output type. -fn detect_output(input: &ItemFn) -> OutputType { - let ret = &input.sig.output; - - match ret { - ReturnType::Default => { - abort!(ret.span(), "provider must have a return type"; help = "add a return type for the type you wish to provide") - } - ReturnType::Type(_, typ) => { - // Extract the output type. - // If it's a Result, we know both Ok and Err. - // If it's not a Result, we assume Error is Infallible and - // Ok is the return type. - - match typ.as_ref() { - Type::Path(path) => { - let path = &path.path; - - let last_segment = path.segments.iter().last().unwrap(); - - if last_segment.ident == "Result" { - match &last_segment.arguments { - PathArguments::None | PathArguments::Parenthesized(_) => abort!( - last_segment.span(), - "provider `Result` return type must have Ok and Err variants" - ), - PathArguments::AngleBracketed(bracketed) => { - let mut iter = bracketed.args.iter(); - let ok = iter.next().unwrap(); - let error = iter.next().unwrap(); - - let (ok, error) = match (ok, error) { - (GenericArgument::Type(ok), GenericArgument::Type(error)) => { - (ok, error) - } - _ => abort!(ok.span(), "result must have two type parameters"), - }; - - OutputType { - ok, - error: quote! { #error }, - error_defined: true, - } - } - } - } else { - OutputType { - ok: typ, - error: quote! { std::convert::Infallible }, - error_defined: false, - } - } - } - typ => { - abort!(typ.span(), "invalid provider return type"; note = "expected concrete type, not a reference") - } - } - } - } -} - -fn generate_provider( - input: &ItemFn, - ctx_type: ContextType, - output_type: OutputType, -) -> TokenStream { - let ident = &input.sig.ident; - let block = &input.block; - let vis = &input.vis; - - let impl_head = match ctx_type { - ContextType::Known { typ, .. } => quote! { impl lieutenant::Provider<#typ> for #ident }, - ContextType::Generic => { - quote! { impl lieutenant::Provider for #ident where C: lieutenant::Context } - } - }; - - let ok = output_type.ok; - let error = &output_type.error; - - let (ctx_param_type, ctx_param_ident) = match ctx_type { - ContextType::Known { typ, ident } => (quote! { #typ }, quote! { #ident }), - ContextType::Generic => (quote! { C }, quote! { > }), - }; - - let convert_ret = if output_type.error_defined { - quote! { ret } - } else { - quote! { Ok(ret) } - }; - - let tokens = quote! { - #[allow(non_camel_case_types)] - #[derive(Default)] - #vis struct #ident; - - #impl_head { - type Output = #ok; - type Error = #error; - - fn provide(&self, #ctx_param_ident: &#ctx_param_type) -> std::result::Result { - let ret = { - #block - }; - - #convert_ret - } - } - }; - tokens -} diff --git a/src/command.rs b/src/command.rs index b8d1bfa..2c2b17e 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,109 +1,77 @@ -use crate::parser::SatisfiesFn; -use crate::Context; -use derivative::Derivative; -use smallvec::SmallVec; -use std::any::TypeId; -use std::borrow::Cow; -use std::cmp::Ordering; +use crate::generic::{Combine, Either, Func, FuncOnce, HList, Tuple}; +use crate::parser::Lazy; -/// A type which can be converted into a `CommandSpec`. -/// -/// This is automatically implemented for functions annotated -/// with the `command` proc macro. -pub trait Command { - /// Returns the spec of this command, which specifies - /// its arguments and executable function. - fn build(self) -> CommandSpec; +pub struct Ref(T); +pub struct RefMut(T); + +pub trait State { + // TODO handle option such to avoide unwrap + fn get(&self) -> Option; } -/// An argument to a command. -#[derive(Derivative)] -#[derivative(Clone(bound = ""))] -pub enum Argument { - /// A literal, matching some fixed string value. - /// - /// Multiple strings may match the same literal - /// argument, to allow for aliasing. - Literal { - /// The set of strings which match this literal. - values: SmallVec<[Cow<'static, str>; 2]>, - }, - /// A custom-parsed argument. - Parser { - /// Name of this argument. - name: Cow<'static, str>, - /// Priority of this argument. Greater priorities - /// take precedence over command nodes with lower - /// priorities. - priority: usize, - /// The function used to check whether - /// a given input matches this parser. - satisfies: SatisfiesFn, - /// Type ID of the argument type. - argument_type: TypeId, - }, +pub trait Command { + type Output; + type State; + + fn call(self, state: &Self::State) -> Self::Output; } -impl Argument { - /// Returns the priority of this argument node. - pub fn priority(&self) -> usize { +impl Command for Either +where + A: Command, + B: Command, +{ + type Output = A::Output; + type State = A::State; + + fn call(self, state: &Self::State) -> Self::Output { match self { - Argument::Literal { .. } => 0, - Argument::Parser { priority, .. } => *priority, + Either::A(a) => a.call(state), + Either::B(b) => b.call(state), } } } -impl PartialEq for Argument +pub struct CommandMapping { + arguments: A, + callback: F, +} + +impl Command for CommandMapping where - C: Context, + A: Lazy, + A::Output: Tuple, + F: Func, { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Argument::Literal { values: v1 }, Argument::Literal { values: v2 }) => v1 == v2, - ( - Argument::Parser { - argument_type: s1, .. - }, - Argument::Parser { - argument_type: a2, .. - }, - ) => s1 == a2, - _ => false, - } - } -} + type Output = F::Output; + type State = A::State; -impl Eq for Argument where C: Context {} + fn call(self, state: &Self::State) -> Self::Output { + let arguments = self.arguments.get(state); -impl PartialOrd for Argument { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) + // TODO handle option such to avoide unwrap + self.callback.call(arguments) } } -impl Ord for Argument { - fn cmp(&self, other: &Self) -> Ordering { - self.priority().cmp(&other.priority()) +impl CommandMapping { + pub fn new(arguments: A, callback: F) -> Self { + Self { + arguments, + callback, + } } } -pub type Exec = fn(&mut C, &str) -> Result<::Ok, ::Error>; - -/// Specifies the arguments to a command, -/// plus its metadata and executable function. -pub struct CommandSpec { - /// Argument nodes to this command. This is a list, - /// not a graph. - pub arguments: Vec>, - /// Description of this command, potentially nonexistent. - pub description: Option>, - /// THe function used to execute this command. - pub exec: Exec, -} +impl FuncOnce<&mut A::State> for CommandMapping +where + A: Lazy, + F: Func, +{ + type Output = F::Output; -impl Command for CommandSpec { - fn build(self) -> CommandSpec { - self + fn call(self, state: &mut A::State) -> Self::Output { + let realized_arguments = self.arguments.get(&state); + self.callback.call(realized_arguments) } } diff --git a/src/dispatcher.rs b/src/dispatcher.rs index 21a4266..3a29aca 100644 --- a/src/dispatcher.rs +++ b/src/dispatcher.rs @@ -1,215 +1,39 @@ -use crate::{command::Exec, Argument, Command, CommandSpec, Context, Input}; -use slab::Slab; -use smallvec::SmallVec; +use crate::parser::{Parser, ParserBase, Result}; +use crate::parsers::{Literals}; +use crate::{Command, State}; +use std::borrow::Cow; -#[derive(Debug)] -pub enum RegisterError { - /// Overlapping commands exist: two commands - /// have an executable node at the same point. - OverlappingCommands, - /// Attempted to register an executable command at the root of the command graph. - ExecutableRoot, +pub struct CommandDispatcher { + literals: Literals, } -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct NodeKey(usize); - -impl std::ops::Deref for NodeKey { - type Target = usize; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -/// Data structure used to dispatch commands. -pub struct CommandDispatcher { - // This structure acts as the root node. - /// Stores all nodes in the command graph. - nodes: Slab>, - /// Children of the root node. - children: SmallVec<[NodeKey; 4]>, - /// Vector of all commands registered to this dispatcher. - commands: Vec>, -} - -impl Default for CommandDispatcher { +impl<'a, E> Default for CommandDispatcher { fn default() -> Self { - Self { - nodes: Default::default(), - children: Default::default(), - commands: Default::default(), + CommandDispatcher { + literals: Default::default(), } } } -impl CommandDispatcher +impl CommandDispatcher<(E,)> where - C: Context, + E: Command, { - /// Creates a new `CommandDispatcher` with no registered commands. pub fn new() -> Self { - Self::default() - } - - /// Registers a command to this `CommandDispatcher`. - pub fn register(&mut self, command: impl Command) -> Result<(), RegisterError> - where - C: 'static, - { - let spec = command.build(); - - let mut arguments = spec.arguments.iter().peekable(); - - let mut node_key: Option = None; - - 'argument: while let Some(argument) = arguments.peek() { - let children = match node_key { - Some(key) => &self.nodes[*key].children, - None => &self.children, - }; - - for child_key in children { - let child = &self.nodes[**child_key]; - - if argument == &&child.argument { - arguments.next(); - node_key = Some(*child_key); - continue 'argument; - } - } - break; - } - - for argument in arguments { - let child = Node::from((*argument).clone()); - let child_key = NodeKey(self.nodes.insert(child)); - - if let Some(node_key) = node_key { - let node = &mut self.nodes[*node_key]; - node.children.push(child_key); - } else { - self.children.push(child_key); - } - - node_key = Some(child_key); - } - - if let Some(key) = node_key { - let node = &mut self.nodes[*key]; - node.execs.push(spec.exec.clone()); - } else { - // Command with zero arguments? - return Err(RegisterError::ExecutableRoot); + Self { + literals: Default::default(), } + } - self.commands.push(spec); - - Ok(()) + pub fn call(&self, state: &E::State, command: &str) -> Result { + Ok(self.literals.parse(&mut command.into())?.0.call(state)) } - /// Method-chaining function to register a command. - /// - /// # Panics - /// Panics if overlapping commands are detected. Use `register` - /// to handle this error. - pub fn with(mut self, command: impl Command) -> Self + pub fn register(&mut self, lit: L, command: C) where - C: 'static, + C: 'static + Parser, + L: Into>, { - self.register(command).unwrap(); - self - } - - /// Dispatches a command. Returns whether a command was executed. - pub fn dispatch(&self, ctx: &mut C, command: &str) -> Result> { - let mut nodes = Vec::new(); - let mut errors = Vec::new(); - - for child_key in &self.children { - nodes.push((Input::new(command), *child_key)); - } - - while let Some((mut input, node_key)) = nodes.pop() { - let node = &self.nodes[*node_key]; - let satisfies = match &node.argument { - Argument::Literal { values } => { - let parsed = input.advance_until(" "); - values.iter().any(|value| value == parsed) - } - Argument::Parser { satisfies, .. } => satisfies(ctx, &mut input), - }; - - if input.is_empty() && satisfies { - for exec in &node.execs { - match exec(ctx, command) { - Ok(ok) => return Ok(ok), - Err(err) => errors.push(err), - } - } - continue; - } - - if satisfies { - for child_key in &node.children { - nodes.push((input, *child_key)); - } - } - } - Err(errors) + self.literals.insert(lit, command.boxed()); } - - pub fn commands(&self) -> impl Iterator> { - self.commands.iter() - } -} - -/// Node on the command graph. -struct Node { - children: SmallVec<[NodeKey; 4]>, - argument: Argument, - execs: Vec>, -} - -impl From> for Node { - fn from(argument: Argument) -> Self { - Node { - children: Default::default(), - argument, - execs: Vec::new(), - } - } -} - -#[cfg(test)] -mod tests { - /*use super::*; - use bstr::B; - use smallvec::smallvec; - - #[test] - fn parse_into_arguments() { - let test: Vec<(&[u8], SmallVec<[&[u8]; 4]>)> = vec![ - ( - B("test 20 \"this is a string: \\\"Hello world\\\"\""), - smallvec![B("test"), B("20"), B("this is a string: \"Hello world\"")], - ), - ( - B("big inputs cost big programmers with big skills"), - smallvec![ - B("big"), - B("inputs"), - B("cost"), - B("big"), - B("programmers"), - B("with"), - B("big"), - B("skills"), - ], - ), - ]; - - for (input, expected) in test { - assert_eq!(CommandDispatcher::parse_into_arguments(input), expected); - } - }*/ -} +} \ No newline at end of file diff --git a/src/generic.rs b/src/generic.rs new file mode 100644 index 0000000..f90633a --- /dev/null +++ b/src/generic.rs @@ -0,0 +1,262 @@ +#[derive(Debug)] +pub struct Product(pub(crate) H, pub(crate) T); + +#[derive(Debug)] +pub enum Either { + A(T), + B(U), +} + +impl Func for Either<(T,), (U,)> +where + T: Func, + U: Func, +{ + type Output = T::Output; + + fn call(&self, args: Args) -> Self::Output { + match self { + Either::A((a,)) => a.call(args), + Either::B((b,)) => b.call(args), + } + } +} + +impl FuncOnce for Either<(T,), (U,)> +where + T: FuncOnce, + U: FuncOnce, +{ + type Output = T::Output; + + fn call(self, args: Args) -> Self::Output { + match self { + Either::A((a,)) => a.call(args), + Either::B((b,)) => b.call(args), + } + } +} + +pub trait Lazy { + type State; + type Output: Tuple; + + fn get(self, state: &Self::State) -> Self::Output; +} + +pub trait HList: Sized { + type Tuple: Tuple; + + fn flatten(self) -> Self::Tuple; +} + +pub trait Tuple: Sized { + type HList: HList; + + fn hlist(self) -> Self::HList; +} + +pub trait Combine { + type Output: HList; + + fn combine(self, other: T) -> Self::Output; +} + +pub trait Func { + type Output; + + fn call(&self, args: Args) -> Self::Output; +} + +pub trait FuncOnce { + type Output; + + fn call(self, args: Args) -> Self::Output; +} + +impl Combine for () { + type Output = T; + + #[inline] + fn combine(self, other: T) -> Self::Output { + other + } +} + + +impl Combine for Product +where + T: Combine, + Product>::Output>: HList, +{ + type Output = Product>::Output>; + + #[inline] + fn combine(self, other: U) -> Self::Output { + Product(self.0, self.1.combine(other)) + } +} + +impl HList for () { + type Tuple = (); + + #[inline] + fn flatten(self) -> Self::Tuple {} +} + +impl Tuple for () { + type HList = (); + + #[inline] + fn hlist(self) -> Self::HList {} +} + +impl Func<()> for F +where + F: Fn() -> R, +{ + type Output = R; + + #[inline] + fn call(&self, _args: ()) -> Self::Output { + (*self)() + } +} + +macro_rules! product { + ($H:expr) => { Product($H, ()) }; + ($H:expr, $($T:expr),*) => { Product($H, product!($($T),*)) }; +} + +macro_rules! Product { + ($H:ty) => { Product<$H, ()> }; + ($H:ty, $($T:ty),*) => { Product<$H, Product!($($T),*)> }; +} + +macro_rules! product_pat { + ($H:pat) => { Product($H, ()) }; + ($H:pat, $($T:pat),*) => { Product($H, product_pat!($($T),*)) }; +} + +macro_rules! generics { + ($type:ident) => { + + + impl<$type> HList for Product!($type) { + type Tuple = ($type,); + + #[inline] + fn flatten(self) -> Self::Tuple { + (self.0,) + } + } + + impl<$type> Tuple for ($type,) { + type HList = Product!($type); + + #[inline] + fn hlist(self) -> Self::HList { + product!(self.0) + } + } + + impl Func for F + where + F: Fn($type) -> R, + { + type Output = R; + + #[inline] + fn call(&self, args: Product!($type)) -> Self::Output { + (*self)(args.0) + } + + } + + impl Func<($type,)> for F + where + F: Fn($type) -> R, + { + type Output = R; + + #[inline] + fn call(&self, args: ($type,)) -> Self::Output { + (*self)(args.0) + } + } + + }; + + ($type1:ident, $( $type:ident ),*) => { + generics!($( $type ),*); + + impl<$type1, $( $type ),*> HList for Product!($type1, $($type),*) { + type Tuple = ($type1, $( $type ),*); + + #[inline] + fn flatten(self) -> Self::Tuple { + #[allow(non_snake_case)] + let product_pat!($type1, $( $type ),*) = self; + ($type1, $( $type ),*) + } + } + + impl<$type1, $( $type ),*> Tuple for ($type1, $($type),*) { + type HList = Product!($type1, $( $type ),*); + + #[inline] + fn hlist(self) -> Self::HList { + #[allow(non_snake_case)] + let ($type1, $( $type ),*) = self; + product!($type1, $( $type ),*) + } + } + + impl Func for F + where + F: Fn($type1, $( $type ),*) -> R, + { + type Output = R; + + #[inline] + fn call(&self, args: Product!($type1, $($type),*)) -> Self::Output { + #[allow(non_snake_case)] + let product_pat!($type1, $( $type ),*) = args; + (*self)($type1, $( $type ),*) + } + } + + impl Func<($type1, $($type),*)> for F + where + F: Fn($type1, $( $type ),*) -> R, + { + type Output = R; + + #[inline] + fn call(&self, args: ($type1, $($type),*)) -> Self::Output { + #[allow(non_snake_case)] + let ($type1, $( $type ),*) = args; + (*self)($type1, $( $type ),*) + } + } + }; +} + +generics! { + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + T9, + T10, + T11, + T12, + T13, + T14, + T15, + T16 +} diff --git a/src/input.rs b/src/input.rs new file mode 100644 index 0000000..a89a4ba --- /dev/null +++ b/src/input.rs @@ -0,0 +1,70 @@ +#[derive(Debug, Clone, Copy)] +pub struct Input<'a> { + ptr: &'a str, +} + +impl<'a> Input<'a> { + pub fn new(ptr: &'a str) -> Self { + Self { ptr } + } + + pub fn take(&mut self, n: usize) -> &'a str { + let n = self + .ptr + .char_indices() + .skip(n) + .next() + .map(|(i, _)| i) + .unwrap_or(0); + let head = &self.ptr[..n]; + self.ptr = &self.ptr[n..]; + head + } + + /// Advances the pointer by the number of trimed spaces. + pub fn trim_start(&mut self) { + self.ptr = self.ptr.trim_start(); + } + + pub fn take_bytes(&mut self, n: usize) -> Option<&'a str> { + let head = self.ptr.get(..n)?; + self.ptr = &self.ptr[n..]; + Some(head) + } + + /// Advances the pointer until the given pattern has been reached, returning + /// the consumed characters. + #[inline] + pub fn advance_until<'b>(&'b mut self, pat: &str) -> &'a str { + let head = self.ptr.split(pat).next().unwrap_or(""); + self.ptr = &self.ptr[(head.len() + pat.len()).min(self.ptr.len())..]; + head + } + + /// Advances until the end of input, returning all + /// consumed characters. + #[inline] + pub fn advance_to_end(&mut self) -> &'a str { + let head = self.ptr; + self.ptr = &self.ptr[self.ptr.len()..]; + head + } + + /// Returns the number of remaining characters to read. + #[inline] + pub fn len(&self) -> usize { + self.ptr.len() + } + + /// Returns whether there are no more characters to read. + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl<'i> From<&'i str> for Input<'i> { + fn from(val: &'i str) -> Self { + Input { ptr: val } + } +} diff --git a/src/lib.rs b/src/lib.rs index e4d5ba3..a12db97 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,12 @@ -mod command; -mod dispatcher; -mod parser; -mod provider; +pub(crate) mod generic; +pub mod parser; +pub mod parsers; +pub mod command; +pub mod dispatcher; +mod input; -pub use command::{Argument, Command, CommandSpec}; -pub use dispatcher::CommandDispatcher; -pub use lieutenant_macros::{command, provider}; -pub use parser::{ArgumentKind, Input}; -pub use provider::{Provideable, Provider}; - -/// Denotes a type that may be passed to commands as input. -pub trait Context: Send + Sync + 'static { - type Error: Send; - type Ok; -} +pub use parser::Parser; +pub use parsers::*; +pub use dispatcher::*; +pub use command::*; +pub use input::Input; \ No newline at end of file diff --git a/src/parser.rs b/src/parser.rs deleted file mode 100644 index a5fe1eb..0000000 --- a/src/parser.rs +++ /dev/null @@ -1,115 +0,0 @@ -use crate::Context; - -/// The input type, acting like a stream of characters. -#[derive(Copy, Clone, Debug)] -pub struct Input<'a> { - ptr: &'a str, -} - -impl<'a> Input<'a> { - pub fn new(ptr: &'a str) -> Self { - Self { ptr } - } - - /// Advances the pointer until the given pattern has been reached, returning - /// the consumed characters. - pub fn advance_until<'b>(&'b mut self, pat: &str) -> &'a str { - let head = self.ptr.split(pat).next().unwrap_or(""); - self.ptr = &self.ptr[(head.len() + pat.len()).min(self.ptr.len())..]; - head - } - - /// Returns the number of remaining characters to read. - pub fn len(&self) -> usize { - self.ptr.len() - } - - /// Returns whether there are no more characters to read. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } -} - -/// Denotes a type which can be used as a command _argument_. -/// -/// The type must define the following functions: -/// * `satisfies`, returning whether the given input is -/// a valid instance of this argument. -pub trait ArgumentKind: Sized { - /// The error type returned by `Parse`. - /// - /// Must implement `Into`. - type ParseError: Into; - - /// Returns whether the given input is a valid - /// instance of this argument. Should advance the - /// pointer to `input` by the number of characters read. - /// - /// This can be performed conveniently using the `ParserUtil` - /// trait. - fn satisfies<'a>(ctx: &C, input: &mut Input<'a>) -> bool; - - /// Parses a value of this type from the given stream of characters. - /// - /// Should advance the pointer to `input` by the number of characters read. - fn parse<'a>(ctx: &C, input: &mut Input<'a>) -> Result; -} - -pub type SatisfiesFn = fn(&C, &mut Input) -> bool; - -mod arguments { - use super::*; - use std::num::*; - use std::path::PathBuf; - use std::str::FromStr; - - macro_rules! from_str_argument { - ($($ty:ty,)* $(,)?) => { - $( - impl ArgumentKind for $ty where C: Context, C::Error: From<<$ty as FromStr>::Err> { - type ParseError = <$ty as FromStr>::Err; - - fn satisfies<'a>(ctx: &C, input: &mut Input<'a>) -> bool { - Self::parse(ctx, input).is_ok() - } - - fn parse<'a>(_ctx: &C, input: &mut Input<'a>) -> Result { - let head = input.advance_until(" "); - Ok(Self::from_str(head)?) - } - } - )* - } - } - - from_str_argument!( - i8, - i16, - i32, - i64, - i128, - isize, - u8, - u16, - u32, - u64, - usize, - f32, - f64, - u128, - String, - bool, - char, - NonZeroI8, - NonZeroI16, - NonZeroI32, - NonZeroI64, - NonZeroIsize, - NonZeroU8, - NonZeroU16, - NonZeroU32, - NonZeroU64, - NonZeroUsize, - PathBuf, - ); -} diff --git a/src/parser/alt.rs b/src/parser/alt.rs new file mode 100644 index 0000000..b1e5133 --- /dev/null +++ b/src/parser/alt.rs @@ -0,0 +1,24 @@ +use super::{Either, Input, Parser, ParserBase, Result}; + +#[derive(Clone, Copy, Debug)] +pub struct Alt { + pub(super) first: T, + pub(super) second: U, +} + +impl ParserBase for Alt +where + T: Parser, + U: Parser, +{ + type Extract = (Either,); + + #[inline] + fn parse<'i>(&self, input: &mut Input<'i>) -> Result { + self.first + .parse(&mut input.clone()) + .map(Either::A) + .or_else(|_| self.second.parse(input).map(Either::B)) + .map(|e| (e,)) + } +} diff --git a/src/parser/executor.rs b/src/parser/executor.rs new file mode 100644 index 0000000..8d27598 --- /dev/null +++ b/src/parser/executor.rs @@ -0,0 +1,23 @@ + +use super::{Parser, ParserBase, Tuple, HList, Combine, Input, Func, Result, Lazy}; +use crate::command::CommandMapping; + +pub struct Executor { + pub(super) parser: P, + pub(super) callback: F, +} + +impl ParserBase for Executor +where + P: Parser, + P::Extract: Lazy, + F: Func<::Output> + Clone, +{ + type Extract = (CommandMapping,); + + #[inline] + fn parse<'i>(&self, input: &mut Input<'i>) -> Result { + let arguments = self.parser.parse(input)?; + Ok((CommandMapping::new(arguments, self.callback.clone()),)) + } +} \ No newline at end of file diff --git a/src/parser/map.rs b/src/parser/map.rs new file mode 100644 index 0000000..db81005 --- /dev/null +++ b/src/parser/map.rs @@ -0,0 +1,21 @@ +use super::{Func, Input, Parser, ParserBase, Result}; + +#[derive(Clone)] +pub struct Map { + pub(super) parser: P, + pub(super) callback: F, +} + +impl ParserBase for Map +where + P: Parser, + F: Func, +{ + type Extract = (F::Output,); + + #[inline] + fn parse<'i>(&self, input: &mut Input<'i>) -> Result { + let ex = self.parser.parse(input)?; + Ok((self.callback.call(ex),)) + } +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..745ec66 --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1,262 @@ +mod then; +mod map; +mod alt; +mod unify; +mod untuple_one; +mod executor; + +pub(crate) use self::then::Then; +pub(crate) use self::map::Map; +pub(crate) use self::alt::Alt; +pub(crate) use self::unify::Unify; +pub(crate) use self::untuple_one::UntupleOne; +pub(crate) use self::executor::Executor; + +pub(crate) use crate::generic::{Combine, Either, Func, HList, Tuple}; + +pub use crate::Input; + +use std::borrow::Cow; + +#[derive(Debug)] +pub enum Error { + // Exact + Literal(Cow<'static, str>), + // OneOf + Literals(Vec>), + Todo, + UnkownCommand, +} + +pub type Result = ::std::result::Result; + +pub trait ParserBase { + type Extract: Tuple; + + fn parse<'i>(&self, input: &mut Input<'i>) -> Result; +} + +pub trait Parser: ParserBase { + fn then(self, other: F) -> Then + where + Self: Sized, + ::HList: Combine<::HList>, + F: Parser + Clone, + { + Then { + first: self, + second: other, + } + } + + /// Alternative parser + fn alt(self, other: F) -> Alt + where + Self: Sized, + F: Parser, + { + Alt { + first: self, + second: other, + } + } + + fn map(self, fun: F) -> Map + where + Self: Sized, + F: Func + Clone, + { + Map { + parser: self, + callback: fun, + } + } + + fn untuple_one(self) -> UntupleOne + where + Self: Parser + Sized, + T: Tuple, + { + UntupleOne { parser: self } + } + + fn unify(self) -> Unify + where + Self: Parser,)> + Sized, + T: Tuple, + { + Unify { parser: self } + } + + fn boxed<'a>(self) -> Box> + where + Self: Sized, + Self: 'static, + { + Box::new(self) + } + + fn execute(self, fun: F) -> Executor + where + Self: Sized, + Self::Extract: Lazy, + F: Func<::Output> + Clone, + { + Executor { + parser: self, + callback: fun, + } + } +} + +pub trait Lazy { + type State; + type Output; + + fn get(self, state: &Self::State) -> Self::Output; +} + +pub struct Const(T); + +impl Lazy for Const { + type Output = T; + type State = (); + fn get(self, _state: &Self::State) -> Self::Output { + self.0 + } +} + +impl Lazy for (T1, T2, T3) +where + T1: Lazy, + T2: Lazy, + T3: Lazy, +{ + type Output = (T1::Output, T2::Output, T3::Output); + type State = T1::State; + + fn get(self, state: &Self::State) -> Self::Output { + (self.0.get(state), self.1.get(state), self.2.get(state)) + } +} + +impl Parser for T where T: ParserBase {} + + +#[cfg(test)] +mod tests { + use super::{ParserBase, Const}; + use crate::generic::{FuncOnce, Func}; + use crate::{Parser, CommandDispatcher, State, RefMut}; + use crate::parsers::{Literals, any, param, literal}; + + #[test] + fn and_command() { + let root = literal("hello") + .then(literal("world")) + .then(param()) + .map(|a: i32| move |n: &mut i32| *n += a); + + let state = 69; + + // let foo = literal("foo") + // .then(param()) + // .execute(|_a: u32| {}) + // .alt(literal("boo").execute(|| {})); + + // if let Ok((command,)) = foo.parse(&mut "foo -32".into()) { + // command.call((state,)); + // } + + let mut n = 45; + + if let Ok((command,)) = root.parse(&mut "Hello World -3".into()) { + command(&mut n) + } + + assert_eq!(n, 42); + + let command = root.parse(&mut "bar".into()); + assert!(command.is_err()); + } + + struct PlayerState(i32, i32, i32); + + impl State for PlayerState { + fn get(&self) -> Option { + todo!() + } + } + + #[test] + fn new_api() { + let mut dispatcher = CommandDispatcher::default(); + let tp = param::() + .then(param::()) + .then(param::()) + .map(|x: i32, y: i32, z: i32| (Const(x), Const(y), Const(z))) + .untuple_one() + .execute(|x: i32, y: i32, z: i32| { + println!("you have teleported to {} {} {}", x, y, z); + }); + + dispatcher.register("tp", tp); + + + + let result = dispatcher.call(&(), "tp 10 20 30"); + dbg!(&result); + assert!(result.is_ok()) + } + + #[test] + fn or_command() { + let add = literal("add") + .then(param()) + .map(|n: i32| move |state: &mut i32| *state += n); + + let times = literal("times") + .then(param()) + .map(|n: i32| move |state: &mut i32| *state *= n); + + let reset = literal("reset").map(|| |state: &mut i32| *state = 0); + let root = literal("math").then(add.alt(times).alt(reset)); + + let mut n = 45; + + if let Ok((command,)) = root.parse(&mut "math add 10".into()) { + command.call((&mut n,)) + } + assert_eq!(n, 55); + + if let Ok((command,)) = root.parse(&mut "math times 10".into()) { + command.call((&mut n,)) + } + assert_eq!(n, 550); + + if let Ok((command,)) = root.parse(&mut "math reset".into()) { + command.call((&mut n,)) + } + assert_eq!(n, 0); + + let command = root.parse(&mut "foo".into()); + assert!(command.is_err()); + } + + #[test] + fn hashed_literals() { + let mut root: Literals<()> = Literals::default(); + root.insert("a0", any().boxed()); + root.insert("a1", any().boxed()); + root.insert("a2", any().boxed()); + root.insert("a3", any().boxed()); + root.insert("a4", any().boxed()); + root.insert("a5", any().boxed()); + root.insert("a6", any().boxed()); + root.insert("a7", any().boxed()); + root.insert("a8", any().boxed()); + root.insert("a9", any().boxed()); + + assert!(root.parse(&mut "a1".into()).is_ok()) + } +} diff --git a/src/parser/then.rs b/src/parser/then.rs new file mode 100644 index 0000000..309e524 --- /dev/null +++ b/src/parser/then.rs @@ -0,0 +1,27 @@ +use super::{Combine, HList, Input, Parser, ParserBase, Tuple, Result}; + +type Combined = <<<::Extract as Tuple>::HList as Combine< + <::Extract as Tuple>::HList, +>>::Output as HList>::Tuple; + +#[derive(Debug, Copy, Clone)] +pub struct Then { + pub(super) first: T, + pub(super) second: U, +} + +impl ParserBase for Then +where + T: Parser, + U: Parser, + ::HList: Combine<::HList>, +{ + type Extract = Combined; + + #[inline] + fn parse<'i>(&self, input: &mut Input<'i>) -> Result { + let first = self.first.parse(input)?.hlist(); + let second = self.second.parse(input)?.hlist(); + Ok(first.combine(second).flatten()) + } +} diff --git a/src/parser/unify.rs b/src/parser/unify.rs new file mode 100644 index 0000000..61ff605 --- /dev/null +++ b/src/parser/unify.rs @@ -0,0 +1,23 @@ +use super::{Either, Input, Parser, ParserBase, Tuple, Result}; + +#[derive(Clone, Copy, Debug)] +pub struct Unify { + pub(super) parser: F, +} + +impl ParserBase for Unify +where + F: Parser,)>, + T: Tuple, +{ + type Extract = T; + + #[inline] + fn parse<'i>(&self, input: &mut Input<'i>) -> Result { + let (ex,) = self.parser.parse(input)?; + Ok(match ex { + Either::A(a) => a, + Either::B(b) => b, + }) + } +} diff --git a/src/parser/untuple_one.rs b/src/parser/untuple_one.rs new file mode 100644 index 0000000..3f32bf3 --- /dev/null +++ b/src/parser/untuple_one.rs @@ -0,0 +1,22 @@ +use super::{Input, Parser, ParserBase, Tuple, Result}; + +#[derive(Clone, Copy, Debug)] +pub struct UntupleOne

{ + pub(super) parser: P, +} + +impl ParserBase for UntupleOne

+where + P: Parser, + T: Tuple, +{ + type Extract = T; + + #[inline] + fn parse<'i>(&self, input: &mut Input<'i>) -> Result { + match self.parser.parse(input) { + Ok((arg,)) => Ok(arg), + Err(err) => Err(err), + } + } +} diff --git a/src/parsers/any.rs b/src/parsers/any.rs new file mode 100644 index 0000000..648cca3 --- /dev/null +++ b/src/parsers/any.rs @@ -0,0 +1,18 @@ +use crate::parser::{ParserBase, Result}; +use crate::Input; + +#[derive(Debug, Clone)] +pub struct Any; + +impl ParserBase for Any { + type Extract = (); + + #[inline] + fn parse<'i>(&self, _input: &mut Input<'i>) -> Result { + Ok(()) + } +} + +pub fn any() -> Any { + Any +} \ No newline at end of file diff --git a/src/parsers/literal.rs b/src/parsers/literal.rs new file mode 100644 index 0000000..3bbaf00 --- /dev/null +++ b/src/parsers/literal.rs @@ -0,0 +1,43 @@ +use crate::parser::{Error, ParserBase, Result}; +use crate::Input; +use unicase::UniCase; +use std::borrow::Cow; + +#[derive(Debug, Clone)] +pub struct Literal { + value: Cow<'static, str>, + unicase: UniCase>, +} + +impl ParserBase for Literal +{ + type Extract = (); + + fn parse<'i>(&self, input: &mut Input<'i>) -> Result { + let head = input + .take_bytes(self.value.len()) + // TODO remove clone + .ok_or(Error::Literal(self.value.clone()))?; + input.trim_start(); + let head_unicase = UniCase::new(head); + if self.unicase == head_unicase { + Ok(()) + } else { + Err(Error::Literal(self.value.clone())) + } + } +} + +pub fn literal(lit: L) -> Literal +where + L: Into> +{ + let lit = lit.into(); + assert!(!lit.is_empty()); + assert!(lit.chars().all(|c| c != ' ')); + // TODO remove clone + Literal { + value: lit.clone(), + unicase: UniCase::new(lit), + } +} diff --git a/src/parsers/literals.rs b/src/parsers/literals.rs new file mode 100644 index 0000000..f9150b1 --- /dev/null +++ b/src/parsers/literals.rs @@ -0,0 +1,51 @@ +use crate::parser::{Parser, ParserBase, Tuple, Result, Error}; +use crate::Input; +use std::collections::HashMap; +use unicase::UniCase; +use std::borrow::Cow; + +pub struct Literals(HashMap>, Box>>); + +impl Literals { + pub fn new() -> Self { + Self::default() + } + + pub fn insert(&mut self, lit: L, parser: Box>) + where + L: Into>, + { + let lit = lit.into(); + assert!(!lit.is_empty()); + assert!(lit.chars().all(|c| c != ' ')); + self.0.insert(UniCase::new(lit), parser); + } +} + +impl<'a, E> Default for Literals { + fn default() -> Self { + Self(HashMap::new()) + } +} + +impl<'a, E> ParserBase for Literals +where + E: Tuple, +{ + type Extract = E; + + fn parse<'i>(&self, input: &mut Input<'i>) -> Result { + let head = input.advance_until(" "); + let head = UniCase::new(head.into()); + self.0.get(&head).ok_or(Error::Literals(vec![]))?.parse(input) + } +} + +pub fn literals<'a, P, L, LS>(literals: LS) -> Literals +where + P: 'static + Parser, + L: Into>, + LS: IntoIterator, +{ + Literals(literals.into_iter().map(|(lit, parser)| (UniCase::new(lit.into()), parser.boxed())).collect()) +} diff --git a/src/parsers/mod.rs b/src/parsers/mod.rs new file mode 100644 index 0000000..eb9036d --- /dev/null +++ b/src/parsers/mod.rs @@ -0,0 +1,9 @@ +mod literal; +mod literals; +mod any; +mod param; + +pub use literal::{Literal, literal}; +pub use literals::{Literals, literals}; +pub use any::{Any, any}; +pub use param::{Param, param}; \ No newline at end of file diff --git a/src/parsers/param.rs b/src/parsers/param.rs new file mode 100644 index 0000000..33c59f0 --- /dev/null +++ b/src/parsers/param.rs @@ -0,0 +1,28 @@ +use crate::parser::{ParserBase, Result, Error}; +use crate::Input; + +#[derive(Debug, Clone)] +pub struct Param { + param: std::marker::PhantomData, +} + +impl ParserBase for Param +where + T: std::str::FromStr, +{ + type Extract = (T,); + + fn parse<'i>(&self, input: &mut Input<'i>) -> Result { + let head = input.advance_until(" "); + match T::from_str(head) { + Ok(ok) => Ok((ok,)), + Err(_) => Err(Error::Todo), + } + } +} + +pub fn param() -> Param { + Param { + param: Default::default(), + } +} \ No newline at end of file diff --git a/src/provider.rs b/src/provider.rs deleted file mode 100644 index 70fd60d..0000000 --- a/src/provider.rs +++ /dev/null @@ -1,12 +0,0 @@ -use crate::Context; - -pub trait Provider: Default { - type Output; - type Error: Into; - - fn provide(&self, ctx: &C) -> Result; -} - -pub trait Provideable { - type Provider: Provider; -} diff --git a/tests/basic.rs b/tests/basic.rs deleted file mode 100644 index ff0c83f..0000000 --- a/tests/basic.rs +++ /dev/null @@ -1,404 +0,0 @@ -use lieutenant::{command, provider, CommandDispatcher, Context, Provideable}; -use std::convert::Infallible; -use thiserror::Error; - -#[derive(Debug, Error, PartialEq)] -enum Error { - #[error("{0}")] - Custom(String), - #[error("failed to parse int")] - ParseInt, -} - -impl From for Error { - fn from(_: std::num::ParseIntError) -> Self { - Error::ParseInt - } -} - -impl From for Error { - fn from(_: std::convert::Infallible) -> Self { - panic!("Cannot fail") - } -} - -#[test] -fn basic_command() { - #[derive(Debug, PartialEq, Eq)] - struct State(i32); - - impl Context for State { - type Error = Error; - type Ok = (); - } - - #[command(usage = "test ")] - fn test(ctx: &mut State, x: i32) -> Result<(), Error> { - *ctx = State(x); - Ok(()) - }; - - let dispatcher = CommandDispatcher::default().with(test); - - let mut x = State(0); - assert!(dispatcher.dispatch(&mut x, "test 27").is_ok()); - assert_eq!(x, State(27)); -} - -/*#[test] -fn basic_command_parallel() { - use futures::future; - use futures::join; - use smol::Timer; - use std::thread; - use std::time::{Duration, Instant}; - - for _ in 0..2 { - // A pending future is one that simply yields forever. - thread::spawn(|| smol::run(future::pending::<()>())); - } - - #[derive(Debug, PartialEq, Eq)] - struct State(i32); - - impl Context for State { - type Error = Error; - type Ok = (); - } - - #[command(usage = "test ")] - async fn test(ctx: &mut State, x: i32) -> Result<(), Error> { - *ctx = State(x); - Timer::after(Duration::from_secs(1)).await; - - Ok(()) - }; - - let dispatcher = CommandDispatcher::default().with(test); - - let mut nodes_a = Vec::new(); - let mut errors_a = Vec::new(); - - let mut nodes_b = Vec::new(); - let mut errors_b = Vec::new(); - - let mut a = State(0); - let mut b = State(0); - - let call_a = dispatcher.dispatch(&mut nodes_a, &mut errors_a, &mut a, "test 27"); - let call_b = dispatcher.dispatch(&mut nodes_b, &mut errors_b, &mut b, "test 27"); - - let now = Instant::now(); - - assert_eq!(smol::run(async { join!(call_a, call_b) }), (Ok(()), Ok(()))); - - assert_eq!(now.elapsed().as_secs(), 1); -}*/ - -#[test] -fn error_handling() { - #[derive(Debug, PartialEq, Eq)] - struct State; - - impl Context for State { - type Error = Error; - type Ok = (); - } - - #[command(usage = "test ")] - fn test(ctx: &mut State, x: i32) -> Result<(), Error> { - if x == 0 { - Ok(()) - } else { - Err(Error::Custom("Not zero".into())) - } - }; - - let dispatcher = CommandDispatcher::default().with(test); - - assert_eq!(dispatcher.dispatch(&mut State, "test 0"), Ok(())); - assert_eq!( - dispatcher.dispatch(&mut State, "test 5"), - Err(vec![Error::Custom("Not zero".into())]) - ); -} - -#[test] -fn multiple_args() { - struct State { - x: i32, - y: String, - } - - impl Context for State { - type Error = Error; - type Ok = (); - } - - #[command(usage = "test14 extra_literal")] - fn test14(state: &mut State, new_x: i32, new_y: String) -> Result<(), Error> { - state.x = new_x; - state.y = new_y; - Ok(()) - } - - let mut dispatcher = CommandDispatcher::default(); - dispatcher.register(test14).unwrap(); - - let mut state = State { - x: 690_854, - y: String::from("wrong"), - }; - assert!(dispatcher - .dispatch(&mut state, "test14 66 string extra_literal") - .is_ok()); - - assert_eq!(state.x, 66); - assert_eq!(state.y.as_str(), "string"); -} - -#[test] -fn multiple_commands() { - struct State { - x: i32, - y: String, - } - - impl Context for State { - type Error = Error; - type Ok = (); - } - - #[command(usage = "cmd1 extra_lit")] - fn cmd1(state: &mut State, new_x: i32) -> Result<(), Error> { - state.x = new_x; - Ok(()) - } - - #[command(usage = "cmd2 ")] - fn cmd2(state: &mut State, new_y: String) -> Result<(), Error> { - state.y = new_y; - Ok(()) - } - - let dispatcher = CommandDispatcher::default().with(cmd1).with(cmd2); - - let mut state = State { - x: 32, - y: String::from("incorrect"), - }; - - assert!(dispatcher.dispatch(&mut state, "cmd1 10").is_err()); // misssing extra_lit - - assert!(dispatcher.dispatch(&mut state, "cmd1 10 extra_lit").is_ok()); - assert_eq!(state.x, 10); - - assert!(dispatcher - .dispatch(&mut state, "invalid command 22") - .is_err()); - - assert!(dispatcher.dispatch(&mut state, "cmd2 new_string").is_ok()); - assert_eq!(state.y.as_str(), "new_string"); -} - -#[test] -fn command_macro() { - struct State { - x: i32, - player: String, - } - - impl Context for State { - type Error = Error; - type Ok = (); - } - - #[command(usage = "test ")] - fn test(state: &mut State, x: i32) -> Result<(), Error> { - state.x = x; - Ok(()) - } - - #[command(usage = "foo ")] - fn foo_a_player(state: &mut State, player: String) -> Result<(), Error> { - state.player.push_str(&player); - Ok(()) - } - - #[command(usage = "bar ")] - fn foo_a_player_then_bar_an_x(state: &mut State, x: i32, player: String) -> Result<(), Error> { - state.player.push_str(&player); - state.x = x + 1; - Ok(()) - } - - let dispatcher = CommandDispatcher::default() - .with(test) - .with(foo_a_player) - .with(foo_a_player_then_bar_an_x); - - let mut state = State { - x: 0, - player: String::new(), - }; - assert!(dispatcher.dispatch(&mut state, "false command").is_err()); - - assert!(dispatcher.dispatch(&mut state, "test 25").is_ok()); - assert_eq!(state.x, 25); - - assert!(dispatcher.dispatch(&mut state, "foo twenty-six").is_ok()); - assert_eq!(state.player.as_str(), "twenty-six"); - - assert!(dispatcher.dispatch(&mut state, "test").is_err()); - - assert!(dispatcher - .dispatch(&mut state, "test not-a-number") - .is_err()); - - assert!(dispatcher.dispatch(&mut state, "bar").is_err()); - - assert!(dispatcher.dispatch(&mut state, "bar player").is_err()); - - assert!(dispatcher.dispatch(&mut state, "bar player four").is_err()); - - assert!(dispatcher.dispatch(&mut state, "bar PLAYER 28").is_ok()); - - assert_eq!(state.x, 29); - assert_eq!(state.player.as_str(), "twenty-sixPLAYER"); -} - -#[test] -fn aliasing() { - struct State { - x: u32, - } - - impl Context for State { - type Error = Error; - type Ok = (); - } - #[command(usage = "test|t lit2|lit3")] - fn command(state: &mut State, x: u32) -> Result<(), Error> { - state.x = x; - Ok(()) - } - - let dispatcher = CommandDispatcher::new().with(command); - - let mut state = State { x: 0 }; - - assert!(dispatcher.dispatch(&mut state, "test 10 lit2").is_ok()); - assert_eq!(state.x, 10); - - assert!(dispatcher.dispatch(&mut state, "t 15 lit3").is_ok()); - assert_eq!(state.x, 15); - - for wrong in ["test 1 lit", "test 1", "t 2", "t 2 lit", "t string lit2"] - .iter() - .copied() - { - assert!(dispatcher.dispatch(&mut state, wrong).is_err()); - } -} - -#[test] -fn providers() { - struct State { - x: u32, - } - - impl Context for State { - type Error = Infallible; - type Ok = (); - } - - struct Provided(u32); - impl Provideable for Provided { - type Provider = provide_u32; - } - - #[provider] - fn provide_u32(ctx: &State) -> Provided { - Provided(ctx.x) - } - - #[command(usage = "test")] - fn cmd(ctx: &mut State, provided: Provided) -> Result<(), Infallible> { - ctx.x += provided.0; - Ok(()) - } - - let mut state = State { x: 15 }; - let dispatcher = CommandDispatcher::new().with(cmd); - - assert!(dispatcher.dispatch(&mut state, "test").is_ok()); - assert_eq!(state.x, 30); -} - -#[test] -fn help_command() { - // use std::borrow::Cow; - // use std::rc::Rc; - // struct State { - // dispatcher: Rc>, - // usages: Vec>, - // descriptions: Vec>, - // } - - // impl Context for State { - // type Error = Error; - // type Ok = (); - // } - - // let mut dispatcher = CommandDispatcher::default(); - - // #[command( - // usage = "help ", - // description = "Shows the descriptions and usages of all commands." - // )] - // fn help(state: &mut State, page: u32) -> Result<(), Error> { - // state.usages = state - // .dispatcher - // .commands() - // .skip(page as usize * 10) - // .take(10) - // .map(|meta| meta.arguments.iter().map(|_| "").collect()) - // .collect(); - // state.descriptions = state - // .dispatcher - // .commands() - // .skip(page as usize * 10) - // .take(10) - // .filter_map(|meta| meta.description.clone()) - // .collect(); - // Ok(()) - // } - - // dispatcher.register(help).unwrap(); - - // let dispatcher = Rc::new(dispatcher); - - // let mut nodes = Vec::new(); - // let mut errors = Vec::new(); - - // let mut ctx = State { - // dispatcher: Rc::clone(&dispatcher), - // usages: vec![], - // descriptions: vec![], - // }; - - // assert!(dispatcher - // .dispatch(&mut nodes, &mut errors, &mut ctx, "help 0") - // .is_ok()); - // assert_eq!(ctx.usages, vec!["/help "]); - // assert_eq!( - // ctx.descriptions, - // vec!["Shows the descriptions and usages of all commands."] - // ); - - // assert!(dispatcher - // .dispatch(&mut nodes, &mut errors, &mut ctx, "help 1") - // .is_ok()); - // assert!(ctx.usages.is_empty()); - // assert!(ctx.descriptions.is_empty()); -}