From 5bfee9688ef5712b8a234cec3e50bb0ee14460db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hozda?= Date: Sun, 15 Oct 2017 17:52:02 +0200 Subject: [PATCH 01/14] add REPL example to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 113e178c..4c7b5b72 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ The repository contains several examples in the `examples` folder: - `reuse_scope` evaluates two pieces of code in separate runs, but using a common scope - `rhai_runner` runs each filename passed to it as a Rhai script - `simple_fn` shows how to register a Rust function to a Rhai engine +- `repl` a simple REPL, see source code for what it can do at the moment Examples can be run with the following command: ```bash From b8157a11216eebbd64bad4739b68c055ac180f24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hozda?= Date: Sat, 28 Oct 2017 12:39:29 +0200 Subject: [PATCH 02/14] implement comments --- src/parser.rs | 31 ++++++++++++++++++++++++++++++- src/tests.rs | 9 +++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/parser.rs b/src/parser.rs index 4bf5ff32..18d3fe72 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -353,7 +353,36 @@ impl<'a> Iterator for TokenIterator<'a> { '+' => return Some(Token::Plus), '-' => return Some(Token::Minus), '*' => return Some(Token::Multiply), - '/' => return Some(Token::Divide), + '/' => { + match self.char_stream.peek() { + Some(&'/') => { + self.char_stream.next(); + while let Some(c) = self.char_stream.next() { + if c == '\n' { break; } + } + } + Some(&'*') => { + let mut level = 1; + self.char_stream.next(); + while let Some(c) = self.char_stream.next() { + match c { + '/' => if let Some('*') = self.char_stream.next() { + level+=1; + } + '*' => if let Some('/') = self.char_stream.next() { + level-=1; + } + _ => (), + } + + if level == 0 { + break; + } + } + } + _ => return Some(Token::Divide), + } + } ';' => return Some(Token::Semicolon), ':' => return Some(Token::Colon), ',' => return Some(Token::Comma), diff --git a/src/tests.rs b/src/tests.rs index 543c13f2..acec7717 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -401,3 +401,12 @@ fn test_array_with_structs() { assert!(false); } } + +#[test] +fn test_comments() { + let mut engine = Engine::new(); + + assert!(engine.eval::("let x = 5; x // I am a single line comment, yay!").is_ok()); + + assert!(engine.eval::("let /* I am a multiline comment, yay! */ x = 5; x").is_ok()); +} From 7f4fbd940a1be8121024ca2ba5d1cc27206b1d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hozda?= Date: Sat, 28 Oct 2017 16:07:51 +0200 Subject: [PATCH 03/14] remove finished items from TODO --- TODO | 2 -- 1 file changed, 2 deletions(-) diff --git a/TODO b/TODO index 513f5173..0a96219d 100644 --- a/TODO +++ b/TODO @@ -1,11 +1,9 @@ pre 1.0: - loop - binary ops - - unary minus and negation - bool ops - basic threads - stdlib - - comments - floats - REPL (consume functions) 1.0: From 679f0de6b6f9db42bed65ca40b0de7bb4c9f6d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hozda?= Date: Sat, 28 Oct 2017 16:08:24 +0200 Subject: [PATCH 04/14] implement unary + and -, add useful functions to Token --- src/engine.rs | 15 +++++- src/parser.rs | 141 +++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 143 insertions(+), 13 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 0d163813..76057cb4 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -7,7 +7,7 @@ use std::fmt; use parser::{lex, parse, Expr, Stmt, FnDef}; use fn_register::FnRegister; -use std::ops::{Add, Sub, Mul, Div}; +use std::ops::{Add, Sub, Mul, Div, Neg}; use std::cmp::{Ord, Eq}; #[derive(Debug)] @@ -1317,6 +1317,14 @@ impl Engine { ) } + macro_rules! reg_un { + ($engine:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( + $( + $engine.register_fn($x, ($op as fn(x: $y)->$y)); + )* + ) + } + macro_rules! reg_cmp { ($engine:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( $( @@ -1329,6 +1337,7 @@ impl Engine { fn sub(x: T, y: T) -> ::Output { x - y } fn mul(x: T, y: T) -> ::Output { x * y } fn div(x: T, y: T) -> ::Output { x / y } + fn neg(x: T) -> ::Output { -x } fn lt(x: T, y: T) -> bool { x < y } fn lte(x: T, y: T) -> bool { x <= y } fn gt(x: T, y: T) -> bool { x > y } @@ -1337,6 +1346,7 @@ impl Engine { fn ne(x: T, y: T) -> bool { x != y } fn and(x: bool, y: bool) -> bool { x && y } fn or(x: bool, y: bool) -> bool { x || y } + fn not(x: bool) -> bool { !x } fn concat(x: String, y: String) -> String { x + &y } reg_op!(engine, "+", add, i32, i64, u32, u64, f32, f64); @@ -1354,6 +1364,9 @@ impl Engine { reg_op!(engine, "||", or, bool); reg_op!(engine, "&&", and, bool); + reg_un!(engine, "-", neg, i32, i64, f32, f64); + reg_un!(engine, "!", not, bool); + engine.register_fn("+", concat); // engine.register_fn("[]", idx); diff --git a/src/parser.rs b/src/parser.rs index 18d3fe72..701ab509 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4,12 +4,13 @@ use std::iter::Peekable; use std::str::Chars; use std::char; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum LexError { UnexpectedChar, MalformedEscapeSequence, MalformedNumber, MalformedChar, + Nothing } impl Error for LexError { @@ -19,6 +20,7 @@ impl Error for LexError { LexError::MalformedEscapeSequence => "Unexpected values in escape sequence", LexError::MalformedNumber => "Unexpected characters in number", LexError::MalformedChar => "Char constant not a single character", + LexError::Nothing => "This error is for internal use only" } } @@ -111,7 +113,7 @@ pub enum Expr { False, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Token { IntConst(i64), Identifier(String), @@ -124,7 +126,9 @@ pub enum Token { LSquare, RSquare, Plus, + UnaryPlus, Minus, + UnaryMinus, Multiply, Divide, Semicolon, @@ -155,7 +159,91 @@ pub enum Token { LexErr(LexError), } +impl Token { + // if another operator is after these, it's probably an unary operator + // not sure about fn's name + pub fn is_next_unary(&self) -> bool { + use self::Token::*; + + match *self { + LCurly | // (+expr) - is unary + // RCurly | {expr} - expr not unary & is closing + LParen | // {-expr} - is unary + // RParen | (expr) - expr not unary & is closing + LSquare | // [-expr] - is unary + // RSquare | [expr] - expr not unary & is closing + Plus | + UnaryPlus | + Minus | + UnaryMinus | + Multiply | + Divide | + Colon | + Comma | + Period | + Equals | + LessThan | + GreaterThan | + Bang | + LessThanEqual | + GreaterThanEqual | + EqualTo | + NotEqualTo | + Pipe | + Or | + Ampersand | + And | + Return => true, + _ => false, + } + } + + #[allow(dead_code)] + pub fn is_bin_op(&self) -> bool { + use self::Token::*; + + match *self { + RCurly | + RParen | + RSquare | + Plus | + Minus | + Multiply | + Divide | + Comma | + // Period | <- does period count? + Equals | + LessThan | + GreaterThan | + LessThanEqual | + GreaterThanEqual | + EqualTo | + NotEqualTo | + Pipe | + Or | + Ampersand | + And => true, + _ => false, + } + } + + #[allow(dead_code)] + pub fn is_un_op(&self) -> bool { + use self::Token::*; + + match *self { + UnaryPlus | + UnaryMinus | + Equals | + Bang | + Return => true, + _ => false, + } + } +} + pub struct TokenIterator<'a> { + last: Token, char_stream: Peekable>, } @@ -262,12 +350,8 @@ impl<'a> TokenIterator<'a> { let out: String = result.iter().cloned().collect(); Ok(out) } -} -impl<'a> Iterator for TokenIterator<'a> { - type Item = Token; - - fn next(&mut self) -> Option { + fn inner_next(&mut self) -> Option { while let Some(c) = self.char_stream.next() { match c { '0'...'9' => { @@ -350,8 +434,14 @@ impl<'a> Iterator for TokenIterator<'a> { ')' => return Some(Token::RParen), '[' => return Some(Token::LSquare), ']' => return Some(Token::RSquare), - '+' => return Some(Token::Plus), - '-' => return Some(Token::Minus), + '+' => { + if self.last.is_next_unary() { return Some(Token::UnaryPlus) } + else { return Some(Token::Plus) } + } + '-' => { + if self.last.is_next_unary() { return Some(Token::UnaryMinus) } + else { return Some(Token::Minus) } + } '*' => return Some(Token::Multiply), '/' => { match self.char_stream.peek() { @@ -450,8 +540,21 @@ impl<'a> Iterator for TokenIterator<'a> { } } +impl<'a> Iterator for TokenIterator<'a> { + type Item = Token; + + // TODO - perhaps this could be optimized to only keep track of last for operators? + fn next(&mut self) -> Option { + self.last = match self.inner_next() { + Some(c) => c, + None => return None, + }; + Some(self.last.clone()) + } +} + pub fn lex(input: &str) -> TokenIterator { - TokenIterator { char_stream: input.chars().peekable() } + TokenIterator { last: Token::LexErr(LexError::Nothing), char_stream: input.chars().peekable() } } fn get_precedence(token: &Token) -> i32 { @@ -599,6 +702,20 @@ fn parse_primary<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { + let tok = match input.peek() { + Some(tok) => tok.clone(), + None => return Err(ParseError::InputPastEndOfFile), + }; + + match tok { + Token::UnaryMinus => { input.next(); Ok(Expr::FnCall("-".to_string(), vec![parse_primary(input)?])) } + Token::UnaryPlus => { input.next(); parse_primary(input) } + Token::Bang => { input.next(); Ok(Expr::FnCall("!".to_string(), vec![parse_primary(input)?])) } + _ => parse_primary(input) + } +} + fn parse_binop<'a>(input: &mut Peekable>, prec: i32, lhs: Expr) @@ -617,7 +734,7 @@ fn parse_binop<'a>(input: &mut Peekable>, } if let Some(op_token) = input.next() { - let mut rhs = try!(parse_primary(input)); + let mut rhs = try!(parse_unary(input)); let mut next_prec = -1; @@ -658,7 +775,7 @@ fn parse_binop<'a>(input: &mut Peekable>, } fn parse_expr<'a>(input: &mut Peekable>) -> Result { - let lhs = try!(parse_primary(input)); + let lhs = try!(parse_unary(input)); parse_binop(input, 0, lhs) } From caf8a411aa734c63642323d6afad518a5c304e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hozda?= Date: Sat, 28 Oct 2017 16:40:38 +0200 Subject: [PATCH 05/14] impl loop and add tests for both unary operators and loops --- src/engine.rs | 13 +++++++++++++ src/parser.rs | 12 ++++++++++++ src/tests.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/src/engine.rs b/src/engine.rs index 76057cb4..ba67c959 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1153,6 +1153,19 @@ impl Engine { } } } + Stmt::Loop(ref body) => { + loop { + match self.eval_stmt(scope, body) { + Err(EvalAltResult::LoopBreak) => { + return Ok(Box::new(())); + } + Err(x) => { + return Err(x); + } + _ => (), + } + } + } Stmt::Break => Err(EvalAltResult::LoopBreak), Stmt::Return => Err(EvalAltResult::Return(Box::new(()))), Stmt::ReturnWithVal(ref a) => { diff --git a/src/parser.rs b/src/parser.rs index 701ab509..2561b7a2 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -90,6 +90,7 @@ pub enum Stmt { If(Box, Box), IfElse(Box, Box, Box), While(Box, Box), + Loop(Box), Var(String, Option>), Block(Vec), Expr(Box), @@ -142,6 +143,7 @@ pub enum Token { If, Else, While, + Loop, LessThan, GreaterThan, Bang, @@ -398,6 +400,7 @@ impl<'a> TokenIterator<'a> { "if" => return Some(Token::If), "else" => return Some(Token::Else), "while" => return Some(Token::While), + "loop" => return Some(Token::Loop), "break" => return Some(Token::Break), "return" => return Some(Token::Return), "fn" => return Some(Token::Fn), @@ -805,6 +808,14 @@ fn parse_while<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { + input.next(); + + let body = try!(parse_block(input)); + + Ok(Stmt::Loop(Box::new(body))) +} + fn parse_var<'a>(input: &mut Peekable>) -> Result { input.next(); @@ -868,6 +879,7 @@ fn parse_stmt<'a>(input: &mut Peekable>) -> Result parse_if(input), Some(&Token::While) => parse_while(input), + Some(&Token::Loop) => parse_loop(input), Some(&Token::Break) => { input.next(); Ok(Stmt::Break) diff --git a/src/tests.rs b/src/tests.rs index acec7717..98f75862 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -410,3 +410,50 @@ fn test_comments() { assert!(engine.eval::("let /* I am a multiline comment, yay! */ x = 5; x").is_ok()); } + +#[test] +fn test_unary_minus() { + let mut engine = Engine::new(); + + assert_eq!(engine.eval::("let x = -5; x").unwrap(), -5); + + assert_eq!(engine.eval::("fn n(x) { -x } n(5)").unwrap(), -5); + + assert_eq!(engine.eval::("5 - -(-5)").unwrap(), 0); +} + +#[test] +fn test_not() { + let mut engine = Engine::new(); + + assert_eq!(engine.eval::("let not_true = !true; not_true").unwrap(), false); + + assert_eq!(engine.eval::("fn not(x) { !x } not(false)").unwrap(), true); + + // TODO - do we allow stacking unary operators directly? e.g '!!!!!!!true' + assert_eq!(engine.eval::("!(!(!(!(true))))").unwrap(), true) +} + +#[test] +fn test_loop() { + let mut engine = Engine::new(); + + assert!( + engine.eval::(" + let x = 0; + let i = 0; + + loop { + if i < 10 { + x = x + i; + i = i + 1; + } + else { + break; + } + } + + x == 45 + ").unwrap() + ) +} From 347191505dfeba36a5cce60ea7fc5ead4e400d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hozda?= Date: Sat, 28 Oct 2017 16:41:48 +0200 Subject: [PATCH 06/14] remove completed entries from TODO --- TODO | 2 -- 1 file changed, 2 deletions(-) diff --git a/TODO b/TODO index 0a96219d..b5b4fdf5 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,5 @@ pre 1.0: - - loop - binary ops - - bool ops - basic threads - stdlib - floats From 780962fcea9e595527260be1d9e37e004a0667a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hozda?= Date: Sat, 28 Oct 2017 17:44:05 +0200 Subject: [PATCH 07/14] add example scripts for comments and loop --- scripts/comments.rhai | 10 ++++++++++ scripts/loop.rhai | 8 ++++++++ 2 files changed, 18 insertions(+) create mode 100644 scripts/comments.rhai create mode 100644 scripts/loop.rhai diff --git a/scripts/comments.rhai b/scripts/comments.rhai new file mode 100644 index 00000000..50f15278 --- /dev/null +++ b/scripts/comments.rhai @@ -0,0 +1,10 @@ +// I am a single line comment! + +let /* I am a spy in a variable declaration! */ x = 5; + +/* I am a simple + multiline comment */ + +/* look /* at /* that, /* multiline */ comments */ can be */ nested */ + +/* sorrounded by */ x // comments diff --git a/scripts/loop.rhai b/scripts/loop.rhai new file mode 100644 index 00000000..91b7a5d8 --- /dev/null +++ b/scripts/loop.rhai @@ -0,0 +1,8 @@ +let x = 10; + +// simulate do..while using loop +loop { + print(x); + x = x - 1; + if x <= 0 { break; } +} From ebbab0d259ab0f3af86edfd20bc90fefc7569eae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hozda?= Date: Sat, 28 Oct 2017 17:44:53 +0200 Subject: [PATCH 08/14] bump version, remove unneeded cause() fn as it now has a provided implementation --- Cargo.toml | 2 +- src/parser.rs | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8b7ccaca..5334ac6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai" -version = "0.5.1" +version = "0.6.0" authors = ["Jonathan Turner", "Lukáš Hozda"] description = "Embedded scripting for Rust" homepage = "https://github.com/jonathandturner/rhai" diff --git a/src/parser.rs b/src/parser.rs index 2561b7a2..667f424c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -23,10 +23,6 @@ impl Error for LexError { LexError::Nothing => "This error is for internal use only" } } - - fn cause(&self) -> Option<&Error> { - None - } } impl fmt::Display for LexError { From 32c252d4d249033fc4390047ec6e988d13d84da8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hozda?= Date: Sat, 28 Oct 2017 20:56:34 +0200 Subject: [PATCH 09/14] allow unary operators as the first token after if or while --- Cargo.toml | 2 +- src/parser.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5334ac6c..4f650763 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai" -version = "0.6.0" +version = "0.6.1" authors = ["Jonathan Turner", "Lukáš Hozda"] description = "Embedded scripting for Rust" homepage = "https://github.com/jonathandturner/rhai" diff --git a/src/parser.rs b/src/parser.rs index 667f424c..dd13de5d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -191,6 +191,8 @@ impl Token { Or | Ampersand | And | + If | + While | Return => true, _ => false, } From 1b22cab7dd4027035f4eb0204148a080ae68cae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hozda?= Date: Sat, 28 Oct 2017 21:59:14 +0200 Subject: [PATCH 10/14] add loops and comments and a list of example scripts and how to run them to README --- README.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/README.md b/README.md index 4c7b5b72..a02a84fa 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,29 @@ Examples can be run with the following command: cargo run --example name ``` +## Example Scripts +We also have a few examples scripts that showcase Rhai's features, all stored in the `scripts` folder: +- `array.rhai` - arrays in Rhai +- `assignment.rhai` - variable declarations +- `comments.rhai` - just comments +- `function_decl1.rhai` - a function without parameters +- `function_decl2.rhai` - a function with two parameters +- `function_decl3.rhai` - a function with many parameters +- `if1.rhai` - if example +- `loop.rhai` - endless loop in Rhai, this example emulates a do..while cycle +- `op1.rhai` - just a simple addition +- `op2.rhai` - simple addition and multiplication +- `op3.rhai` - change evaluation order with parenthesis +- `speed_test.rhai` - a simple program to measure the speed of Rhai's interpreter +- `string.rhai`- string operations +- `while.rhai` - while loop + +To run the scripts, you can either make your own tiny program, or make use of the `rhai_runner` +example program: +```bash +cargo run --example rhai_runner scripts/any_script.rhai +``` + # Hello world To get going with Rhai, you create an instance of the scripting engine and then run eval. @@ -290,6 +313,17 @@ while x > 0 { } ``` +## Loop +```rust +let x = 10; + +loop { + print(x); + x = x - 1; + if x == 0 { break; } +} +``` + ## Functions Rhai supports defining functions in script: @@ -337,3 +371,18 @@ let name = "Bob"; let middle_initial = 'C'; ``` +## Comments + +```rust +let /* intruder comment */ name = "Bob"; +// This is a very important comment +/* This comment spans + multiple lines, so it + only makes sense that + it is even more important */ + +/* Fear not, Rhai satisfies all your nesting + needs with nested comments: + /*/*/*/*/**/*/*/*/*/ +*/ +``` From 47ce39062fd1ca2566311fdf5ce67c6f9d67a4c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hozda?= Date: Sat, 28 Oct 2017 22:33:00 +0200 Subject: [PATCH 11/14] initial documentation (WIP) --- src/engine.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/lib.rs | 39 +++++++++++++++++++++++++++++++++++++- 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index ba67c959..e18b9386 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -91,14 +91,44 @@ pub enum FnType { InternalFn(FnDef), } +/// Rhai's engine type. This is what you use to run Rhai scripts +/// +/// ```rust +/// extern crate rhai; +/// use rhai::Engine; +/// +/// fn main() { +/// let mut engine = Engine::new(); +/// +/// if let Ok(result) = engine.eval::("40 + 2") { +/// println!("Answer: {}", result); // prints 42 +/// } +/// } +/// ``` pub struct Engine { pub fns: HashMap>, } +/// A type containing information about current scope. +/// Useful for keeping state between `Engine` runs +/// +/// ```rust +/// use rhai::{Engine, Scope}; +/// +/// let mut engine = Engine::new(); +/// let mut my_scope = Scope::new(); +/// +/// assert!(engine.eval_with_scope::<()>(&mut my_scope, "let x = 5;").is_ok()); +/// assert_eq!(engine.eval_with_scope::(&mut my_scope, "x + 1").unwrap(), 6); +/// ``` +/// +/// Between runs, `Engine` only remembers functions when not using own `Scope`. pub type Scope = Vec<(String, Box)>; impl Engine { - fn call_fn(&self, + /// Universal method for calling functions, that are either + /// registered with the `Engine` or written in Rhai + pub fn call_fn(&self, name: &str, arg1: Option<&mut Box>, arg2: Option<&mut Box>, @@ -500,6 +530,8 @@ impl Engine { } } + /// Register a type for use with Engine. Keep in mind that + /// your type must implement Clone. pub fn register_type(&mut self) { fn clone_helper(t: T) -> T { t.clone() @@ -508,6 +540,7 @@ impl Engine { self.register_fn("clone", clone_helper as fn(T) -> T); } + /// Register a get function for a member of a registered type pub fn register_get(&mut self, name: &str, get_fn: F) where F: 'static + Fn(&mut T) -> U { @@ -516,6 +549,7 @@ impl Engine { self.register_fn(&get_name, get_fn); } + /// Register a set function for a member of a registered type pub fn register_set(&mut self, name: &str, set_fn: F) where F: 'static + Fn(&mut T, U) -> () { @@ -524,6 +558,7 @@ impl Engine { self.register_fn(&set_name, set_fn); } + /// Shorthand for registering both getters and setters pub fn register_get_set(&mut self, name: &str, get_fn: F, @@ -1187,6 +1222,7 @@ impl Engine { } } + /// Evaluate a file pub fn eval_file(&mut self, fname: &str) -> Result { use std::fs::File; use std::io::prelude::*; @@ -1204,12 +1240,14 @@ impl Engine { } } + /// Evaluate a string pub fn eval(&mut self, input: &str) -> Result { let mut scope: Scope = Vec::new(); self.eval_with_scope(&mut scope, input) } + /// Evaluate with own scope pub fn eval_with_scope(&mut self, scope: &mut Scope, input: &str) @@ -1254,6 +1292,9 @@ impl Engine { } } + /// Evaluate a file, but only return errors, if there are any. + /// Useful for when you don't need the result, but still need + /// to keep track of possible errors pub fn consume_file(&mut self, fname: &str) -> Result<(), EvalAltResult> { use std::fs::File; use std::io::prelude::*; @@ -1273,6 +1314,9 @@ impl Engine { } } + /// Evaluate a string, but only return errors, if there are any. + /// Useful for when you don't need the result, but still need + /// to keep track of possible errors pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> { let mut scope: Scope = Scope::new(); @@ -1281,6 +1325,9 @@ impl Engine { res } + /// Evaluate a string with own scoppe, but only return errors, if there are any. + /// Useful for when you don't need the result, but still need + /// to keep track of possible errors pub fn consume_with_scope(&mut self, scope: &mut Scope, input: &str) -> Result<(), EvalAltResult> { let tokens = lex(input); @@ -1311,6 +1358,8 @@ impl Engine { } } + /// Register the default library. That means, numberic types, char, bool + /// String, arithmetics and string concatenations. pub fn register_default_lib(engine: &mut Engine) { engine.register_type::(); engine.register_type::(); @@ -1388,6 +1437,7 @@ impl Engine { // (*ent).push(FnType::ExternalFn2(Box::new(idx))); } + /// Make a new engine pub fn new() -> Engine { let mut engine = Engine { fns: HashMap::new() }; diff --git a/src/lib.rs b/src/lib.rs index 0b9cc5ca..c3466507 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,40 @@ -//! Rhai - embedded scripting for Rust +//! # Rhai - embedded scripting for Rust +//! Rhai is a tiny, simple and very fast embedded scripting language for Rust +//! that gives you a safe and easy way to add scripting to your applications. +//! It provides a familiar syntax based on JS and Rust and a simple Rust interface. +//! Here is a quick example. First, the contents of `my_script.rhai`: +//! +//! ```rust_todo_disable_testing_enable_highlighting +//! fn factorial(x) { +//! if x == 1 { return 1; } +//! x * factorial(x - 1) +//! } +//! +//! compute_something(factorial(10)) +//! ``` +//! +//! And the Rust part: +//! +//! ```rust +//! use rhai::{FnRegister, Engine}; +//! +//! fn compute_something(x: i64) -> bool { +//! (x % 40) == 0 +//! } +//! +//! let mut engine = Engine::new(); +//! engine.register_fn("compute_something", compute_something); +//! # // Very ugly hack incoming, TODO +//! # use std::fs::{File, remove_file}; +//! # use std::io::Write; +//! # use std::mem::drop; +//! # let mut f = File::create("my_script.rhai").unwrap(); +//! # write!(f, "{}", "fn f(x) { if x == 1 { return 1; } x * f(x-1) } compute_something(f(10))"); +//! assert!(engine.eval_file::("my_script.rhai").unwrap()); +//! # let _ = remove_file("my_script.rhai"); +//! ``` +//! +//! [Check out the README on github for more information!](https://github.com/jonathandturner/rhai) // lints required by Rhai #![allow(unknown_lints, @@ -16,3 +52,4 @@ mod tests; pub use engine::{Engine, Scope}; pub use fn_register::FnRegister; + From f81ef3665e374a87bf3a028d8267889c015d27b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hozda?= Date: Sat, 28 Oct 2017 22:33:39 +0200 Subject: [PATCH 12/14] bump version once more --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4f650763..3db83960 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai" -version = "0.6.1" +version = "0.6.2" authors = ["Jonathan Turner", "Lukáš Hozda"] description = "Embedded scripting for Rust" homepage = "https://github.com/jonathandturner/rhai" From e042e1e31d6e6b918843596467e53181a4c4c659 Mon Sep 17 00:00:00 2001 From: russ Date: Sun, 29 Oct 2017 22:03:35 -0700 Subject: [PATCH 13/14] candidate increment/decrement implementation --- src/parser.rs | 42 ++++++++++++++++++++++++++++++++++++++---- src/tests.rs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 4bf5ff32..e9fc9563 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -152,6 +152,8 @@ pub enum Token { Fn, Break, Return, + PlusEquals, + MinusEquals, LexErr(LexError), } @@ -350,8 +352,24 @@ impl<'a> Iterator for TokenIterator<'a> { ')' => return Some(Token::RParen), '[' => return Some(Token::LSquare), ']' => return Some(Token::RSquare), - '+' => return Some(Token::Plus), - '-' => return Some(Token::Minus), + '+' => { + return match self.char_stream.peek() { + Some(&'=') => { + self.char_stream.next(); + Some(Token::PlusEquals) + }, + _ => Some(Token::Plus) + } + }, + '-' => { + return match self.char_stream.peek() { + Some(&'=') => { + self.char_stream.next(); + Some(Token::MinusEquals) + }, + _ => Some(Token::Minus) + } + }, '*' => return Some(Token::Multiply), '/' => return Some(Token::Divide), ';' => return Some(Token::Semicolon), @@ -427,7 +445,9 @@ pub fn lex(input: &str) -> TokenIterator { fn get_precedence(token: &Token) -> i32 { match *token { - Token::Equals => 10, + Token::Equals + | Token::PlusEquals + | Token::MinusEquals => 10, Token::Or => 11, Token::And => 12, Token::LessThan @@ -458,7 +478,7 @@ fn parse_call_expr<'a>(id: String, input: &mut Peekable>) -> Result { let mut args = Vec::new(); - + if let Some(&Token::RParen) = input.peek() { input.next(); return Ok(Expr::FnCall(id, args)); @@ -609,6 +629,20 @@ fn parse_binop<'a>(input: &mut Peekable>, Token::Multiply => Expr::FnCall("*".to_string(), vec![lhs_curr, rhs]), Token::Divide => Expr::FnCall("/".to_string(), vec![lhs_curr, rhs]), Token::Equals => Expr::Assignment(Box::new(lhs_curr), Box::new(rhs)), + Token::PlusEquals => { + let lhs_copy = lhs_curr.clone(); + Expr::Assignment( + Box::new(lhs_curr), + Box::new(Expr::FnCall("+".to_string(), vec![lhs_copy, rhs])) + ) + }, + Token::MinusEquals => { + let lhs_copy = lhs_curr.clone(); + Expr::Assignment( + Box::new(lhs_curr), + Box::new(Expr::FnCall("-".to_string(), vec![lhs_copy, rhs])) + ) + }, Token::Period => Expr::Dot(Box::new(lhs_curr), Box::new(rhs)), Token::EqualTo => Expr::FnCall("==".to_string(), vec![lhs_curr, rhs]), Token::NotEqualTo => Expr::FnCall("!=".to_string(), vec![lhs_curr, rhs]), diff --git a/src/tests.rs b/src/tests.rs index 543c13f2..2a75a38d 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -401,3 +401,37 @@ fn test_array_with_structs() { assert!(false); } } + +#[test] +fn test_increment() { + let mut engine = Engine::new(); + + if let Ok(result) = engine.eval::("let x = 1; x += 2; x") { + assert_eq!(result, 3); + } else { + assert!(false); + } + + if let Ok(result) = engine.eval::("let s = \"test\"; s += \"ing\"; s") { + assert_eq!(result, "testing".to_owned()); + } else { + assert!(false); + } +} + +#[test] +fn test_decrement() { + let mut engine = Engine::new(); + + if let Ok(result) = engine.eval::("let x = 10; x -= 7; x") { + assert_eq!(result, 3); + } else { + assert!(false); + } + + if let Ok(_) = engine.eval::("let s = \"test\"; s -= \"ing\"; s") { + assert!(false); + } else { + assert!(true); + } +} \ No newline at end of file From 50b8fe365a3e0d7a606dac3e1706ec1aa56a7e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hozda?= Date: Mon, 30 Oct 2017 08:53:11 +0100 Subject: [PATCH 14/14] fix warning in lib.rs doctest --- src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c3466507..d97fea0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,9 +27,8 @@ //! # // Very ugly hack incoming, TODO //! # use std::fs::{File, remove_file}; //! # use std::io::Write; -//! # use std::mem::drop; //! # let mut f = File::create("my_script.rhai").unwrap(); -//! # write!(f, "{}", "fn f(x) { if x == 1 { return 1; } x * f(x-1) } compute_something(f(10))"); +//! # let _ = write!(f, "{}", "fn f(x) { if x == 1 { return 1; } x * f(x-1) } compute_something(f(10))"); //! assert!(engine.eval_file::("my_script.rhai").unwrap()); //! # let _ = remove_file("my_script.rhai"); //! ```