Move built-in functions to expressive
This commit is contained in:
parent
fda852f69a
commit
8363cd20eb
@ -18,6 +18,7 @@ regex = { version = "1.5.5", optional = true}
|
|||||||
serde = { version = "1.0.133", optional = true}
|
serde = { version = "1.0.133", optional = true}
|
||||||
serde_derive = { version = "1.0.133", optional = true}
|
serde_derive = { version = "1.0.133", optional = true}
|
||||||
rand = { version = "0.8.5", optional = true}
|
rand = { version = "0.8.5", optional = true}
|
||||||
|
chrono = "0.4.26"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
serde_support = ["serde", "serde_derive"]
|
serde_support = ["serde", "serde_derive"]
|
||||||
|
22
src/container.rs
Normal file
22
src/container.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
use crate::{Result, Value};
|
||||||
|
|
||||||
|
pub fn run(argument: &Value) -> Result<Value> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
pub fn build(argument: &Value) -> Result<Value> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
pub fn list(argument: &Value) -> Result<Value> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod image {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn build(argument: &Value) -> Result<Value> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
pub fn list(argument: &Value) -> Result<Value> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
@ -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.
|
//! 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.
|
//! 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::{
|
use crate::{
|
||||||
|
container,
|
||||||
function::Function,
|
function::Function,
|
||||||
|
time,
|
||||||
value::{value_type::ValueType, Value},
|
value::{value_type::ValueType, Value},
|
||||||
EvalexprError, EvalexprResult,
|
EvalexprError, EvalexprResult,
|
||||||
};
|
};
|
||||||
@ -99,9 +107,7 @@ impl Context for VariableMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult<Value> {
|
fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult<Value> {
|
||||||
match identifier {
|
call_whale_function(identifier, argument)
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,3 +195,278 @@ macro_rules! context_map {
|
|||||||
.map(|_| context)
|
.map(|_| context)
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn call_whale_function(identifier: &str, argument: &Value) -> Result<Value, EvalexprError> {
|
||||||
|
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(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
27
src/dir.rs
Normal file
27
src/dir.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use crate::{Result, Value};
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
pub fn create(arg: &Value) -> Result<Value> {
|
||||||
|
let path = arg.as_string()?;
|
||||||
|
fs::create_dir_all(path)?;
|
||||||
|
|
||||||
|
Ok(Value::Empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(arg: &Value) -> Result<Value> {
|
||||||
|
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::<String>();
|
||||||
|
|
||||||
|
Ok(Value::String(file_list))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove() -> Result<Value> {
|
||||||
|
Ok(Value::Empty)
|
||||||
|
}
|
@ -121,6 +121,7 @@ impl fmt::Display for EvalexprError {
|
|||||||
write!(f, "This context does not allow disabling builtin functions")
|
write!(f, "This context does not allow disabling builtin functions")
|
||||||
},
|
},
|
||||||
IllegalEscapeSequence(string) => write!(f, "Illegal escape sequence: {}", string),
|
IllegalEscapeSequence(string) => write!(f, "Illegal escape sequence: {}", string),
|
||||||
|
FunctionFailure(message) => write!(f, "Function failure: {}", message),
|
||||||
CustomMessage(message) => write!(f, "Error: {}", message),
|
CustomMessage(message) => write!(f, "Error: {}", message),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -215,10 +215,19 @@ pub enum EvalexprError {
|
|||||||
/// This context does not allow disabling builtin functions.
|
/// This context does not allow disabling builtin functions.
|
||||||
BuiltinFunctionsCannotBeDisabled,
|
BuiltinFunctionsCannotBeDisabled,
|
||||||
|
|
||||||
|
/// The function failed due to an external error.
|
||||||
|
FunctionFailure(String),
|
||||||
|
|
||||||
/// A custom error explained by its message.
|
/// A custom error explained by its message.
|
||||||
CustomMessage(String),
|
CustomMessage(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for EvalexprError {
|
||||||
|
fn from(value: std::io::Error) -> Self {
|
||||||
|
EvalexprError::FunctionFailure(value.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl EvalexprError {
|
impl EvalexprError {
|
||||||
pub(crate) fn wrong_operator_argument_amount(actual: usize, expected: usize) -> Self {
|
pub(crate) fn wrong_operator_argument_amount(actual: usize, expected: usize) -> Self {
|
||||||
EvalexprError::WrongOperatorArgumentAmount { actual, expected }
|
EvalexprError::WrongOperatorArgumentAmount { actual, expected }
|
||||||
|
0
src/file.rs
Normal file
0
src/file.rs
Normal file
0
src/gui.rs
Normal file
0
src/gui.rs
Normal file
0
src/internal.rs
Normal file
0
src/internal.rs
Normal file
561
src/lib.rs
561
src/lib.rs
@ -1,552 +1,6 @@
|
|||||||
//!
|
/*!
|
||||||
//! ## Quickstart
|
Expressive, the whale language.
|
||||||
//!
|
*/
|
||||||
//! Add `evalexpr` as dependency to your `Cargo.toml`:
|
|
||||||
//!
|
|
||||||
//! ```toml
|
|
||||||
//! [dependencies]
|
|
||||||
//! evalexpr = "<desired version>"
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! 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<Value>` 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<b` | no | Expression is interpreted as variable `a`, operator `<` and variable `b` |
|
|
||||||
//! | `a b` | no | Expression is interpreted as function `a` applied to argument `b` |
|
|
||||||
//! | `123` | no | Expression is interpreted as `Value::Int` |
|
|
||||||
//! | `true` | no | Expression is interpreted as `Value::Bool` |
|
|
||||||
//! | `.34` | no | Expression is interpreted as `Value::Float` |
|
|
||||||
//!
|
|
||||||
//! Variables have a precedence of 200.
|
|
||||||
//!
|
|
||||||
//! ### User-Defined Functions
|
|
||||||
//!
|
|
||||||
//! This crate allows to define arbitrary functions to be used in parsed expressions.
|
|
||||||
//! A function is defined as a `Function` instance, wrapping an `fn(&Value) -> EvalexprResult<Value>`.
|
|
||||||
//! 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::<Node>(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)]
|
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
#[cfg(feature = "regex_support")]
|
#[cfg(feature = "regex_support")]
|
||||||
@ -570,13 +24,22 @@ pub use crate::{
|
|||||||
value::{value_type::ValueType, EmptyType, FloatType, IntType, TupleType, Value, EMPTY_VALUE},
|
value::{value_type::ValueType, EmptyType, FloatType, IntType, TupleType, Value, EMPTY_VALUE},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, EvalexprError>;
|
||||||
|
|
||||||
|
mod container;
|
||||||
mod context;
|
mod context;
|
||||||
|
mod dir;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
#[cfg(feature = "serde_support")]
|
#[cfg(feature = "serde_support")]
|
||||||
mod feature_serde;
|
mod feature_serde;
|
||||||
|
mod file;
|
||||||
mod function;
|
mod function;
|
||||||
|
mod gui;
|
||||||
mod interface;
|
mod interface;
|
||||||
|
mod internal;
|
||||||
mod operator;
|
mod operator;
|
||||||
|
mod shell;
|
||||||
|
mod time;
|
||||||
mod token;
|
mod token;
|
||||||
mod tree;
|
mod tree;
|
||||||
mod value;
|
mod value;
|
||||||
|
17
src/shell.rs
Normal file
17
src/shell.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
use crate::{Result, Value};
|
||||||
|
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
pub fn fish(arg: &Value) -> Result<Value> {
|
||||||
|
let fish_command = arg.as_string()?;
|
||||||
|
|
||||||
|
Command::new("fish")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(fish_command)
|
||||||
|
.spawn()
|
||||||
|
.unwrap()
|
||||||
|
.wait()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Ok(Value::Empty)
|
||||||
|
}
|
25
src/time.rs
Normal file
25
src/time.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
use crate::{Result, Value};
|
||||||
|
|
||||||
|
use chrono::{Days, Utc};
|
||||||
|
|
||||||
|
pub fn now() -> Result<Value> {
|
||||||
|
let now = Utc::now().time().to_string();
|
||||||
|
|
||||||
|
Ok(Value::String(now))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn today() -> Result<Value> {
|
||||||
|
let today = Utc::now().date_naive().to_string();
|
||||||
|
|
||||||
|
Ok(Value::String(today))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tomorrow() -> Result<Value> {
|
||||||
|
let tomorrow = Utc::now()
|
||||||
|
.checked_add_days(Days::new(1))
|
||||||
|
.unwrap()
|
||||||
|
.date_naive()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
Ok(Value::String(tomorrow))
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user