Skip to content

Commit

Permalink
Merge pull request #10 from rscarson/0.5.2
Browse files Browse the repository at this point in the history
0.5.2
  • Loading branch information
rscarson authored Mar 28, 2022
2 parents 29161f7 + 39d6113 commit 97e985f
Show file tree
Hide file tree
Showing 15 changed files with 192 additions and 115 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ jobs:
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
- name: Run barebones tests
run: cargo test --no-default-features --lib
- name: Run clippy
run: cargo clippy
4 changes: 4 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
0.5.2
-----
Make extensions a feature that can be disabled

0.5.1
-----
Change to how errors are exported
Expand Down
10 changes: 5 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 11 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,25 @@ categories = ["parser-implementations", "development-tools", "command-line-utili
homepage = "https://rscarson.github.io/Lavendeux/"
repository = "https://github.com/rscarson/lavendeux-parser"
readme = "readme.md"
version = "0.5.1"
version = "0.5.2"
edition = "2021"

[features]
default = ["extensions"]
extensions = ["js-sandbox"]

[dependencies]
pest = "2.1.3"
pest_derive = "2.1.0"
rand = "0.8.4"
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
reqwest = { version = "0.11.10", features = ["blocking"] }
chrono = "0.4.19"
rand = "0.8.4"
derive_more = "0.99.17"
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
js-sandbox = "0.1.6"

# Feature deps
js-sandbox = { version = "0.1.6", optional = true }

[dev-dependencies]
version-sync = "0.9"
41 changes: 33 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
[![Build Status](https://github.com/rscarson/lavendeux-parser/workflows/Rust/badge.svg)](https://github.com/rscarson/lavendeux-parser/actions?workflow=Rust)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/rscarson/lavendeux-parser/master/LICENSE)

lavendeux-parser is an exensible parsing engine for mathematical expressions.
It supports variable and function assignments, a variety of datatypes, and can
be extended easily at runtime through extensions written in javascript.
lavendeux-parser is an exensible parsing engine for mathematical expressions.
It supports variable and function assignments, a variety of datatypes, and can
be extended easily at runtime through extensions written in javascript.

Extensions are run in a sandboxed environment with no host or network access.
This project is the engine behind [Lavendeux](https://rscarson.github.io/lavendeux/).
Extensions are run in a sandboxed environment with no host or network access.
This project is the engine behind [Lavendeux](https://rscarson.github.io/lavendeux/).

## Getting Started
To use it, create a `ParserState` object, and use it to tokenize input with `Token::new`:
## Getting Started
To use it, create a `ParserState` object, and use it to tokenize input with `Token::new`:
```rust
use lavendeux_parser::{ParserState, ParserError, Token, Value};

Expand Down Expand Up @@ -90,8 +90,33 @@ fn main() -> Result<(), ParserError> {
Ok(())
}
```

## Using Extensions
Extensions give a more flexible way of adding functionality at runtime. Extensions are written in javascript.

Extensions are enabled by default, and can be excluded by disabling the crate's "extensions" feature

Extensions can be loaded as follows:
```rust
use lavendeux_parser::{ParserState, ParserError, Value, Token};

fn main() -> Result<(), ParserError> {
let mut state : ParserState = ParserState::new();

// Load one extension
state.extensions.load("example_extensions/colour_utils.js")?;

// Load a whole directory
state.extensions.load_all("./example_extensions")?;

// Once loaded, functions and @decorators decribed in the extensions
// can be called in expressions being parsed
let token = Token::new("complement(0xFF0000) @colour", &mut state)?;
assert_eq!(token.text(), "#ffff00");
Ok(())
}
```

## Syntax
Expressions can be composed of integers, floats, strings, as well as numbers of various bases:
```javascript
Expand Down Expand Up @@ -150,7 +175,7 @@ f(x) = 2*x**2 + 3*x + 5
f(2.3)

// Recursive functions work too!
factorial(x) = x==1 ? x : (x * factorial(x - 1) )
factorial(x) = x==0 ? 1 : (x * factorial(x - 1) )
factorial(5)
```

Expand Down
2 changes: 1 addition & 1 deletion src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ mod values;
pub use values::*;

/// Represents all possible errors during expression handling
#[derive(From, Debug, Clone)]
#[derive(Debug, Clone)]
pub enum ParserError {
/// An error with an unknown cause
General(String),
Expand Down
71 changes: 19 additions & 52 deletions src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,73 +5,40 @@ use std::collections::HashMap;
pub type FunctionHandler = fn(&[Value]) -> Result<Value, ParserError>;

mod trig;
use trig::*;

mod dev;
use dev::*;

mod network;
use network::*;

mod str;
use self::str::*;

#[derive(Clone)]
pub struct FunctionTable(HashMap<String, FunctionHandler>);
impl FunctionTable {
/// Initialize a new function table, complete with default builtin functions
pub fn new() -> FunctionTable {
let mut table : FunctionTable = FunctionTable(HashMap::new());
table.register_builtins();
table
}

/// Register builtin functions
fn register_builtins(&mut self) {
// Rounding functions
table.register("ceil", builtin_ceil);
table.register("floor", builtin_floor);
table.register("round", builtin_round);

// Conversion functions
table.register("to_radians", builtin_to_radians);
table.register("to_degrees", builtin_to_degrees);
table.register("abs", builtin_abs);

// Trig functions
table.register("tan", builtin_tan);
table.register("cos", builtin_cos);
table.register("sin", builtin_sin);
table.register("atan", builtin_atan);
table.register("acos", builtin_acos);
table.register("asin", builtin_asin);
table.register("tanh", builtin_tanh);
table.register("cosh", builtin_cosh);
table.register("sinh", builtin_sinh);
self.register("ceil", builtin_ceil);
self.register("floor", builtin_floor);
self.register("round", builtin_round);
self.register("abs", builtin_abs);

// Roots and logs
table.register("ln", builtin_ln);
table.register("log10", builtin_log10);
table.register("log", builtin_log);
table.register("sqrt", builtin_sqrt);
table.register("root", builtin_root);

// String functions
table.register("concat", builtin_concat);
table.register("uppercase", builtin_uppercase);
table.register("lowercase", builtin_lowercase);
table.register("trim", builtin_trim);
table.register("strlen", builtin_strlen);
table.register("substr", builtin_substr);
table.register("contains", builtin_contains);
self.register("ln", builtin_ln);
self.register("log10", builtin_log10);
self.register("log", builtin_log);
self.register("sqrt", builtin_sqrt);
self.register("root", builtin_root);

// Developper functions
table.register("choose", builtin_choose);
table.register("rand", builtin_rand);
table.register("time", builtin_time);
table.register("tail", builtin_tail);

// Networking functions
table.register("get", builtin_get);
table.register("post", builtin_post);
table.register("resolve", builtin_resolve);

table
// Other builtins
str::register_functions(self);
dev::register_functions(self);
network::register_functions(self);
trig::register_functions(self);
}

/// Register a function in the table
Expand Down
42 changes: 31 additions & 11 deletions src/functions/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,30 @@ use std::time::{SystemTime, UNIX_EPOCH};
use std::fs::File;
use std::io::{BufRead, BufReader};
use rand::prelude::*;
use super::FunctionTable;
use crate::value::{Value, IntegerType};
use crate::errors::*;

pub fn builtin_choose(args: &[Value]) -> Result<Value, ParserError> {
/// Register developper functions
pub fn register_functions(table: &mut FunctionTable) {
table.register("choose", builtin_choose);
table.register("rand", builtin_rand);
table.register("time", builtin_time);
table.register("tail", builtin_tail);
}

fn builtin_choose(args: &[Value]) -> Result<Value, ParserError> {
let mut rng = rand::thread_rng();

if args.is_empty() {
Err(ParserError::FunctionNArg(FunctionNArgError::new("choose(..)", 1, 100)))
} else {
let arg = rng.gen_range(0..(args.len() - 1));
let arg = rng.gen_range(0..args.len());
Ok(args[arg].clone())
}
}

pub fn builtin_rand(args: &[Value]) -> Result<Value, ParserError> {
fn builtin_rand(args: &[Value]) -> Result<Value, ParserError> {
let mut rng = rand::thread_rng();
if args.is_empty() {
// Generate a float between 0 and 1
Expand All @@ -32,14 +41,14 @@ pub fn builtin_rand(args: &[Value]) -> Result<Value, ParserError> {
}
}

pub fn builtin_time(_args: &[Value]) -> Result<Value, ParserError> {
fn builtin_time(_args: &[Value]) -> Result<Value, ParserError> {
match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(n) => Ok(Value::Integer(n.as_secs() as IntegerType)),
Err(_) => Ok(Value::Integer(0))
}
}

pub fn builtin_tail(args: &[Value]) -> Result<Value, ParserError> {
fn builtin_tail(args: &[Value]) -> Result<Value, ParserError> {
if args.len() != 1 && args.len() != 2 {
return Err(ParserError::FunctionNArg(FunctionNArgError::new("tail(file, [n_lines])", 1, 2)));
}
Expand Down Expand Up @@ -71,17 +80,28 @@ mod test_builtin_table {

#[test]
fn test_choose() {
let result = builtin_choose(&[Value::String("test".to_string()), Value::Integer(5)]).unwrap();
assert_eq!(true, result == Value::String("test".to_string()) || result == Value::Integer(5));
let mut result;
for _ in 0..30 {
result = builtin_choose(&[Value::String("test".to_string()), Value::Integer(5)]).unwrap();
assert_eq!(true, result.is_string() || result == Value::Integer(5).is_int());
}
}

#[test]
fn test_rand() {
let mut result = builtin_rand(&[]).unwrap();
assert_eq!(true, result.as_float().unwrap() >= 0.0 && result.as_float().unwrap() <= 1.0);
let mut result;

result = builtin_rand(&[Value::Integer(5), Value::Integer(10)]).unwrap();
assert_eq!(true, result.as_int().unwrap() >= 5 && result.as_int().unwrap() <= 10);
for _ in 0..30 {
result = builtin_rand(&[]).unwrap();
println!("{}", result);
assert_eq!(true, result.as_float().unwrap() >= 0.0 && result.as_float().unwrap() <= 1.0);
}

for _ in 0..30 {
result = builtin_rand(&[Value::Integer(5), Value::Integer(10)]).unwrap();
println!("{}", result);
assert_eq!(true, result.as_int().unwrap() >= 5 && result.as_int().unwrap() <= 10);
}
}

#[test]
Expand Down
14 changes: 11 additions & 3 deletions src/functions/network.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
use std::net::ToSocketAddrs;
use std::time::Duration;
use super::FunctionTable;
use crate::value::{Value};
use crate::errors::*;

pub fn builtin_resolve(args: &[Value]) -> Result<Value, ParserError> {
/// Register network functions
pub fn register_functions(table: &mut FunctionTable) {
table.register("get", builtin_get);
table.register("post", builtin_post);
table.register("resolve", builtin_resolve);
}

fn builtin_resolve(args: &[Value]) -> Result<Value, ParserError> {
if args.len() != 1 {
return Err(ParserError::FunctionNArg(FunctionNArgError::new("resolve(hostname)", 1, 1)));
}
Expand All @@ -24,7 +32,7 @@ pub fn builtin_resolve(args: &[Value]) -> Result<Value, ParserError> {
}
}

pub fn builtin_get(args: &[Value]) -> Result<Value, ParserError> {
fn builtin_get(args: &[Value]) -> Result<Value, ParserError> {
if args.is_empty() {
return Err(ParserError::FunctionNArg(FunctionNArgError::new("get(url, [\"header-name=value\", ...])", 1, 1)));
}
Expand Down Expand Up @@ -54,7 +62,7 @@ pub fn builtin_get(args: &[Value]) -> Result<Value, ParserError> {
}
}

pub fn builtin_post(args: &[Value]) -> Result<Value, ParserError> {
fn builtin_post(args: &[Value]) -> Result<Value, ParserError> {
if args.len() != 2 {
return Err(ParserError::FunctionNArg(FunctionNArgError::new("post(url, body)", 1, 1)));
}
Expand Down
Loading

0 comments on commit 97e985f

Please sign in to comment.