From 8363cd20eb86f2c584aa95c3b0203079dfc402b8 Mon Sep 17 00:00:00 2001 From: Jeff Date: Fri, 23 Jun 2023 23:36:48 -0400 Subject: [PATCH] Move built-in functions to expressive --- Cargo.toml | 1 + src/container.rs | 22 ++ src/context/mod.rs | 289 +++++++++++++++++++++- src/dir.rs | 27 +++ src/error/display.rs | 1 + src/error/mod.rs | 11 +- src/file.rs | 0 src/gui.rs | 0 src/internal.rs | 0 src/lib.rs | 561 +------------------------------------------ src/shell.rs | 17 ++ src/time.rs | 25 ++ 12 files changed, 400 insertions(+), 554 deletions(-) create mode 100644 src/container.rs create mode 100644 src/dir.rs create mode 100644 src/file.rs create mode 100644 src/gui.rs create mode 100644 src/internal.rs create mode 100644 src/shell.rs create mode 100644 src/time.rs diff --git a/Cargo.toml b/Cargo.toml index 1178d3f..dc2e432 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ regex = { version = "1.5.5", optional = true} serde = { version = "1.0.133", optional = true} serde_derive = { version = "1.0.133", optional = true} rand = { version = "0.8.5", optional = true} +chrono = "0.4.26" [features] serde_support = ["serde", "serde_derive"] diff --git a/src/container.rs b/src/container.rs new file mode 100644 index 0000000..24d185f --- /dev/null +++ b/src/container.rs @@ -0,0 +1,22 @@ +use crate::{Result, Value}; + +pub fn run(argument: &Value) -> Result { + todo!() +} +pub fn build(argument: &Value) -> Result { + todo!() +} +pub fn list(argument: &Value) -> Result { + todo!() +} + +pub mod image { + use super::*; + + pub fn build(argument: &Value) -> Result { + todo!() + } + pub fn list(argument: &Value) -> Result { + todo!() + } +} diff --git a/src/context/mod.rs b/src/context/mod.rs index a413b04..6d915db 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -4,10 +4,18 @@ //! This crate implements two basic variants, the `EmptyContext`, that returns `None` for each identifier and cannot be manipulated, and the `HashMapContext`, that stores its mappings in hash maps. //! The HashMapContext is type-safe and returns an error if the user tries to assign a value of a different type than before to an identifier. -use std::{collections::BTreeMap, fmt::Display}; +use std::{ + collections::BTreeMap, + fmt::Display, + fs, + io::{Read, Write}, + process::Command, +}; use crate::{ + container, function::Function, + time, value::{value_type::ValueType, Value}, EvalexprError, EvalexprResult, }; @@ -99,9 +107,7 @@ impl Context for VariableMap { } fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult { - match identifier { - _ => todo!(), - } + call_whale_function(identifier, argument) } } @@ -189,3 +195,278 @@ macro_rules! context_map { .map(|_| context) }}; } + +fn call_whale_function(identifier: &str, argument: &Value) -> Result { + match identifier { + "container::image::build" => container::image::build(argument), + "container::image::list" => container::image::list(argument), + "container::list" => container::list(argument), + "container::run" => container::run(argument), + "convert" => todo!(), + "dir::create" => { + let path = argument.as_string()?; + std::fs::create_dir_all(path).unwrap(); + + Ok(Value::Empty) + }, + "dir::read" => { + let path = argument.as_string()?; + let files = std::fs::read_dir(path) + .unwrap() + .map(|entry| entry.unwrap().file_name().into_string().unwrap_or_default()) + .collect(); + + Ok(Value::String(files)) + }, + "dir::remove" => { + let path = argument.as_string()?; + std::fs::remove_file(path).unwrap(); + + Ok(Value::Empty) + }, + "dir::trash" => todo!(), + "dnf::copr" => { + let repo_name = if let Ok(string) = argument.as_string() { + string + } else if let Ok(tuple) = argument.as_tuple() { + let mut repos = String::new(); + + for repo in tuple { + let repo = repo.as_string()?; + + repos.push_str(&repo); + repos.push(' '); + } + + repos + } else { + return Err(EvalexprError::ExpectedString { + actual: argument.clone(), + }); + }; + let script = format!("dnf -y copr enable {repo_name}"); + + Command::new("fish") + .arg("-c") + .arg(script) + .spawn() + .unwrap() + .wait() + .unwrap(); + + Ok(Value::Empty) + }, + "dnf::packages" => { + let tuple = argument.as_tuple()?; + let mut packages = String::new(); + + for package in tuple { + let package = package.as_string()?; + + packages.push_str(&package); + packages.push(' '); + } + let script = format!("dnf -y install {packages}"); + + Command::new("fish") + .arg("-c") + .arg(script) + .spawn() + .unwrap() + .wait() + .unwrap(); + + Ok(Value::Empty) + }, + "dnf::repos" => { + let tuple = argument.as_tuple()?; + let mut repos = String::new(); + + for repo in tuple { + let repo = repo.as_string()?; + + repos.push_str(&repo); + repos.push(' '); + } + let script = format!("dnf -y config-manager --add-repo {repos}"); + + Command::new("fish") + .arg("-c") + .arg(script) + .spawn() + .unwrap() + .wait() + .unwrap(); + + Ok(Value::Empty) + }, + "dnf::upgrade" => { + Command::new("fish") + .arg("-c") + .arg("dnf -y upgrade") + .spawn() + .unwrap() + .wait() + .unwrap(); + + Ok(Value::Empty) + }, + "file::append" => { + let strings = argument.as_tuple()?; + + if strings.len() < 2 { + return Err(EvalexprError::WrongFunctionArgumentAmount { + expected: 2, + actual: strings.len(), + }); + } + + let path = strings.first().unwrap().as_string()?; + let mut file = std::fs::OpenOptions::new().append(true).open(path).unwrap(); + + for content in &strings[1..] { + let content = content.as_string()?; + + file.write_all(content.as_bytes()).unwrap(); + } + + Ok(Value::Empty) + }, + "file::metadata" => { + let path = argument.as_string()?; + let metadata = std::fs::metadata(path).unwrap(); + + Ok(Value::String(format!("{:#?}", metadata))) + }, + "file::move" => { + let mut paths = argument.as_tuple()?; + + if paths.len() != 2 { + return Err(EvalexprError::WrongFunctionArgumentAmount { + expected: 2, + actual: paths.len(), + }); + } + + let to = paths.pop().unwrap().as_string()?; + let from = paths.pop().unwrap().as_string()?; + + std::fs::copy(&from, to) + .and_then(|_| std::fs::remove_file(from)) + .unwrap(); + + Ok(Value::Empty) + }, + "file::read" => { + let path = argument.as_string()?; + let mut contents = String::new(); + + fs::OpenOptions::new() + .read(true) + .create(false) + .open(&path) + .unwrap() + .read_to_string(&mut contents) + .unwrap(); + + Ok(Value::String(contents)) + }, + "file::remove" => { + let path = argument.as_string()?; + std::fs::remove_file(path).unwrap(); + + Ok(Value::Empty) + }, + "file::trash" => todo!(), + "file::write" => { + let strings = argument.as_tuple()?; + + if strings.len() < 2 { + return Err(EvalexprError::WrongFunctionArgumentAmount { + expected: 2, + actual: strings.len(), + }); + } + + let path = strings.first().unwrap().as_string()?; + let mut file = fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(path) + .unwrap(); + + for content in &strings[1..] { + let content = content.as_string()?; + + file.write_all(content.as_bytes()).unwrap(); + } + + Ok(Value::Empty) + }, + "gui::window" => todo!(), + "gui" => todo!(), + "map" => todo!(), + "network::download" => todo!(), + "network::hostname" => todo!(), + "network::scan" => todo!(), + "os::status" => { + Command::new("fish") + .arg("-c") + .arg("rpm-ostree status") + .spawn() + .unwrap() + .wait() + .unwrap(); + Ok(Value::Empty) + }, + "os::upgrade" => { + Command::new("fish") + .arg("-c") + .arg("rpm-ostree upgrade") + .spawn() + .unwrap() + .wait() + .unwrap(); + Ok(Value::Empty) + }, + "output" => { + println!("{}", argument); + Ok(Value::Empty) + }, + "random::boolean" => todo!(), + "random::float" => todo!(), + "random::int" => todo!(), + "random::string" => todo!(), + "run::async" => todo!(), + "run::sync" => todo!(), + "run" => todo!(), + "rust::packages" => todo!(), + "rust::toolchain" => todo!(), + "shell::bash" => todo!(), + "shell::fish" => todo!(), + "shell::nushell" => todo!(), + "shell::sh" => todo!(), + "shell::zsh" => todo!(), + "system::info" => todo!(), + "system::monitor" => todo!(), + "system::processes" => todo!(), + "system::services" => todo!(), + "system::users" => todo!(), + "time::now" => time::now(), + "time::today" => time::today(), + "time::tomorrow" => time::tomorrow(), + "time" => time::now(), + "toolbox::create" => todo!(), + "toolbox::enter" => todo!(), + "toolbox::image::build" => todo!(), + "toolbox::image::list" => todo!(), + "toolbox::list" => todo!(), + "trash" => todo!(), + "wait" => todo!(), + "watch" => todo!(), + _ => Err(EvalexprError::FunctionIdentifierNotFound( + identifier.to_string(), + )), + } +} diff --git a/src/dir.rs b/src/dir.rs new file mode 100644 index 0000000..e1c0a29 --- /dev/null +++ b/src/dir.rs @@ -0,0 +1,27 @@ +use crate::{Result, Value}; + +use std::fs; + +pub fn create(arg: &Value) -> Result { + let path = arg.as_string()?; + fs::create_dir_all(path)?; + + Ok(Value::Empty) +} + +pub fn read(arg: &Value) -> Result { + let path = arg.as_string()?; + + let file_list = fs::read_dir(path)? + .map(|entry| { + let file_name = entry.unwrap().file_name().into_string().unwrap(); + format!("{}\n", file_name) + }) + .collect::(); + + Ok(Value::String(file_list)) +} + +pub fn remove() -> Result { + Ok(Value::Empty) +} diff --git a/src/error/display.rs b/src/error/display.rs index e903f62..eff5807 100644 --- a/src/error/display.rs +++ b/src/error/display.rs @@ -121,6 +121,7 @@ impl fmt::Display for EvalexprError { write!(f, "This context does not allow disabling builtin functions") }, IllegalEscapeSequence(string) => write!(f, "Illegal escape sequence: {}", string), + FunctionFailure(message) => write!(f, "Function failure: {}", message), CustomMessage(message) => write!(f, "Error: {}", message), } } diff --git a/src/error/mod.rs b/src/error/mod.rs index 47acf8a..1372a96 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -215,10 +215,19 @@ pub enum EvalexprError { /// This context does not allow disabling builtin functions. BuiltinFunctionsCannotBeDisabled, + /// The function failed due to an external error. + FunctionFailure(String), + /// A custom error explained by its message. CustomMessage(String), } +impl From for EvalexprError { + fn from(value: std::io::Error) -> Self { + EvalexprError::FunctionFailure(value.to_string()) + } +} + impl EvalexprError { pub(crate) fn wrong_operator_argument_amount(actual: usize, expected: usize) -> Self { EvalexprError::WrongOperatorArgumentAmount { actual, expected } @@ -285,7 +294,7 @@ impl EvalexprError { pub fn expected_empty(actual: Value) -> Self { EvalexprError::ExpectedEmpty { actual } } - + /// Constructs `EvalexprError::ExpectedEmpty{actual}`. pub fn expected_map(actual: Value) -> Self { EvalexprError::ExpectedMap { actual } diff --git a/src/file.rs b/src/file.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/gui.rs b/src/gui.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/internal.rs b/src/internal.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/lib.rs b/src/lib.rs index 2bf53a5..f609467 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,552 +1,6 @@ -//! -//! ## Quickstart -//! -//! Add `evalexpr` as dependency to your `Cargo.toml`: -//! -//! ```toml -//! [dependencies] -//! evalexpr = "" -//! ``` -//! -//! Then you can use `evalexpr` to **evaluate expressions** like this: -//! -//! ```rust -//! use evalexpr::*; -//! -//! assert_eq!(eval("1 + 2 + 3"), Ok(Value::from(6))); -//! // `eval` returns a variant of the `Value` enum, -//! // while `eval_[type]` returns the respective type directly. -//! // Both can be used interchangeably. -//! assert_eq!(eval_int("1 + 2 + 3"), Ok(6)); -//! assert_eq!(eval("1 - 2 * 3"), Ok(Value::from(-5))); -//! assert_eq!(eval("1.0 + 2 * 3"), Ok(Value::from(7.0))); -//! assert_eq!(eval("true && 4 > 2"), Ok(Value::from(true))); -//! ``` -//! -//! You can **chain** expressions and **assign** to variables like this: -//! -//! ```rust -//! use evalexpr::*; -//! -//! let mut context = HashMapContext::new(); -//! // Assign 5 to a like this -//! assert_eq!(eval_empty_with_context_mut("a = 5", &mut context), Ok(EMPTY_VALUE)); -//! // The HashMapContext is type safe, so this will fail now -//! assert_eq!(eval_empty_with_context_mut("a = 5.0", &mut context), -//! Err(EvalexprError::expected_int(Value::from(5.0)))); -//! // We can check which value the context stores for a like this -//! assert_eq!(context.get_value("a"), Some(&Value::from(5))); -//! // And use the value in another expression like this -//! assert_eq!(eval_int_with_context_mut("a = a + 2; a", &mut context), Ok(7)); -//! // It is also possible to save a bit of typing by using an operator-assignment operator -//! assert_eq!(eval_int_with_context_mut("a += 2; a", &mut context), Ok(9)); -//! ``` -//! -//! And you can use **variables** and **functions** in expressions like this: -//! -//! ```rust -//! use evalexpr::*; -//! -//! let context = context_map! { -//! "five" => 5, -//! "twelve" => 12, -//! "f" => Function::new(|argument| { -//! if let Ok(int) = argument.as_int() { -//! Ok(Value::Int(int / 2)) -//! } else if let Ok(float) = argument.as_float() { -//! Ok(Value::Float(float / 2.0)) -//! } else { -//! Err(EvalexprError::expected_number(argument.clone())) -//! } -//! }), -//! "avg" => Function::new(|argument| { -//! let arguments = argument.as_tuple()?; -//! -//! if let (Value::Int(a), Value::Int(b)) = (&arguments[0], &arguments[1]) { -//! Ok(Value::Int((a + b) / 2)) -//! } else { -//! Ok(Value::Float((arguments[0].as_number()? + arguments[1].as_number()?) / 2.0)) -//! } -//! }) -//! }.unwrap(); // Do proper error handling here -//! -//! assert_eq!(eval_with_context("five + 8 > f(twelve)", &context), Ok(Value::from(true))); -//! // `eval_with_context` returns a variant of the `Value` enum, -//! // while `eval_[type]_with_context` returns the respective type directly. -//! // Both can be used interchangeably. -//! assert_eq!(eval_boolean_with_context("five + 8 > f(twelve)", &context), Ok(true)); -//! assert_eq!(eval_with_context("avg(2, 4) == 3", &context), Ok(Value::from(true))); -//! ``` -//! -//! You can also **precompile** expressions like this: -//! -//! ```rust -//! use evalexpr::*; -//! -//! let precompiled = build_operator_tree("a * b - c > 5").unwrap(); // Do proper error handling here -//! -//! let mut context = context_map! { -//! "a" => 6, -//! "b" => 2, -//! "c" => 3 -//! }.unwrap(); // Do proper error handling here -//! assert_eq!(precompiled.eval_with_context(&context), Ok(Value::from(true))); -//! -//! context.set_value("c".into(), 8.into()).unwrap(); // Do proper error handling here -//! assert_eq!(precompiled.eval_with_context(&context), Ok(Value::from(false))); -//! // `Node::eval_with_context` returns a variant of the `Value` enum, -//! // while `Node::eval_[type]_with_context` returns the respective type directly. -//! // Both can be used interchangeably. -//! assert_eq!(precompiled.eval_boolean_with_context(&context), Ok(false)); -//! ``` -//! -//! ## CLI -//! -//! While primarily meant to be used as a library, `evalexpr` is also available as a command line tool. -//! It can be installed and used as follows: -//! -//! ```bash -//! cargo install evalexpr -//! evalexpr 2 + 3 # outputs `5` to stdout. -//! ``` -//! -//! ## Features -//! -//! ### Operators -//! -//! This crate offers a set of binary and unary operators for building expressions. -//! Operators have a precedence to determine their order of evaluation, where operators of higher precedence are evaluated first. -//! The precedence should resemble that of most common programming languages, especially Rust. -//! Variables and values have a precedence of 200, and function literals have 190. -//! -//! Supported binary operators: -//! -//! | Operator | Precedence | Description | -//! |----------|------------|-------------| -//! | ^ | 120 | Exponentiation | -//! | * | 100 | Product | -//! | / | 100 | Division (integer if both arguments are integers, otherwise float) | -//! | % | 100 | Modulo (integer if both arguments are integers, otherwise float) | -//! | + | 95 | Sum or String Concatenation | -//! | - | 95 | Difference | -//! | < | 80 | Lower than | -//! | \> | 80 | Greater than | -//! | <= | 80 | Lower than or equal | -//! | \>= | 80 | Greater than or equal | -//! | == | 80 | Equal | -//! | != | 80 | Not equal | -//! | && | 75 | Logical and | -//! | || | 70 | Logical or | -//! | = | 50 | Assignment | -//! | += | 50 | Sum-Assignment or String-Concatenation-Assignment | -//! | -= | 50 | Difference-Assignment | -//! | *= | 50 | Product-Assignment | -//! | /= | 50 | Division-Assignment | -//! | %= | 50 | Modulo-Assignment | -//! | ^= | 50 | Exponentiation-Assignment | -//! | &&= | 50 | Logical-And-Assignment | -//! | ||= | 50 | Logical-Or-Assignment | -//! | , | 40 | Aggregation | -//! | ; | 0 | Expression Chaining | -//! -//! Supported unary operators: -//! -//! | Operator | Precedence | Description | -//! |----------|------------|-------------| -//! | - | 110 | Negation | -//! | ! | 110 | Logical not | -//! -//! Operators that take numbers as arguments can either take integers or floating point numbers. -//! If one of the arguments is a floating point number, all others are converted to floating point numbers as well, and the resulting value is a floating point number as well. -//! Otherwise, the result is an integer. -//! An exception to this is the exponentiation operator that always returns a floating point number. -//! Example: -//! -//! ```rust -//! use evalexpr::*; -//! -//! assert_eq!(eval("1 / 2"), Ok(Value::from(0))); -//! assert_eq!(eval("1.0 / 2"), Ok(Value::from(0.5))); -//! assert_eq!(eval("2^2"), Ok(Value::from(4.0))); -//! ``` -//! -//! #### The Aggregation Operator -//! -//! The aggregation operator aggregates a set of values into a tuple. -//! A tuple can contain arbitrary values, it is not restricted to a single type. -//! The operator is n-ary, so it supports creating tuples longer than length two. -//! Example: -//! -//! ```rust -//! use evalexpr::*; -//! -//! assert_eq!(eval("1, \"b\", 3"), -//! Ok(Value::from(vec![Value::from(1), Value::from("b"), Value::from(3)]))); -//! ``` -//! -//! To create nested tuples, use parentheses: -//! -//! ```rust -//! use evalexpr::*; -//! -//! assert_eq!(eval("1, 2, (true, \"b\")"), Ok(Value::from(vec![ -//! Value::from(1), -//! Value::from(2), -//! Value::from(vec![ -//! Value::from(true), -//! Value::from("b") -//! ]) -//! ]))); -//! ``` -//! -//! #### The Assignment Operator -//! -//! This crate features the assignment operator, that allows expressions to store their result in a variable in the expression context. -//! If an expression uses the assignment operator, it must be evaluated with a mutable context. -//! -//! Note that assignments are type safe when using the `HashMapContext`. -//! That means that if an identifier is assigned a value of a type once, it cannot be assigned a value of another type. -//! -//! ```rust -//! use evalexpr::*; -//! -//! let mut context = HashMapContext::new(); -//! assert_eq!(eval_with_context("a = 5", &context), Err(EvalexprError::ContextNotMutable)); -//! assert_eq!(eval_empty_with_context_mut("a = 5", &mut context), Ok(EMPTY_VALUE)); -//! assert_eq!(eval_empty_with_context_mut("a = 5.0", &mut context), -//! Err(EvalexprError::expected_int(5.0.into()))); -//! assert_eq!(eval_int_with_context("a", &context), Ok(5)); -//! assert_eq!(context.get_value("a"), Some(5.into()).as_ref()); -//! ``` -//! -//! For each binary operator, there exists an equivalent operator-assignment operator. -//! Here are some examples: -//! -//! ```rust -//! use evalexpr::*; -//! -//! assert_eq!(eval_int("a = 2; a *= 2; a += 2; a"), Ok(6)); -//! assert_eq!(eval_float("a = 2.2; a /= 2.0 / 4 + 1; a"), Ok(2.2 / (2.0 / 4.0 + 1.0))); -//! assert_eq!(eval_string("a = \"abc\"; a += \"def\"; a"), Ok("abcdef".to_string())); -//! assert_eq!(eval_boolean("a = true; a &&= false; a"), Ok(false)); -//! ``` -//! -//! #### The Expression Chaining Operator -//! -//! The expression chaining operator works as one would expect from programming languages that use the semicolon to end statements, like `Rust`, `C` or `Java`. -//! It has the special feature that it returns the value of the last expression in the expression chain. -//! If the last expression is terminated by a semicolon as well, then `Value::Empty` is returned. -//! Expression chaining is useful together with assignment to create small scripts. -//! -//! ```rust -//! use evalexpr::*; -//! -//! let mut context = HashMapContext::new(); -//! assert_eq!(eval("1;2;3;4;"), Ok(Value::Empty)); -//! assert_eq!(eval("1;2;3;4"), Ok(4.into())); -//! -//! // Initialization of variables via script -//! assert_eq!(eval_empty_with_context_mut("hp = 1; max_hp = 5; heal_amount = 3;", &mut context), -//! Ok(EMPTY_VALUE)); -//! // Precompile healing script -//! let healing_script = build_operator_tree("hp = min(hp + heal_amount, max_hp); hp").unwrap(); // Do proper error handling here -//! // Execute precompiled healing script -//! assert_eq!(healing_script.eval_int_with_context_mut(&mut context), Ok(4)); -//! assert_eq!(healing_script.eval_int_with_context_mut(&mut context), Ok(5)); -//! ``` -//! -//! ### Contexts -//! -//! An expression evaluator that just evaluates expressions would be useful already, but this crate can do more. -//! It allows using [*variables*](#variables), [*assignments*](#the-assignment-operator), [*statement chaining*](#the-expression-chaining-operator) and [*user-defined functions*](#user-defined-functions) within an expression. -//! When assigning to variables, the assignment is stored in a context. -//! When the variable is read later on, it is read from the context. -//! Contexts can be preserved between multiple calls to eval by creating them yourself. -//! Here is a simple example to show the difference between preserving and not preserving context between evaluations: -//! -//! ```rust -//! use evalexpr::*; -//! -//! assert_eq!(eval("a = 5;"), Ok(Value::from(()))); -//! // The context is not preserved between eval calls -//! assert_eq!(eval("a"), Err(EvalexprError::VariableIdentifierNotFound("a".to_string()))); -//! -//! let mut context = HashMapContext::new(); -//! assert_eq!(eval_with_context_mut("a = 5;", &mut context), Ok(Value::from(()))); -//! // Assignments require mutable contexts -//! assert_eq!(eval_with_context("a = 6", &context), Err(EvalexprError::ContextNotMutable)); -//! // The HashMapContext is type safe -//! assert_eq!(eval_with_context_mut("a = 5.5", &mut context), -//! Err(EvalexprError::ExpectedInt { actual: Value::from(5.5) })); -//! // Reading a variable does not require a mutable context -//! assert_eq!(eval_with_context("a", &context), Ok(Value::from(5))); -//! -//! ``` -//! -//! Note that the assignment is forgotten between the two calls to eval in the first example. -//! In the second part, the assignment is correctly preserved. -//! Note as well that to assign to a variable, the context needs to be passed as a mutable reference. -//! When passed as an immutable reference, an error is returned. -//! -//! Also, the `HashMapContext` is type safe. -//! This means that assigning to `a` again with a different type yields an error. -//! Type unsafe contexts may be implemented if requested. -//! For reading `a`, it is enough to pass an immutable reference. -//! -//! Contexts can also be manipulated in code. -//! Take a look at the following example: -//! -//! ```rust -//! use evalexpr::*; -//! -//! let mut context = HashMapContext::new(); -//! // We can set variables in code like this... -//! context.set_value("a".into(), 5.into()); -//! // ...and read from them in expressions -//! assert_eq!(eval_int_with_context("a", &context), Ok(5)); -//! // We can write or overwrite variables in expressions... -//! assert_eq!(eval_with_context_mut("a = 10; b = 1.0;", &mut context), Ok(().into())); -//! // ...and read the value in code like this -//! assert_eq!(context.get_value("a"), Some(&Value::from(10))); -//! assert_eq!(context.get_value("b"), Some(&Value::from(1.0))); -//! ``` -//! -//! Contexts are also required for user-defined functions. -//! Those can be passed one by one with the `set_function` method, but it might be more convenient to use the `context_map!` macro instead: -//! -//! ```rust -//! use evalexpr::*; -//! -//! let context = context_map!{ -//! "f" => Function::new(|args| Ok(Value::from(args.as_int()? + 5))), -//! }.unwrap_or_else(|error| panic!("Error creating context: {}", error)); -//! assert_eq!(eval_int_with_context("f 5", &context), Ok(10)); -//! ``` -//! -//! For more information about user-defined functions, refer to the respective [section](#user-defined-functions). -//! -//! ### Builtin Functions -//! -//! This crate offers a set of builtin functions (see below for a full list). -//! They can be disabled if needed as follows: -//! -//! ```rust -//! use evalexpr::*; -//! let mut context = HashMapContext::new(); -//! assert_eq!(eval_with_context("max(1,3)",&context),Ok(Value::from(3))); -//! context.set_builtin_functions_disabled(true).unwrap(); // Do proper error handling here -//! assert_eq!(eval_with_context("max(1,3)",&context),Err(EvalexprError::FunctionIdentifierNotFound(String::from("max")))); -//! ``` -//! -//! Not all contexts support enabling or disabling builtin functions. -//! Specifically the `EmptyContext` has builtin functions disabled by default, and they cannot be enabled. -//! Symmetrically, the `EmptyContextWithBuiltinFunctions` has builtin functions enabled by default, and they cannot be disabled. -//! -//! | Identifier | Argument Amount | Argument Types | Description | -//! |----------------------|-----------------|-------------------------------|-------------| -//! | `min` | >= 1 | Numeric | Returns the minimum of the arguments | -//! | `max` | >= 1 | Numeric | Returns the maximum of the arguments | -//! | `len` | 1 | String/Tuple | Returns the character length of a string, or the amount of elements in a tuple (not recursively) | -//! | `floor` | 1 | Numeric | Returns the largest integer less than or equal to a number | -//! | `round` | 1 | Numeric | Returns the nearest integer to a number. Rounds half-way cases away from 0.0 | -//! | `ceil` | 1 | Numeric | Returns the smallest integer greater than or equal to a number | -//! | `if` | 3 | Boolean, Any, Any | If the first argument is true, returns the second argument, otherwise, returns the third | -//! | `contains` | 2 | Tuple, any non-tuple | Returns true if second argument exists in first tuple argument. | -//! | `contains_any` | 2 | Tuple, Tuple of any non-tuple | Returns true if one of the values in the second tuple argument exists in first tuple argument. | -//! | `typeof` | 1 | Any | returns "string", "float", "int", "boolean", "tuple", or "empty" depending on the type of the argument | -//! | `math::is_nan` | 1 | Numeric | Returns true if the argument is the floating-point value NaN, false if it is another floating-point value, and throws an error if it is not a number | -//! | `math::is_finite` | 1 | Numeric | Returns true if the argument is a finite floating-point number, false otherwise | -//! | `math::is_infinite` | 1 | Numeric | Returns true if the argument is an infinite floating-point number, false otherwise | -//! | `math::is_normal` | 1 | Numeric | Returns true if the argument is a floating-point number that is neither zero, infinite, [subnormal](https://en.wikipedia.org/wiki/Subnormal_number), or NaN, false otherwise | -//! | `math::ln` | 1 | Numeric | Returns the natural logarithm of the number | -//! | `math::log` | 2 | Numeric, Numeric | Returns the logarithm of the number with respect to an arbitrary base | -//! | `math::log2` | 1 | Numeric | Returns the base 2 logarithm of the number | -//! | `math::log10` | 1 | Numeric | Returns the base 10 logarithm of the number | -//! | `math::exp` | 1 | Numeric | Returns `e^(number)`, (the exponential function) | -//! | `math::exp2` | 1 | Numeric | Returns `2^(number)` | -//! | `math::pow` | 2 | Numeric, Numeric | Raises a number to the power of the other number | -//! | `math::cos` | 1 | Numeric | Computes the cosine of a number (in radians) | -//! | `math::acos` | 1 | Numeric | Computes the arccosine of a number. The return value is in radians in the range [0, pi] or NaN if the number is outside the range [-1, 1] | -//! | `math::cosh` | 1 | Numeric | Hyperbolic cosine function | -//! | `math::acosh` | 1 | Numeric | Inverse hyperbolic cosine function | -//! | `math::sin` | 1 | Numeric | Computes the sine of a number (in radians) | -//! | `math::asin` | 1 | Numeric | Computes the arcsine of a number. The return value is in radians in the range [-pi/2, pi/2] or NaN if the number is outside the range [-1, 1] | -//! | `math::sinh` | 1 | Numeric | Hyperbolic sine function | -//! | `math::asinh` | 1 | Numeric | Inverse hyperbolic sine function | -//! | `math::tan` | 1 | Numeric | Computes the tangent of a number (in radians) | -//! | `math::atan` | 1 | Numeric | Computes the arctangent of a number. The return value is in radians in the range [-pi/2, pi/2] | -//! | `math::atan2` | 2 | Numeric, Numeric | Computes the four quadrant arctangent in radians | -//! | `math::tanh` | 1 | Numeric | Hyperbolic tangent function | -//! | `math::atanh` | 1 | Numeric | Inverse hyperbolic tangent function. | -//! | `math::sqrt` | 1 | Numeric | Returns the square root of a number. Returns NaN for a negative number | -//! | `math::cbrt` | 1 | Numeric | Returns the cube root of a number | -//! | `math::hypot` | 2 | Numeric | Calculates the length of the hypotenuse of a right-angle triangle given legs of length given by the two arguments | -//! | `math::abs` | 1 | Numeric | Returns the absolute value of a number, returning an integer if the argument was an integer, and a float otherwise | -//! | `str::regex_matches` | 2 | String, String | Returns true if the first argument matches the regex in the second argument (Requires `regex_support` feature flag) | -//! | `str::regex_replace` | 3 | String, String, String | Returns the first argument with all matches of the regex in the second argument replaced by the third argument (Requires `regex_support` feature flag) | -//! | `str::to_lowercase` | 1 | String | Returns the lower-case version of the string | -//! | `str::to_uppercase` | 1 | String | Returns the upper-case version of the string | -//! | `str::trim` | 1 | String | Strips whitespace from the start and the end of the string | -//! | `str::from` | >= 0 | Any | Returns passed value as string | -//! | `bitand` | 2 | Int | Computes the bitwise and of the given integers | -//! | `bitor` | 2 | Int | Computes the bitwise or of the given integers | -//! | `bitxor` | 2 | Int | Computes the bitwise xor of the given integers | -//! | `bitnot` | 1 | Int | Computes the bitwise not of the given integer | -//! | `shl` | 2 | Int | Computes the given integer bitwise shifted left by the other given integer | -//! | `shr` | 2 | Int | Computes the given integer bitwise shifted right by the other given integer | -//! | `random` | 0 | Empty | Return a random float between 0 and 1. Requires the `rand` feature flag. | -//! -//! The `min` and `max` functions can deal with a mixture of integer and floating point arguments. -//! If the maximum or minimum is an integer, then an integer is returned. -//! Otherwise, a float is returned. -//! -//! The regex functions require the feature flag `regex_support`. -//! -//! ### Values -//! -//! Operators take values as arguments and produce values as results. -//! Values can be booleans, integer or floating point numbers, strings, tuples or the empty type. -//! Values are denoted as displayed in the following table. -//! -//! | Value type | Example | -//! |------------|---------| -//! | `Value::String` | `"abc"`, `""`, `"a\"b\\c"` | -//! | `Value::Boolean` | `true`, `false` | -//! | `Value::Int` | `3`, `-9`, `0`, `135412`, `0xfe02`, `-0x1e` | -//! | `Value::Float` | `3.`, `.35`, `1.00`, `0.5`, `123.554`, `23e4`, `-2e-3`, `3.54e+2` | -//! | `Value::Tuple` | `(3, 55.0, false, ())`, `(1, 2)` | -//! | `Value::Empty` | `()` | -//! -//! Integers are internally represented as `i64`, and floating point numbers are represented as `f64`. -//! Tuples are represented as `Vec` and empty values are not stored, but represented by Rust's unit type `()` where necessary. -//! -//! There exist type aliases for some of the types. -//! They include `IntType`, `FloatType`, `TupleType` and `EmptyType`. -//! -//! Values can be constructed either directly or using the `From` trait. -//! They can be decomposed using the `Value::as_[type]` methods. -//! The type of a value can be checked using the `Value::is_[type]` methods. -//! -//! **Examples for constructing a value:** -//! -//! | Code | Result | -//! |------|--------| -//! | `Value::from(4)` | `Value::Int(4)` | -//! | `Value::from(4.4)` | `Value::Float(4.4)` | -//! | `Value::from(true)` | `Value::Boolean(true)` | -//! | `Value::from(vec![Value::from(3)])` | `Value::Tuple(vec![Value::Int(3)])` | -//! -//! **Examples for deconstructing a value:** -//! -//! | Code | Result | -//! |------|--------| -//! | `Value::from(4).as_int()` | `Ok(4)` | -//! | `Value::from(4.4).as_float()` | `Ok(4.4)` | -//! | `Value::from(true).as_int()` | `Err(Error::ExpectedInt {actual: Value::Boolean(true)})` | -//! -//! Values have a precedence of 200. -//! -//! ### Variables -//! -//! This crate allows to compile parameterizable formulas by using variables. -//! A variable is a literal in the formula, that does not contain whitespace or can be parsed as value. -//! For working with variables, a [context](#contexts) is required. -//! It stores the mappings from variables to their values. -//! -//! Variables do not have fixed types in the expression itself, but are typed by the context. -//! Once a variable is assigned a value of a specific type, it cannot be assigned a value of another type. -//! This might change in the future and can be changed by using a type-unsafe context (not provided by this crate as of now). -//! -//! Here are some examples and counter-examples on expressions that are interpreted as variables: -//! -//! | Expression | Variable? | Explanation | -//! |------------|--------|-------------| -//! | `a` | yes | | -//! | `abc` | yes | | -//! | `a EvalexprResult`. -//! The definition needs to be included in the [`Context`](#contexts) that is used for evaluation. -//! As of now, functions cannot be defined within the expression, but that might change in the future. -//! -//! The function gets passed what ever value is directly behind it, be it a tuple or a single values. -//! If there is no value behind a function, it is interpreted as a variable instead. -//! More specifically, a function needs to be followed by either an opening brace `(`, another literal, or a value. -//! While not including special support for multi-valued functions, they can be realized by requiring a single tuple argument. -//! -//! Be aware that functions need to verify the types of values that are passed to them. -//! The `error` module contains some shortcuts for verification, and error types for passing a wrong value type. -//! Also, most numeric functions need to distinguish between being called with integers or floating point numbers, and act accordingly. -//! -//! Here are some examples and counter-examples on expressions that are interpreted as function calls: -//! -//! | Expression | Function? | Explanation | -//! |------------|--------|-------------| -//! | `a v` | yes | | -//! | `x 5.5` | yes | | -//! | `a (3, true)` | yes | | -//! | `a b 4` | yes | Call `a` with the result of calling `b` with `4` | -//! | `5 b` | no | Error, value cannot be followed by a literal | -//! | `12 3` | no | Error, value cannot be followed by a value | -//! | `a 5 6` | no | Error, function call cannot be followed by a value | -//! -//! Functions have a precedence of 190. -//! -//! ### [Serde](https://serde.rs) -//! -//! To use this crate with serde, the `serde_support` feature flag has to be set. -//! This can be done like this in the `Cargo.toml`: -//! -//! ```toml -//! [dependencies] -//! evalexpr = {version = "7", features = ["serde_support"]} -//! ``` -//! -//! This crate implements `serde::de::Deserialize` for its type `Node` that represents a parsed expression tree. -//! The implementation expects a [serde `string`](https://serde.rs/data-model.html) as input. -//! Example parsing with [ron format](docs.rs/ron): -//! -//! ```rust -//! # #[cfg(feature = "serde_support")] { -//! extern crate ron; -//! use evalexpr::*; -//! -//! let mut context = context_map!{ -//! "five" => 5 -//! }.unwrap(); // Do proper error handling here -//! -//! // In ron format, strings are surrounded by " -//! let serialized_free = "\"five * five\""; -//! match ron::de::from_str::(serialized_free) { -//! Ok(free) => assert_eq!(free.eval_with_context(&context), Ok(Value::from(25))), -//! Err(error) => { -//! () // Handle error -//! } -//! } -//! # } -//! ``` -//! -//! With `serde`, expressions can be integrated into arbitrarily complex data. -//! -//! The crate also implements `Serialize` and `Deserialize` for the `HashMapContext`, -//! but note that only the variables get (de)serialized, not the functions. -//! -//! ## License -//! -//! This crate is primarily distributed under the terms of the MIT license. -//! See [LICENSE](LICENSE) for details. -//! - -#![deny(missing_docs)] +/*! +Expressive, the whale language. +*/ #![forbid(unsafe_code)] #[cfg(feature = "regex_support")] @@ -570,13 +24,22 @@ pub use crate::{ value::{value_type::ValueType, EmptyType, FloatType, IntType, TupleType, Value, EMPTY_VALUE}, }; +pub type Result = std::result::Result; + +mod container; mod context; +mod dir; pub mod error; #[cfg(feature = "serde_support")] mod feature_serde; +mod file; mod function; +mod gui; mod interface; +mod internal; mod operator; +mod shell; +mod time; mod token; mod tree; mod value; diff --git a/src/shell.rs b/src/shell.rs new file mode 100644 index 0000000..12bb47b --- /dev/null +++ b/src/shell.rs @@ -0,0 +1,17 @@ +use crate::{Result, Value}; + +use std::process::Command; + +pub fn fish(arg: &Value) -> Result { + let fish_command = arg.as_string()?; + + Command::new("fish") + .arg("-c") + .arg(fish_command) + .spawn() + .unwrap() + .wait() + .unwrap(); + + Ok(Value::Empty) +} diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..ee4b5f6 --- /dev/null +++ b/src/time.rs @@ -0,0 +1,25 @@ +use crate::{Result, Value}; + +use chrono::{Days, Utc}; + +pub fn now() -> Result { + let now = Utc::now().time().to_string(); + + Ok(Value::String(now)) +} + +pub fn today() -> Result { + let today = Utc::now().date_naive().to_string(); + + Ok(Value::String(today)) +} + +pub fn tomorrow() -> Result { + let tomorrow = Utc::now() + .checked_add_days(Days::new(1)) + .unwrap() + .date_naive() + .to_string(); + + Ok(Value::String(tomorrow)) +}