expression_parser
TODO: think of a new name.
Why
- Library first
- JSON support (copy paste your json, should be valid code)
- Immutable
- Compiled code externaly saveable (using serde)
- No external calls build-in (you can add those yourself if you want)
Non Goals
- Speed, speed is nice but not a goal
Examples
Take a look at the expression example:
cargo run --example expression 1 + 12
Or even better use the REPL:
cargo run --example repl
For syntax check the examples page and the rest of the Github Pages
library usage
Simple example:
#![allow(unused)] fn main() { use expression_parser::{Environment, ExpressionFile}; let result = ExpressionFile::run("1 + 5 - 2", &mut Environment::default()); assert_eq!(Ok(4.0.into()), result); }
Example with variable assignment:
#![allow(unused)] fn main() { use expression_parser::{Environment, ExpressionFile, ExpressionValue}; let input = r#" a = [1, 2, 3]; b = [3, 2, 1]; c = concat(a, b); d = concat(b, a); concat(c, [4,4], d); "#; let file = ExpressionFile::parse(input)?; let evaluated = ExpressionFile::eval(file, &mut Environment::default()); assert_eq!( ExpressionValue::from(vec![ 1, 2, 3, 3, 2, 1, 4, 4, 3, 2, 1, 1, 2, 3 ]), evaluated.unwrap() ); }
For better examples take a look at the library usage page
Introduction
Basic types
Types are nearly equal to JSON types:
- String
"Text";
"More text";
// unicode rust
"ok\u{00e9}"; // prints oké
// also json unicode
"ok\u00e9"; // prints oké as well
"prints\t\tescaped\n\"characters\"\\";
// prints escaped
// "characters"\
- Number
123;
12.345;
1.23e6; // 1230000
1.23e-3; // 0.00123
- Boolean
true;
false;
- Null
null;
- List
[ 1, 2, 3 ];
["test", "test"];
// also mixed type lists
[1, "test", false];
- Map
// just your regular json
{
"test": 123,
"list": [1,2,3],
"map": {
"nested": true
}
}
Extra Types:
- Function
// simple example
{x => x + x}
Variables
Creating new variables can be done just by using the =
symbol.
Example:
a = 1;
b = 2;
c = a + b;
Currently there is no garbage collection, but if you want you can manually delete variables with:
a = 1;
unset a;
Built-in Functions
Add
Adds the two arguments together
Usage:
first = 1 + 1 == 2;
second = 123 + 321 == 444;
third = -321 + 321 == 0;
All
Checks if all arguments are truthy
Usage:
first = all([1, true, [1,2,3], {"test": true}]);
second = all([1, false, [1,2,3], {"test": true}]) == false;
And
If the first argument is truthy returns the second argument, otherwise returns the first argument
Usage:
first = true and true;
second = 1 and true;
third = [1,2,3] and true;
fourth = 0 and 1234 == 0;
Any
Checks if any arguments are truthy
Usage:
first = any([1, false, [1,2,3], {"test": true}]);
second = any([0.0, true, [], {}]);
third = any([0.0, false, [], {}]) == false;
Assert
Raises an error with the given message if the first argument is not truthy
Usage:
one = assert(true == true);
two = assert([1,2,3]) == [1,2,3];
# with an optional error message
three = assert(1, "not truthy") == 1;
four = try(assert(1 == 0, "panic"), "default") == "default";
Call
Calls the function with given arguments
Usage:
my_function = {x => x + 25};
one = my_function.(5) == 30;
# the same as:
two = call(my_function, 5) == 30;
one and two
Concat
Combines two or more lists into one
Usage:
first = concat([1,2], [3,4], [5,6]) == [1,2,3,4,5,6];
second = concat("test", "-", "test") == "test-test";
Contains
Checks if the seconds argument is in the first argument
Usage:
first = contains("testing", "test");
second = contains([1,2,3,4], 3);
Cos
Calculates the cosine of the number
Usage:
first = cos(0) == 1;
Div
Divides the second argument from the first argument
Usage:
first = 1 / 5 == 0.2;
second = 50 / 50 == 1;
third = 150 / 6 == 25;
Equal
Compares if the two arguments are equal
Usage:
first = 1 == 1;
second = [1,2,3] == [1,2,3];
third = {"test": true} == {"test": true};
Error
Raises an error with the given message
Usage:
# an error with "something went wrong" will be catched and returns "default"
one = try(error("something went wrong"), "default") == "default";
Format
Formats the arguments into the template, only positional arguments are supported.
Usage:
one = format("{} + {} = {}", 1, 1, 2) == "1 + 1 = 2";
two = format("{2} - {1} = {0}", 5, 8, 13) == "13 - 8 = 5";
three = format("{}{}", "abc", "def") == "abcdef";
one and two and three
Get
Gets the value from a list or a map
Usage:
first = get([1, 2, 3], 1) == 2;
// throws error:
// get([1, 2, 3], -1.23) != 2;
second = get([1, 2, 3], 1.5 - 0.5) == 2;
third = get({"test": 12, "another": -1}, "test") == 12;
// throws error:
// get({"test": 1}, "another")
Greater
Compares if left is greater than right
Usage:
first = 2 > 1;
Help
Prints help message
Usage:
// prints general help
help();
// prints help for specific function
help(contains);
// print all the functions
help(functions);
If
If the first argument is truthy returns the second argument, otherwise returns the third argument
Usage:
first = if(1 == 1, "success", "failed") == "success";
one = "1";
two = "2";
some_test = e != pi;
second = if(some_test, one, two) == one;
// if function can also take a function
third = if(1 == 1, {=>
1 + 2
}, {=>
"error"
}) == 3;
Join
Combine the first argument into a string with the second argument as is seperator
Usage:
first = join(["1", "2", "3"], ", ") == "1, 2, 3";
second = join(["test", "testing", "test"], "-") == "test-testing-test";
Length
Returns the length of map of list
Usage:
first = length(["1", "2", "3"]) == 3;
second = length({"test": 1, "another": 3}) == 2;
third = length(range(15)) == 15;
Lesser
Compares if left is less than right
Usage:
first = 1 < 2;
Lower
Converts input to lowercase
Usage:
first = lower("TESTING") == "testing";
Mul
Multiplies the two arguments together
Usage:
first = 1 * 1 == 1;
second = 150 * 0 == 0;
third = -5 * -5 == 25;
Not equal
Compares if the two arguments are not equal
Usage:
first = 1 != 2;
second = [1,2,3] != [3,2,1];
third = {"test": true} != {"test": false};
Now
Returns the unix timestamp of the current time
Usage:
now();
Or
If the first argument is truthy returns the first argument, otherwise returns the second argument
Usage:
first = false or true;
second = 0.0 or true;
third = [] or true;
Pow
Raises the first argument to the second argument
Usage:
first = 1 ** 1 == 1;
second = 2 ^ 3 == 8;
third = 16 ** 0.5 == 4;
Prints value
Usage:
// prints the item to configured logger
one = print({"test": 1}) == {"test": 1};
one
Product
Calculates the product of the arguments
Usage:
first = product(1,2,3,4,5) == 120;
second = product([1,2,3,4,5]) == 120;
Push
Push value to the list
Usage:
first = push([1, 2, 3], 1) == [1,2,3,1];
second = push([1, 2, 3], [1, 2, 3]) == [1,2,3,[1,2,3]];
Put
Put third argument into the map or list under the second argument
Usage:
first = put({}, "test", 123) == {"test": 123};
// overwrites existing key
second = put({"test": 23}, "test", 5) == {"test": 5};
third = put({"test": 23}, "test", put({"nested": 23}, "nested", 5)) == {"test": {"nested": 5}};
// put replaces the value at the index
four = put([1, 2, 3], 2, 15) == [1, 2, 15];
Random
Generate a random number. Defaults to a number between 0 and 1, but the range can be set as argument
Usage:
// random() returns a random number between 0 and 1
first = random() != random();
second = random(-1, 1) != random(2, 5);
third = random(-5) != random(5);
Range
Generate a list
Usage:
// generates a list till the given argument
first = range(3) == [0,1,2];
// generates a list from to the given arguments
second = range(1, 4) == [1,2,3];
// generate a list from to the given arguments with an additional step argument
third = range(0, 15, 5) == [0, 5, 10];
four = range(0, 16, 5) == [0, 5, 10, 15];
// the last argument can also be a function that returns the next value
// this will generate the squares of the values in the range
five = range(0, 4, {x => x*x}) == [0, 1, 4, 9];
// the last argument is just a function so it can also be used with variables
filler = 10;
six = range(0, 4, {=> filler}) == [10, 10, 10, 10];
multiplier = {multiply => {number => multiply*number}};
times_three = multiplier.(3);
times_five = multiplier.(5);
seven = range(0, 4, times_three) == [0, 3, 6, 9];
eight = range(0, 4, times_five) == [0, 5, 10, 15];
Reduce
Reduce a list to a single value
Usage:
// sum functionality
one = reduce([1,2,3,4], 0, {acc, x => acc + x}) == 10;
// map like functionality
two = reduce([1,2], [], {acc, x => push(acc, x*3)}) == [3,6];
// generate a map. This example generates a map with odd numbers
three = reduce([1,2,3,4], {}, {acc, item =>
put(acc, format("key-{}", item), item * 2 + 1)
}) == {"key-1": 3, "key-2": 5, "key-3": 7, "key-4": 9};
Remove
Removes index from the list or key from the map
Usage:
// for lists
first = remove([1, 2, 3], 1) == [1, 3];
second = remove([1, 2, 3], -1) == [1, 2];
// for maps
third = remove({"test": 1}, "test") == {};
four = remove({"test": 1}, "another") == {"test": 1};
five = remove({"test": 1, "another": 123}, "another") == {"test": 1};
Shuffle
Shuffles the given list
Usage:
shuffle([1,2,3,4]);
Sin
Calculates the sine of the number
Usage:
first = sin(0) == 0;
Sub
Subtracts the second argument from the first argument
Usage:
first = 1 - 1 == 0;
second = 421 - 321 == 100;
third = -123 - 321 == -444;
Sum
Sums up the arguments
Usage:
first = sum(1,2,3,4,5) == 15;
second = sum([1,2,3,4,5]) == 15;
Tan
Calculates the tangent of the number
Usage:
first = tan(0) == 0;
Trim
Removes the characters at the start and end of the first argument, second argument is an optional argument that contains the character to remove, defaults to ' '
Usage:
first = trim(" test ") == "test";
second = trim("__Testing_Test__", "_") == "Testing_Test";
third = trim("A sentence.\n\n\n\n\n", "\n") == "A sentence.";
Try
Tries the first argument, if that fails returns the second argument
Usage:
// will just return the get function
first = try(get({"test": 10}, "test"), 123) == 10;
// get function will raise because "test" is not found
second = try(get({}, "test"), 123) == 123;
map = {"test": 15};
invalid_key = 1;
third = try(put(map, invalid_key, 123), map) == map;
Type
Returns the type of the argument
Usage:
one = type(1.0) == "number";
two = type([]) == "list";
three = type({}) == "map";
four = type("test") == "string";
five = type({=> x}) == "function";
six = type(true) == "boolean";
seven = type(null) == "null";
Upper
Converts input to uppercase
Usage:
first = upper("testing") == "TESTING";
User Functions
User functions can be defined as follows:
my_func = {x, y =>
x + y
};
my_func.(2, 4) // returns 6
You see that the calling of a function is different than built-ins functions. It uses the .
operator to call the function.
Variables outside the function can be used inside the function.
GLOBAL = 15;
my_func = {x =>
GLOBAL + x
};
my_func.(4) // returns 19
Even currying it implemented.
factory = {x =>
{ y => x + y }
};
generated_function = factory.(4);
generated_function.(12) // returns 16
Also simple recursion works:
factorial = {n =>
if((n == 0) or (n == 1), 1, n * factorial.(n-1))
};
factorial.(8) // returns 40320
Examples
Import
import { value, func } from "./examples/file/import_from.txt";
func.(value)
Import_from
value = {
"text": "testing",
"number": 123
};
func = { data =>
text = get(data, "text");
number = get(data, "number");
format("{} {}", text, number)
};
Recursion
createList = {list, index, till =>
newList = push(list, index);
newIndex = index+1;
if(newIndex == till, newList, {=> createList.(newList, newIndex, till)})
};
// using range(150) is ofcourse better, because this will overflow to stack
createList.([], 0, 150);
Script
Test::new = {=>
{
"y": [1,2,3],
"x": {
"x": 123
}
}
};
Test::get = {map, key =>
inner = get(map, key);
get(inner, key)
};
my_map = Test::new.();
Test::get.(my_map, "x")
Secant_method
// port from the python method used on https://en.wikipedia.org/wiki/Secant_method
secant_method = {f, x0, x1, iterations =>
result = reduce(range(iterations), [x0, x1], {prev_numbers =>
x0 = get(prev_numbers, 0);
x1 = get(prev_numbers, 1);
x2 = x1 - f.(x1) * (x1 - x0) / (f.(x1) - f.(x0));
[x1, x2]
});
get(result, 1)
};
my_func = {x =>
x ** 2 - 612
};
secant_method.(my_func, 10, 30, 5) == 24.738633748750722
Sort
MergeSort::sort = {list =>
if(length(list) < 2, list, {=>
lists = MergeSort::split_middle.(list);
left = get(lists, "left");
right = get(lists, "right");
left = MergeSort::sort.(left);
right = MergeSort::sort.(right);
MergeSort::merge.(left, right)
})
};
MergeSort::merge = {left, right =>
result = reduce(range(length(left) + length(right)), {"left": left, "right": right, "sorted": []}, {acc =>
left = get(acc, "left");
right = get(acc, "right");
sorted = get(acc, "sorted");
if(length(left) == 0, {"left": [], "right": [], "sorted": concat(sorted, right)}, {=>
if(length(right) == 0, {"left": [], "right": [], "sorted": concat(sorted, left)}, {=>
left_first = get(left, 0);
right_first = get(right, 0);
if(left_first < right_first, {=>
left = remove(left, 0);
sorted = push(sorted, left_first);
{"left": left, "right": right, "sorted": sorted}
}, {=>
right = remove(right, 0);
sorted = push(sorted, right_first);
{"left": left, "right": right, "sorted": sorted}
})
})
})
});
get(result, "sorted")
};
MergeSort::split_middle = {list =>
middle = length(list) / 2;
lists = reduce(list, {"left": [], "right": []}, {acc, x =>
left = get(acc, "left");
right = get(acc, "right");
if(
length(left) < middle,
{"left": push(left, x), "right": right},
{"left": left, "right": push(right, x)}
);
});
lists
};
data_list = shuffle(range(15));
// check if data is not already sorted
assert(data_list != range(15));
MergeSort::sort.(data_list)
Library Usage
use expression_parser::{Environment, ExpressionFile};
let input = r#"
a = 1 + 1;
a + 5
"#;
// ofcourse handle this better in you code
let parsed_expression = ExpressionFile::parse(input).unwrap();
// you can now decide what to do with the expression
// we will just evaluate it here with default variables.
let mut vars = Environment::default();
let output = ExpressionFile::eval(parsed_expression, &mut vars).unwrap();
assert_eq!(output, 7.into());
Add extra variables you can use in your code:
use expression_parser::{Env, Environment, ExpressionFile};
let input = r#"
5 * DATA
"#;
let parsed_expression = ExpressionFile::parse(input).unwrap();
let mut env = Environment::default();
env.variables_mut().insert("DATA", 1234.into());
let output = ExpressionFile::eval(parsed_expression, &mut env).unwrap();
assert_eq!(output, 6170.into());
Define your own functions in Rust like:
use expression_parser::{
Closure, Env, Environment, Error, ExpressionFile, ExpressionValue,
};
use std::sync::Arc;
let mut env = Environment::default();
// a `Closure` struct is just a container for holding the function.
// `new` takes a list of the arguments used (only for debugging purposes)
// and an `Arc` with a `Box`ed function with two arguments.
// the first is a list containing all the arguments given by the user. These need to be validated yourself.
// the second argument is a `Environment` struct that has methods to access variables outside the function and side effects.
// the return value is a `Result<ExpressionValue, Error>`
let closure = Closure::new(
vec!["x".to_string(), "y".to_string()],
Arc::new(Box::new(|vars, _| {
/// validate the arguments or return an error
fn validate_number(x: Option<&ExpressionValue>) -> Result<f64, Error> {
x.ok_or(Error::new_static("missing arguments"))?
.as_number()
.ok_or(Error::new_static("argument not a number"))
}
let x = validate_number(vars.get(0))?;
let y = validate_number(vars.get(1))?;
let result = x * y;
// the `into` casts the rust value into a `ExpressionValue` enum
Ok(result.into())
})),
);
env.variables_mut().insert("external_func", closure.into());
let script = r#"
external_func.(6, 2)
"#;
let parsed = ExpressionFile::parse(script).unwrap();
let result = ExpressionFile::eval(parsed, &mut env);
assert_eq!(result, Ok(12.into()))
use expression_parser::{Closure, Env, Environment, Error, Expression, ExpressionFile};
use std::sync::Arc;
let closure = Closure::new(
vec!["map".to_string(), "key".to_string()],
Arc::new(Box::new(|vars, context| {
let map = vars
.get(0)
.ok_or(Error::new_static("expect a map as the first argument"))?
// probably do something more smart than just clone
.clone()
.as_map()
.ok_or(Error::new_static("expect a map as the first argument"))?;
let key = vars
.get(1)
.ok_or(Error::new_static("expect a key as the second argument"))?
.as_string()
.ok_or(Error::new_static("expect a key as the second argument"))?;
// get access to the underlying HashMap
let result = map.0.get(&key).ok_or(Error::new_static("key not found"))?;
// the result is an expression, these can include variables ect.
// We can just match on the value or eval the Expression with the current context.
let result = Expression::eval(result.clone(), &mut Box::new(context))?;
Ok(result)
})),
);
let mut env = Environment::default();
env.variables_mut().insert("map_get", closure.into());
let script = r#"
map_get.(
{"test": "some_value"},
"test"
)
"#;
let result = ExpressionFile::run(script, &mut env);
assert_eq!(result, Ok("some_value".into()));
let script = r#"
text = "some_variable";
map_get.(
{"test": text},
"test"
)
"#;
let result = ExpressionFile::run(script, &mut env);
assert_eq!(result, Ok("some_variable".into()));
Use your own variables:
use expression_parser::{Environment, ExpressionFile, ExpressionValue};
use std::collections::HashMap;
// if you like python keywords better than the names I picked
let mut my_variables = HashMap::new();
my_variables.insert(String::from("True"), ExpressionValue::Bool(true));
my_variables.insert(String::from("False"), ExpressionValue::Bool(false));
my_variables.insert(String::from("None"), ExpressionValue::Null);
// `Environment::builder` lets you configure your environment yourself.
let mut env = Environment::builder()
.with_variables(Box::new(my_variables))
.build();
let input = r#"
a = True and True;
b = False or True;
c = None or True;
all(a, b, c)
"#;
let parsed_expression = ExpressionFile::parse(input).unwrap();
let output = ExpressionFile::eval(parsed_expression, &mut env).unwrap();
assert_eq!(output, true.into());
Or extend the default variables:
use expression_parser::{Environment, ExpressionFile, VariableMap, Variables};
// `Variables::default` returns a `VariableMap` with all the default variables
let mut my_variables = Variables::default();
// so this allready exists in the variables.
assert!(my_variables.insert("true", true.into()).is_some());
my_variables.insert("DATA", vec![1, 5, 1].into());
my_variables.insert("SOME", true.into());
// `Environment::builder` lets you configure your environment yourself.
let mut env = Environment::builder()
.with_variables(Box::new(my_variables))
.build();
let input = r#"
// returns DATA because SOME is true
if(SOME, DATA, error("data not found"))
"#;
let parsed_expression = ExpressionFile::parse(input).unwrap();
let output = ExpressionFile::eval(parsed_expression, &mut env).unwrap();
assert_eq!(output, vec![1, 5, 1].into());