diff --git a/CHANGELOG b/CHANGELOG index 3d4b80a..6b01b79 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +0.6.1 +----- +Remove debug output +help() no longer takes just strings + 0.6.0 ----- Added (Implied)(Multiplication) 2x, etc diff --git a/README.md b/README.md index c6f7226..36c9761 100644 --- a/README.md +++ b/README.md @@ -54,16 +54,16 @@ fn main() -> Result<(), ParserError> { A number of functions and @decorators are available for expressions to use - add more using the state: ```rust -use lavendeux_parser::{ParserState, ParserError, FunctionDefinition, FunctionArgument, Value}; +use lavendeux_parser::{ParserState, ParserError, DecoratorDefinition, FunctionDefinition, FunctionArgument, Value}; use lavendeux_parser::errors::*; -// Decorators take in a single value, and return a string representation -fn new_decorator_handler(arg: &Value) -> Result { - Ok(arg.as_string()) -} - let mut state : ParserState = ParserState::new(); -state.decorators.register("new_decorator", new_decorator_handler); +state.decorators.register(DecoratorDefinition { + name: &["upper", "uppercase"], + description: "Outputs an uppercase version of the input", + argument: ExpectedTypes::Any, + handler: |_, input| Ok(input.as_string().to_uppercase()) +}); // Functions take in an array of values, and return a single value state.functions.register(FunctionDefinition { diff --git a/src/decorators.rs b/src/decorators.rs index b114d4d..703f5c0 100644 --- a/src/decorators.rs +++ b/src/decorators.rs @@ -3,40 +3,31 @@ use super::errors::*; use std::collections::HashMap; use chrono::prelude::*; -pub type DecoratorHandler = fn(&Value) -> Result; +pub type DecoratorHandler = fn(&DecoratorDefinition, &Value) -> Result; +/// Holds a set of callable decorators #[derive(Clone)] -pub struct DecoratorTable(HashMap); +pub struct DecoratorTable(HashMap); impl DecoratorTable { /// Initialize a new decorator table, complete with default builtin decorators pub fn new() -> DecoratorTable { let mut table : DecoratorTable = DecoratorTable(HashMap::new()); - table.0.insert("default".to_string(), decorator_default); - table.0.insert("hex".to_string(), decorator_hex); - table.0.insert("bin".to_string(), decorator_bin); - table.0.insert("oct".to_string(), decorator_oct); + table.register(DEFAULT); + table.register(HEX); + table.register(OCT); + table.register(BIN); - table.0.insert("sci".to_string(), decorator_sci); - table.0.insert("float".to_string(), decorator_float); - table.0.insert("int".to_string(), decorator_int); - table.0.insert("bool".to_string(), decorator_bool); + table.register(SCI); + table.register(FLOAT); + table.register(INT); + table.register(BOOL); - table.0.insert("utc".to_string(), decorator_utc); - - table.0.insert("dollar".to_string(), decorator_dollars); - table.0.insert("dollars".to_string(), decorator_dollars); - table.0.insert("usd".to_string(), decorator_dollars); - table.0.insert("aud".to_string(), decorator_dollars); - table.0.insert("cad".to_string(), decorator_dollars); - - table.0.insert("euro".to_string(), decorator_euros); - table.0.insert("euros".to_string(), decorator_euros); - - table.0.insert("pound".to_string(), decorator_pounds); - table.0.insert("pounds".to_string(), decorator_pounds); - - table.0.insert("yen".to_string(), decorator_yen); + table.register(UTC); + table.register(DOLLAR); + table.register(EURO); + table.register(POUND); + table.register(YEN); table } @@ -46,8 +37,10 @@ impl DecoratorTable { /// # Arguments /// * `name` - Decorator name /// * `handler` - Decorator handler - pub fn register(&mut self, name: &str, handler: DecoratorHandler) { - self.0.insert(name.to_string(), handler); + pub fn register(&mut self, definition: DecoratorDefinition) { + for name in definition.name() { + self.0.insert(name.to_string(), definition.clone()); + } } /// Check if the table contains a decorator by the given name @@ -58,9 +51,17 @@ impl DecoratorTable { self.0.contains_key(name) } + /// Return a given decorator + /// + /// # Arguments + /// * `name` - Function name + pub fn get(&self, name: &str) -> Option<&DecoratorDefinition> { + self.0.get(name) + } + /// Get a collection of all included decorators - pub fn all(&self) -> Vec { - self.0.keys().cloned().collect() + pub fn all(&self) -> Vec<&String> { + self.0.keys().collect::>() } /// Call a decorator @@ -70,7 +71,7 @@ impl DecoratorTable { /// * `args` - Decorator arguments pub fn call(&self, name: &str, arg: &Value) -> Result { match self.0.get(name) { - Some(f) => f(arg), + Some(f) => f.call(arg), None => Err(ParserError::DecoratorName(DecoratorNameError::new(name))) } } @@ -82,113 +83,198 @@ impl Default for DecoratorTable { } } -pub fn decorator_default(input: &Value) -> Result { - match input { - Value::Boolean(_) => decorator_bool(input), - Value::Integer(_) => decorator_int(input), - Value::Float(_) => decorator_float(input), - Value::String(s) => Ok(s.to_string()), - Value::None => Ok("".to_string()) - } -} +/// Holds the definition of a builtin callable decorator +#[derive(Clone)] +pub struct DecoratorDefinition { + /// Decorator call name + pub name: &'static [&'static str], + + /// Decorator short description + pub description: &'static str, -fn decorator_hex(input: &Value) -> Result { - if let Some(n) = input.as_int() { - Ok(format!("{:#x}", n)) - } else { - Err(ParserError::FunctionArgType(FunctionArgTypeError::new("@hex", 1, ExpectedTypes::IntOrFloat))) - } -} + /// Type of input the decorator expects + pub argument: ExpectedTypes, -fn decorator_bin(input: &Value) -> Result { - if let Some(n) = input.as_int() { - Ok(format!("{:#0b}", n)) - } else { - Err(ParserError::FunctionArgType(FunctionArgTypeError::new("@bin", 1, ExpectedTypes::IntOrFloat))) - } + /// Handler function + pub handler: DecoratorHandler } - -fn decorator_oct(input: &Value) -> Result { - if let Some(n) = input.as_int() { - Ok(format!("{:#0o}", n)) - } else { - Err(ParserError::FunctionArgType(FunctionArgTypeError::new("@oct", 1, ExpectedTypes::IntOrFloat))) +impl DecoratorDefinition { + /// Return the decorator's names + pub fn name(&self) -> &[&str] { + self.name } -} - -fn decorator_sci(input: &Value) -> Result { - if let Some(n) = input.as_float() { - Ok(format!("{:e}", n)) - } else { - Err(ParserError::FunctionArgType(FunctionArgTypeError::new("@sci", 1, ExpectedTypes::IntOrFloat))) + + /// Return the decorator's description + pub fn description(&self) -> &str { + self.description } -} -fn decorator_utc(input: &Value) -> Result { - if let Some(n) = input.as_int() { - let t = NaiveDateTime::from_timestamp(n, 0); - let datetime: DateTime = DateTime::from_utc(t, Utc); - Ok(datetime.format("%Y-%m-%d %H:%M:%S").to_string()) - } else { - Err(ParserError::FunctionArgType(FunctionArgTypeError::new("@utc", 1, ExpectedTypes::IntOrFloat))) + /// Return the decorator's argument type + pub fn arg(&self) -> ExpectedTypes { + self.argument.clone() + } + + /// Return the decorator's signature + pub fn signature(&self) -> String { + let name = self.name.into_iter().map(|n|format!("@{n}")).collect::>().join("/"); + format!("{}", name) + } + + /// Return the decorator's signature + pub fn help(&self) -> String { + format!("{}: {}", self.signature(), self.description) } -} -fn decorator_currency(input: &Value, sig: &str, symbol: &str) -> Result { - if let Some(n) = input.as_float() { - let mut f = format!("{}{:.2}", symbol, n); - if !f.contains('.') { - f += ".0"; - } - f = f - .chars().rev().collect::>() - .chunks(3).map(|c| c.iter().collect::()).collect::>().join(",") - .replacen(',', "", 1) - .chars().rev().collect::(); - if f.chars().nth(1).unwrap() == ',' { - f = f.replacen(',', "", 1); + /// Validate decorator arguments, and return an error if one exists + /// + /// # Arguments + /// * `arg` - Decorator input + pub fn validate(&self, arg: &Value) -> Option { + let valid = match self.arg() { + ExpectedTypes::Float => arg.is_float(), + ExpectedTypes::Int => arg.is_int(), + ExpectedTypes::IntOrFloat => arg.is_float() || arg.is_int(), + ExpectedTypes::String => true, ExpectedTypes::Boolean => true, ExpectedTypes::Any => true // These can be converted from any type + }; + + if !valid { + Some(ParserError::FunctionArgType(FunctionArgTypeError::new(&self.signature(), 1, self.arg()))) + } else { + None } - Ok(f) - } else { - Err(ParserError::FunctionArgType(FunctionArgTypeError::new(sig, 1, ExpectedTypes::IntOrFloat))) } -} - -fn decorator_dollars(input: &Value) -> Result { - decorator_currency(input, "@dollars", "$") -} - -fn decorator_euros(input: &Value) -> Result { - decorator_currency(input, "@euros", "€") -} - -fn decorator_pounds(input: &Value) -> Result { - decorator_currency(input, "@pounds", "£") -} -fn decorator_yen(input: &Value) -> Result { - decorator_currency(input, "@yen", "¥") -} - -fn decorator_float(input: &Value) -> Result { - if let Some(n) = input.as_float() { - Ok(Value::Float(n).as_string()) - } else { - Err(ParserError::FunctionArgType(FunctionArgTypeError::new("@float", 1, ExpectedTypes::IntOrFloat))) + // Call the associated decorator handler + /// + /// # Arguments + /// * `arg` - Decorator input + pub fn call(&self, arg: &Value) -> Result { + if let Some(error) = self.validate(arg) { + Err(error) + } else { + (self.handler)(self, arg) + } } } -fn decorator_int(input: &Value) -> Result { - if let Some(n) = input.as_int() { - Ok(Value::Integer(n).as_string()) - } else { - Err(ParserError::FunctionArgType(FunctionArgTypeError::new("@int", 1, ExpectedTypes::IntOrFloat))) +fn decorator_currency(input: &Value, symbol: &str) -> Result { + let n = input.as_float().unwrap(); + let mut f = format!("{}{:.2}", symbol, n); + if !f.contains('.') { + f += ".0"; + } + f = f + .chars().rev().collect::>() + .chunks(3).map(|c| c.iter().collect::()).collect::>().join(",") + .replacen(',', "", 1) + .chars().rev().collect::(); + if f.chars().nth(1).unwrap() == ',' { + f = f.replacen(',', "", 1); } + Ok(f) } -fn decorator_bool(input: &Value) -> Result { - Ok(Value::Boolean(input.as_bool()).as_string()) -} +const DEFAULT : DecoratorDefinition = DecoratorDefinition { + name: &["default"], + description: "Default formatter, type dependent", + argument: ExpectedTypes::Any, + handler: |_, input| match input { + Value::Boolean(_) => (BOOL.handler)(&BOOL, input), + Value::Integer(_) => (INT.handler)(&INT, input), + Value::Float(_) => (FLOAT.handler)(&FLOAT, input), + Value::String(s) => Ok(s.to_string()), + Value::None => Ok("".to_string()) + } +}; + +const HEX : DecoratorDefinition = DecoratorDefinition { + name: &["hex"], + description: "Base 16 number formatting, such as 0xFF", + argument: ExpectedTypes::IntOrFloat, + handler: |_, input| Ok(format!("{:#x}", input.as_int().unwrap())) +}; + +const OCT : DecoratorDefinition = DecoratorDefinition { + name: &["oct"], + description: "Base 8 number formatting, such as 0b77", + argument: ExpectedTypes::IntOrFloat, + handler: |_, input| Ok(format!("{:#0o}", input.as_int().unwrap())) +}; + +const BIN : DecoratorDefinition = DecoratorDefinition { + name: &["bin"], + description: "Base 2 number formatting, such as 0b11", + argument: ExpectedTypes::IntOrFloat, + handler: |_, input| Ok(format!("{:#0b}", input.as_int().unwrap())) +}; + +const SCI : DecoratorDefinition = DecoratorDefinition { + name: &["sci"], + description: "Scientific number formatting, such as 1.2Ee-3", + argument: ExpectedTypes::IntOrFloat, + handler: |_, input| Ok(format!("{:e}", input.as_float().unwrap())) +}; + +const UTC : DecoratorDefinition = DecoratorDefinition { + name: &["utc"], + description: "Interprets an integer as a timestamp, and formats it in UTC standard", + argument: ExpectedTypes::IntOrFloat, + handler: |_, input| { + let n = input.as_int().unwrap(); + let t = NaiveDateTime::from_timestamp(n, 0); + let datetime: DateTime = DateTime::from_utc(t, Utc); + Ok(datetime.format("%Y-%m-%d %H:%M:%S").to_string()) + } +}; + +const DOLLAR : DecoratorDefinition = DecoratorDefinition { + name: &["dollar", "dollars", "usd", "aud", "cad"], + description: "Format a number as a dollar amount", + argument: ExpectedTypes::IntOrFloat, + handler: |_, input| decorator_currency(input, "$") +}; + +const EURO : DecoratorDefinition = DecoratorDefinition { + name: &["euro", "euros"], + description: "Format a number as a euro amount", + argument: ExpectedTypes::IntOrFloat, + handler: |_, input| decorator_currency(input, "€") +}; + +const POUND : DecoratorDefinition = DecoratorDefinition { + name: &["pound", "pounds"], + description: "Format a number as a pound amount", + argument: ExpectedTypes::IntOrFloat, + handler: |_, input| decorator_currency(input, "£") +}; + +const YEN : DecoratorDefinition = DecoratorDefinition { + name: &["yen"], + description: "Format a number as a yen amount", + argument: ExpectedTypes::IntOrFloat, + handler: |_, input| decorator_currency(input, "¥") +}; + +const FLOAT : DecoratorDefinition = DecoratorDefinition { + name: &["float"], + description: "Format a number as floating point", + argument: ExpectedTypes::IntOrFloat, + handler: |_, input| Ok(Value::Float(input.as_float().unwrap()).as_string()) +}; + +const INT : DecoratorDefinition = DecoratorDefinition { + name: &["int", "integer"], + description: "Format a number as an integer", + argument: ExpectedTypes::IntOrFloat, + handler: |_, input| Ok(Value::Integer(input.as_int().unwrap()).as_string()) +}; + +const BOOL : DecoratorDefinition = DecoratorDefinition { + name: &["bool", "boolean"], + description: "Format a number as a boolean", + argument: ExpectedTypes::IntOrFloat, + handler: |_, input| Ok(Value::Boolean(input.as_bool()).as_string()) +}; #[cfg(test)] mod test_builtin_functions { @@ -200,62 +286,62 @@ mod test_builtin_functions { #[test] fn test_hex() { - assert_eq!("0xff", decorator_hex(&Value::Integer(255)).unwrap()); - assert_eq!("0xff", decorator_hex(&Value::Float(255.1)).unwrap()); + assert_eq!("0xff", HEX.call(&Value::Integer(255)).unwrap()); + assert_eq!("0xff", HEX.call(&Value::Float(255.1)).unwrap()); } #[test] fn test_bin() { - assert_eq!("0b11111111", decorator_bin(&Value::Integer(255)).unwrap()); - assert_eq!("0b11111111", decorator_bin(&Value::Float(255.1)).unwrap()); + assert_eq!("0b11111111", BIN.call(&Value::Integer(255)).unwrap()); + assert_eq!("0b11111111", BIN.call(&Value::Float(255.1)).unwrap()); } #[test] fn test_oct() { - assert_eq!("0o10", decorator_oct(&Value::Integer(8)).unwrap()); - assert_eq!("0o10", decorator_oct(&Value::Float(8.1)).unwrap()); + assert_eq!("0o10", OCT.call(&Value::Integer(8)).unwrap()); + assert_eq!("0o10", OCT.call(&Value::Float(8.1)).unwrap()); } #[test] fn test_sci() { - assert_eq!("8e0", decorator_sci(&Value::Integer(8)).unwrap()); - assert_eq!("-8.1e1", decorator_sci(&Value::Float(-81.0)).unwrap()); - assert_eq!("8.1e-2", decorator_sci(&Value::Float(0.081)).unwrap()); + assert_eq!("8e0", SCI.call(&Value::Integer(8)).unwrap()); + assert_eq!("-8.1e1", SCI.call(&Value::Float(-81.0)).unwrap()); + assert_eq!("8.1e-2", SCI.call(&Value::Float(0.081)).unwrap()); } #[test] fn test_float() { - assert_eq!("8.0", decorator_float(&Value::Integer(8)).unwrap()); - assert_eq!("81.0", decorator_float(&Value::Float(81.0)).unwrap()); - assert_eq!("0.0", decorator_float(&Value::Float(0.0000000001)).unwrap()); - assert_eq!("0.081", decorator_float(&Value::Float(0.081)).unwrap()); + assert_eq!("8.0", FLOAT.call(&Value::Integer(8)).unwrap()); + assert_eq!("81.0", FLOAT.call(&Value::Float(81.0)).unwrap()); + assert_eq!("0.0", FLOAT.call(&Value::Float(0.0000000001)).unwrap()); + assert_eq!("0.081", FLOAT.call(&Value::Float(0.081)).unwrap()); } #[test] fn test_int() { - assert_eq!("-8", decorator_int(&Value::Integer(-8)).unwrap()); - assert_eq!("81", decorator_int(&Value::Float(81.0)).unwrap()); - assert_eq!("0", decorator_int(&Value::Float(0.081)).unwrap()); + assert_eq!("-8", INT.call(&Value::Integer(-8)).unwrap()); + assert_eq!("81", INT.call(&Value::Float(81.0)).unwrap()); + assert_eq!("0", INT.call(&Value::Float(0.081)).unwrap()); } #[test] fn test_bool() { - assert_eq!("false", decorator_bool(&Value::Integer(0)).unwrap()); - assert_eq!("true", decorator_bool(&Value::Integer(81)).unwrap()); - assert_eq!("true", decorator_bool(&Value::Float(0.081)).unwrap()); + assert_eq!("false", BOOL.call(&Value::Integer(0)).unwrap()); + assert_eq!("true", BOOL.call(&Value::Integer(81)).unwrap()); + assert_eq!("true", BOOL.call(&Value::Float(0.081)).unwrap()); } #[test] fn test_dollars() { - assert_eq!("¥100.00", decorator_yen(&Value::Integer(100)).unwrap()); - assert_eq!("$1,000.00", decorator_dollars(&Value::Integer(1000)).unwrap()); - assert_eq!("€10,000.00", decorator_euros(&Value::Integer(10000)).unwrap()); - assert_eq!("£100,000.00", decorator_pounds(&Value::Integer(100000)).unwrap()); - assert_eq!("£1,000,000.00", decorator_pounds(&Value::Integer(1000000)).unwrap()); + assert_eq!("¥100.00", YEN.call(&Value::Integer(100)).unwrap()); + assert_eq!("$1,000.00", DOLLAR.call(&Value::Integer(1000)).unwrap()); + assert_eq!("€10,000.00", EURO.call(&Value::Integer(10000)).unwrap()); + assert_eq!("£100,000.00", POUND.call(&Value::Integer(100000)).unwrap()); + assert_eq!("£1,000,000.00", POUND.call(&Value::Integer(1000000)).unwrap()); } #[test] fn utc() { - assert_eq!("2022-03-20 14:05:33", decorator_utc(&Value::Integer(1647785133)).unwrap()); + assert_eq!("2022-03-20 14:05:33", UTC.call(&Value::Integer(1647785133)).unwrap()); } } \ No newline at end of file diff --git a/src/functions/math.rs b/src/functions/math.rs index 67007ec..449011c 100644 --- a/src/functions/math.rs +++ b/src/functions/math.rs @@ -2,6 +2,40 @@ use super::{FunctionDefinition, FunctionArgument, FunctionTable}; use crate::value::{Value, IntegerType}; use crate::errors::*; +const MIN : FunctionDefinition = FunctionDefinition { + name: "min", + description: "Returns the smallest numeric value from the supplied arguments", + arguments: || vec![ + FunctionArgument::new_plural("n", ExpectedTypes::IntOrFloat, false), + ], + handler: |_, args: &[Value]| { + let mut valid_args = args.into_iter().filter(|a|!a.as_float().unwrap().is_nan()).map(|a|a.clone()).collect::>(); + valid_args.sort_by(|a,b| a.as_float().unwrap().partial_cmp(&b.as_float().unwrap()).unwrap()); + if valid_args.is_empty() { + Ok(args[0].clone()) + } else { + Ok(valid_args[0].clone()) + } + } +}; + +const MAX : FunctionDefinition = FunctionDefinition { + name: "max", + description: "Returns the largest numeric value from the supplied arguments", + arguments: || vec![ + FunctionArgument::new_plural("n", ExpectedTypes::IntOrFloat, false), + ], + handler: |_, args: &[Value]| { + let mut valid_args = args.into_iter().filter(|a|!a.as_float().unwrap().is_nan()).map(|a|a.clone()).collect::>(); + valid_args.sort_by(|a,b| b.as_float().unwrap().partial_cmp(&a.as_float().unwrap()).unwrap()); + if valid_args.is_empty() { + Ok(args[0].clone()) + } else { + Ok(valid_args[0].clone()) + } + } +}; + const CEIL : FunctionDefinition = FunctionDefinition { name: "ceil", description: "Returns the nearest whole integer larger than n", @@ -120,6 +154,8 @@ const ROOT : FunctionDefinition = FunctionDefinition { /// Register string functions pub fn register_functions(table: &mut FunctionTable) { // Rounding functions + table.register(MIN); + table.register(MAX); table.register(CEIL); table.register(FLOOR); table.register(ROUND); @@ -136,8 +172,41 @@ pub fn register_functions(table: &mut FunctionTable) { #[cfg(test)] mod test_builtin_functions { + use crate::value::{FloatType}; use super::*; + #[test] + fn test_min() { + assert_eq!(Value::Integer(3), (MIN.handler)(&MIN, &[ + Value::Float(3.5), + Value::Integer(3), + Value::Integer(7), + Value::Float(FloatType::NAN) + ]).unwrap()); + assert_eq!(Value::Float(3.1), (MIN.handler)(&MIN, &[ + Value::Float(3.5), + Value::Float(3.1), + Value::Integer(7), + Value::Float(FloatType::NAN) + ]).unwrap()); + } + + #[test] + fn test_max() { + assert_eq!(Value::Integer(7), (MAX.handler)(&MAX, &[ + Value::Float(3.5), + Value::Integer(3), + Value::Integer(7), + Value::Float(FloatType::NAN) + ]).unwrap()); + assert_eq!(Value::Float(7.1), (MAX.handler)(&MAX, &[ + Value::Float(3.5), + Value::Integer(3), + Value::Float(7.1), + Value::Float(FloatType::NAN) + ]).unwrap()); + } + #[test] fn test_ceil() { assert_eq!(Value::Integer(4), (CEIL.handler)(&CEIL, &[Value::Float(3.5)]).unwrap()); diff --git a/src/handlers/callable.rs b/src/handlers/callable.rs index dc1c555..cd7078b 100644 --- a/src/handlers/callable.rs +++ b/src/handlers/callable.rs @@ -79,7 +79,9 @@ pub fn call_expression_handler(token: &mut Token, state: &mut ParserState) -> Op ).collect::>().join("\n").as_str(); output += format!("\n\nBuilt-in Decorators\n{}\n", divider).as_str(); - output += inline_sort(state.decorators.all()).join(", ").as_str(); + output += inline_sort(state.decorators.all()).into_iter().map(|f| + format!("@{}: {}", f, state.decorators.get(f).unwrap().description()) + ).collect::>().join("\n").as_str(); #[cfg(feature = "extensions")] if !state.extensions.all().is_empty() { diff --git a/src/lib.rs b/src/lib.rs index 024157c..e0d5ae1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,16 +50,16 @@ //! //! A number of functions and @decorators are available for expressions to use - add more using the state: //! ```rust -//! use lavendeux_parser::{ParserState, ParserError, FunctionDefinition, FunctionArgument, Value}; +//! use lavendeux_parser::{ParserState, ParserError, DecoratorDefinition, FunctionDefinition, FunctionArgument, Value}; //! use lavendeux_parser::errors::*; -//! -//! // Decorators take in a single value, and return a string representation -//! fn new_decorator_handler(arg: &Value) -> Result { -//! Ok(arg.as_string()) -//! } -//! +//! //! let mut state : ParserState = ParserState::new(); -//! state.decorators.register("new_decorator", new_decorator_handler); +//! state.decorators.register(DecoratorDefinition { +//! name: &["upper", "uppercase"], +//! description: "Outputs an uppercase version of the input", +//! argument: ExpectedTypes::Any, +//! handler: |_, input| Ok(input.as_string().to_uppercase()) +//! }); //! //! // Functions take in an array of values, and return a single value //! state.functions.register(FunctionDefinition { @@ -106,7 +106,6 @@ #![warn(missing_docs)] #![warn(rustdoc::missing_doc_code_examples)] -mod decorators; mod handlers; mod token; mod value; @@ -115,6 +114,9 @@ mod state; mod functions; pub use functions::{FunctionTable, FunctionDefinition, FunctionArgument}; +mod decorators; +pub use decorators::{DecoratorTable, DecoratorDefinition}; + #[cfg(feature = "extensions")] mod extensions; diff --git a/src/token.rs b/src/token.rs index 45102e4..bdbfc77 100644 --- a/src/token.rs +++ b/src/token.rs @@ -513,6 +513,7 @@ mod test_token { )); let t = Token::new("help()", &mut state).unwrap(); + assert_eq!(t.text(), "".to_string()); assert_eq!(true, t.text.contains("Built-in Functions")); assert_eq!(true, t.text.contains("Built-in Decorators"));