sync'd latest master

This commit is contained in:
russ 2017-10-30 08:08:44 -07:00
parent 18c6892df3
commit e5e58fce98
9 changed files with 479 additions and 27 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rhai" name = "rhai"
version = "0.5.1" version = "0.6.2"
authors = ["Jonathan Turner", "Lukáš Hozda"] authors = ["Jonathan Turner", "Lukáš Hozda"]
description = "Embedded scripting for Rust" description = "Embedded scripting for Rust"
homepage = "https://github.com/jonathandturner/rhai" homepage = "https://github.com/jonathandturner/rhai"

View File

@ -37,12 +37,36 @@ The repository contains several examples in the `examples` folder:
- `reuse_scope` evaluates two pieces of code in separate runs, but using a common scope - `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 - `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 - `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: Examples can be run with the following command:
```bash ```bash
cargo run --example name 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 # Hello world
To get going with Rhai, you create an instance of the scripting engine and then run eval. To get going with Rhai, you create an instance of the scripting engine and then run eval.
@ -289,6 +313,17 @@ while x > 0 {
} }
``` ```
## Loop
```rust
let x = 10;
loop {
print(x);
x = x - 1;
if x == 0 { break; }
}
```
## Functions ## Functions
Rhai supports defining functions in script: Rhai supports defining functions in script:
@ -336,3 +371,18 @@ let name = "Bob";
let middle_initial = 'C'; 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:
/*/*/*/*/**/*/*/*/*/
*/
```

4
TODO
View File

@ -1,11 +1,7 @@
pre 1.0: pre 1.0:
- loop
- binary ops - binary ops
- unary minus and negation
- bool ops
- basic threads - basic threads
- stdlib - stdlib
- comments
- floats - floats
- REPL (consume functions) - REPL (consume functions)
1.0: 1.0:

10
scripts/comments.rhai Normal file
View File

@ -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

8
scripts/loop.rhai Normal file
View File

@ -0,0 +1,8 @@
let x = 10;
// simulate do..while using loop
loop {
print(x);
x = x - 1;
if x <= 0 { break; }
}

View File

@ -7,7 +7,7 @@ use std::fmt;
use parser::{lex, parse, Expr, Stmt, FnDef}; use parser::{lex, parse, Expr, Stmt, FnDef};
use fn_register::FnRegister; use fn_register::FnRegister;
use std::ops::{Add, Sub, Mul, Div}; use std::ops::{Add, Sub, Mul, Div, Neg};
use std::cmp::{PartialOrd, PartialEq}; use std::cmp::{PartialOrd, PartialEq};
#[derive(Debug)] #[derive(Debug)]
@ -91,14 +91,44 @@ pub enum FnType {
InternalFn(FnDef), 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::<i64>("40 + 2") {
/// println!("Answer: {}", result); // prints 42
/// }
/// }
/// ```
pub struct Engine { pub struct Engine {
pub fns: HashMap<String, Vec<FnType>>, pub fns: HashMap<String, Vec<FnType>>,
} }
/// 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::<i64>(&mut my_scope, "x + 1").unwrap(), 6);
/// ```
///
/// Between runs, `Engine` only remembers functions when not using own `Scope`.
pub type Scope = Vec<(String, Box<Any>)>; pub type Scope = Vec<(String, Box<Any>)>;
impl Engine { 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, name: &str,
arg1: Option<&mut Box<Any>>, arg1: Option<&mut Box<Any>>,
arg2: Option<&mut Box<Any>>, arg2: Option<&mut Box<Any>>,
@ -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<T: Clone + Any>(&mut self) { pub fn register_type<T: Clone + Any>(&mut self) {
fn clone_helper<T: Clone>(t: T) -> T { fn clone_helper<T: Clone>(t: T) -> T {
t.clone() t.clone()
@ -508,6 +540,7 @@ impl Engine {
self.register_fn("clone", clone_helper as fn(T) -> T); 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<T: Clone + Any, U: Clone + Any, F>(&mut self, name: &str, get_fn: F) pub fn register_get<T: Clone + Any, U: Clone + Any, F>(&mut self, name: &str, get_fn: F)
where F: 'static + Fn(&mut T) -> U where F: 'static + Fn(&mut T) -> U
{ {
@ -516,6 +549,7 @@ impl Engine {
self.register_fn(&get_name, get_fn); self.register_fn(&get_name, get_fn);
} }
/// Register a set function for a member of a registered type
pub fn register_set<T: Clone + Any, U: Clone + Any, F>(&mut self, name: &str, set_fn: F) pub fn register_set<T: Clone + Any, U: Clone + Any, F>(&mut self, name: &str, set_fn: F)
where F: 'static + Fn(&mut T, U) -> () where F: 'static + Fn(&mut T, U) -> ()
{ {
@ -524,6 +558,7 @@ impl Engine {
self.register_fn(&set_name, set_fn); self.register_fn(&set_name, set_fn);
} }
/// Shorthand for registering both getters and setters
pub fn register_get_set<T: Clone + Any, U: Clone + Any, F, G>(&mut self, pub fn register_get_set<T: Clone + Any, U: Clone + Any, F, G>(&mut self,
name: &str, name: &str,
get_fn: F, get_fn: F,
@ -1154,6 +1189,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::Break => Err(EvalAltResult::LoopBreak),
Stmt::Return => Err(EvalAltResult::Return(Box::new(()))), Stmt::Return => Err(EvalAltResult::Return(Box::new(()))),
Stmt::ReturnWithVal(ref a) => { Stmt::ReturnWithVal(ref a) => {
@ -1175,6 +1223,7 @@ impl Engine {
} }
} }
/// Evaluate a file
pub fn eval_file<T: Any + Clone>(&mut self, fname: &str) -> Result<T, EvalAltResult> { pub fn eval_file<T: Any + Clone>(&mut self, fname: &str) -> Result<T, EvalAltResult> {
use std::fs::File; use std::fs::File;
use std::io::prelude::*; use std::io::prelude::*;
@ -1192,12 +1241,14 @@ impl Engine {
} }
} }
/// Evaluate a string
pub fn eval<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> { pub fn eval<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> {
let mut scope: Scope = Vec::new(); let mut scope: Scope = Vec::new();
self.eval_with_scope(&mut scope, input) self.eval_with_scope(&mut scope, input)
} }
/// Evaluate with own scope
pub fn eval_with_scope<T: Any + Clone>(&mut self, pub fn eval_with_scope<T: Any + Clone>(&mut self,
scope: &mut Scope, scope: &mut Scope,
input: &str) input: &str)
@ -1242,6 +1293,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> { pub fn consume_file(&mut self, fname: &str) -> Result<(), EvalAltResult> {
use std::fs::File; use std::fs::File;
use std::io::prelude::*; use std::io::prelude::*;
@ -1261,6 +1315,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> { pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> {
let mut scope: Scope = Scope::new(); let mut scope: Scope = Scope::new();
@ -1269,6 +1326,9 @@ impl Engine {
res 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> { pub fn consume_with_scope(&mut self, scope: &mut Scope, input: &str) -> Result<(), EvalAltResult> {
let tokens = lex(input); let tokens = lex(input);
@ -1299,6 +1359,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) { pub fn register_default_lib(engine: &mut Engine) {
engine.register_type::<i32>(); engine.register_type::<i32>();
engine.register_type::<u32>(); engine.register_type::<u32>();
@ -1318,6 +1380,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 { macro_rules! reg_cmp {
($engine:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( ($engine:expr, $x:expr, $op:expr, $( $y:ty ),*) => (
$( $(
@ -1330,6 +1400,7 @@ impl Engine {
fn sub<T: Sub>(x: T, y: T) -> <T as Sub>::Output { x - y } fn sub<T: Sub>(x: T, y: T) -> <T as Sub>::Output { x - y }
fn mul<T: Mul>(x: T, y: T) -> <T as Mul>::Output { x * y } fn mul<T: Mul>(x: T, y: T) -> <T as Mul>::Output { x * y }
fn div<T: Div>(x: T, y: T) -> <T as Div>::Output { x / y } fn div<T: Div>(x: T, y: T) -> <T as Div>::Output { x / y }
fn neg<T: Neg>(x: T) -> <T as Neg>::Output { -x }
fn lt<T: PartialOrd>(x: T, y: T) -> bool { x < y } fn lt<T: PartialOrd>(x: T, y: T) -> bool { x < y }
fn lte<T: PartialOrd>(x: T, y: T) -> bool { x <= y } fn lte<T: PartialOrd>(x: T, y: T) -> bool { x <= y }
fn gt<T: PartialOrd>(x: T, y: T) -> bool { x > y } fn gt<T: PartialOrd>(x: T, y: T) -> bool { x > y }
@ -1338,6 +1409,7 @@ impl Engine {
fn ne<T: PartialEq>(x: T, y: T) -> bool { x != y } fn ne<T: PartialEq>(x: T, y: T) -> bool { x != y }
fn and(x: bool, y: bool) -> bool { x && y } fn and(x: bool, y: bool) -> bool { x && y }
fn or(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 } fn concat(x: String, y: String) -> String { x + &y }
reg_op!(engine, "+", add, i32, i64, u32, u64, f32, f64); reg_op!(engine, "+", add, i32, i64, u32, u64, f32, f64);
@ -1355,6 +1427,9 @@ impl Engine {
reg_op!(engine, "||", or, bool); reg_op!(engine, "||", or, bool);
reg_op!(engine, "&&", and, 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("+", concat);
// engine.register_fn("[]", idx); // engine.register_fn("[]", idx);
@ -1363,6 +1438,7 @@ impl Engine {
// (*ent).push(FnType::ExternalFn2(Box::new(idx))); // (*ent).push(FnType::ExternalFn2(Box::new(idx)));
} }
/// Make a new engine
pub fn new() -> Engine { pub fn new() -> Engine {
let mut engine = Engine { fns: HashMap::new() }; let mut engine = Engine { fns: HashMap::new() };

View File

@ -1,4 +1,39 @@
//! 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;
//! # let mut f = File::create("my_script.rhai").unwrap();
//! # let _ = write!(f, "{}", "fn f(x) { if x == 1 { return 1; } x * f(x-1) } compute_something(f(10))");
//! assert!(engine.eval_file::<bool>("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 // lints required by Rhai
#![allow(unknown_lints, #![allow(unknown_lints,
@ -16,3 +51,4 @@ mod tests;
pub use engine::{Engine, Scope}; pub use engine::{Engine, Scope};
pub use fn_register::FnRegister; pub use fn_register::FnRegister;

View File

@ -4,12 +4,13 @@ use std::iter::Peekable;
use std::str::Chars; use std::str::Chars;
use std::char; use std::char;
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum LexError { pub enum LexError {
UnexpectedChar, UnexpectedChar,
MalformedEscapeSequence, MalformedEscapeSequence,
MalformedNumber, MalformedNumber,
MalformedChar, MalformedChar,
Nothing
} }
impl Error for LexError { impl Error for LexError {
@ -19,12 +20,9 @@ impl Error for LexError {
LexError::MalformedEscapeSequence => "Unexpected values in escape sequence", LexError::MalformedEscapeSequence => "Unexpected values in escape sequence",
LexError::MalformedNumber => "Unexpected characters in number", LexError::MalformedNumber => "Unexpected characters in number",
LexError::MalformedChar => "Char constant not a single character", LexError::MalformedChar => "Char constant not a single character",
LexError::Nothing => "This error is for internal use only"
} }
} }
fn cause(&self) -> Option<&Error> {
None
}
} }
impl fmt::Display for LexError { impl fmt::Display for LexError {
@ -88,6 +86,7 @@ pub enum Stmt {
If(Box<Expr>, Box<Stmt>), If(Box<Expr>, Box<Stmt>),
IfElse(Box<Expr>, Box<Stmt>, Box<Stmt>), IfElse(Box<Expr>, Box<Stmt>, Box<Stmt>),
While(Box<Expr>, Box<Stmt>), While(Box<Expr>, Box<Stmt>),
Loop(Box<Stmt>),
Var(String, Option<Box<Expr>>), Var(String, Option<Box<Expr>>),
Block(Vec<Stmt>), Block(Vec<Stmt>),
Expr(Box<Expr>), Expr(Box<Expr>),
@ -112,7 +111,7 @@ pub enum Expr {
False, False,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum Token { pub enum Token {
IntConst(i64), IntConst(i64),
FloatConst(f64), FloatConst(f64),
@ -126,7 +125,9 @@ pub enum Token {
LSquare, LSquare,
RSquare, RSquare,
Plus, Plus,
UnaryPlus,
Minus, Minus,
UnaryMinus,
Multiply, Multiply,
Divide, Divide,
Semicolon, Semicolon,
@ -140,6 +141,7 @@ pub enum Token {
If, If,
Else, Else,
While, While,
Loop,
LessThan, LessThan,
GreaterThan, GreaterThan,
Bang, Bang,
@ -154,10 +156,98 @@ pub enum Token {
Fn, Fn,
Break, Break,
Return, Return,
PlusEquals,
MinusEquals,
LexErr(LexError), 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 |
If |
While |
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> { pub struct TokenIterator<'a> {
last: Token,
char_stream: Peekable<Chars<'a>>, char_stream: Peekable<Chars<'a>>,
} }
@ -264,12 +354,8 @@ impl<'a> TokenIterator<'a> {
let out: String = result.iter().cloned().collect(); let out: String = result.iter().cloned().collect();
Ok(out) Ok(out)
} }
}
impl<'a> Iterator for TokenIterator<'a> { fn inner_next(&mut self) -> Option<Token> {
type Item = Token;
fn next(&mut self) -> Option<Self::Item> {
while let Some(c) = self.char_stream.next() { while let Some(c) = self.char_stream.next() {
match c { match c {
'0'...'9' => { '0'...'9' => {
@ -330,6 +416,7 @@ impl<'a> Iterator for TokenIterator<'a> {
"if" => return Some(Token::If), "if" => return Some(Token::If),
"else" => return Some(Token::Else), "else" => return Some(Token::Else),
"while" => return Some(Token::While), "while" => return Some(Token::While),
"loop" => return Some(Token::Loop),
"break" => return Some(Token::Break), "break" => return Some(Token::Break),
"return" => return Some(Token::Return), "return" => return Some(Token::Return),
"fn" => return Some(Token::Fn), "fn" => return Some(Token::Fn),
@ -366,10 +453,57 @@ impl<'a> Iterator for TokenIterator<'a> {
')' => return Some(Token::RParen), ')' => return Some(Token::RParen),
'[' => return Some(Token::LSquare), '[' => return Some(Token::LSquare),
']' => return Some(Token::RSquare), ']' => 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)
},
_ if self.last.is_next_unary() => Some(Token::UnaryPlus),
_ => Some(Token::Plus),
}
},
'-' => {
return match self.char_stream.peek() {
Some(&'=') => {
self.char_stream.next();
Some(Token::MinusEquals)
},
_ if self.last.is_next_unary() => Some(Token::UnaryMinus),
_ => Some(Token::Minus),
}
},
'*' => return Some(Token::Multiply), '*' => 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::Semicolon),
':' => return Some(Token::Colon), ':' => return Some(Token::Colon),
',' => return Some(Token::Comma), ',' => return Some(Token::Comma),
@ -437,13 +571,28 @@ impl<'a> Iterator for TokenIterator<'a> {
} }
} }
impl<'a> Iterator for TokenIterator<'a> {
type Item = Token;
// TODO - perhaps this could be optimized?
fn next(&mut self) -> Option<Self::Item> {
self.last = match self.inner_next() {
Some(c) => c,
None => return None,
};
Some(self.last.clone())
}
}
pub fn lex(input: &str) -> TokenIterator { 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 { fn get_precedence(token: &Token) -> i32 {
match *token { match *token {
Token::Equals => 10, Token::Equals
| Token::PlusEquals
| Token::MinusEquals => 10,
Token::Or => 11, Token::Or => 11,
Token::And => 12, Token::And => 12,
Token::LessThan Token::LessThan
@ -587,6 +736,20 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
} }
} }
fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
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<TokenIterator<'a>>, fn parse_binop<'a>(input: &mut Peekable<TokenIterator<'a>>,
prec: i32, prec: i32,
lhs: Expr) lhs: Expr)
@ -605,7 +768,7 @@ fn parse_binop<'a>(input: &mut Peekable<TokenIterator<'a>>,
} }
if let Some(op_token) = input.next() { 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; let mut next_prec = -1;
@ -626,6 +789,20 @@ fn parse_binop<'a>(input: &mut Peekable<TokenIterator<'a>>,
Token::Multiply => Expr::FnCall("*".to_string(), vec![lhs_curr, rhs]), Token::Multiply => Expr::FnCall("*".to_string(), vec![lhs_curr, rhs]),
Token::Divide => 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::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::Period => Expr::Dot(Box::new(lhs_curr), Box::new(rhs)),
Token::EqualTo => Expr::FnCall("==".to_string(), vec![lhs_curr, rhs]), Token::EqualTo => Expr::FnCall("==".to_string(), vec![lhs_curr, rhs]),
Token::NotEqualTo => Expr::FnCall("!=".to_string(), vec![lhs_curr, rhs]), Token::NotEqualTo => Expr::FnCall("!=".to_string(), vec![lhs_curr, rhs]),
@ -646,7 +823,7 @@ fn parse_binop<'a>(input: &mut Peekable<TokenIterator<'a>>,
} }
fn parse_expr<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> { fn parse_expr<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
let lhs = try!(parse_primary(input)); let lhs = try!(parse_unary(input));
parse_binop(input, 0, lhs) parse_binop(input, 0, lhs)
} }
@ -676,6 +853,14 @@ fn parse_while<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
Ok(Stmt::While(Box::new(guard), Box::new(body))) Ok(Stmt::While(Box::new(guard), Box::new(body)))
} }
fn parse_loop<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
input.next();
let body = try!(parse_block(input));
Ok(Stmt::Loop(Box::new(body)))
}
fn parse_var<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> { fn parse_var<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
input.next(); input.next();
@ -739,6 +924,7 @@ fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
match input.peek() { match input.peek() {
Some(&Token::If) => parse_if(input), Some(&Token::If) => parse_if(input),
Some(&Token::While) => parse_while(input), Some(&Token::While) => parse_while(input),
Some(&Token::Loop) => parse_loop(input),
Some(&Token::Break) => { Some(&Token::Break) => {
input.next(); input.next();
Ok(Stmt::Break) Ok(Stmt::Break)

View File

@ -470,3 +470,93 @@ fn struct_with_float() {
assert!(false); assert!(false);
} }
} }
#[test]
fn test_comments() {
let mut engine = Engine::new();
assert!(engine.eval::<i64>("let x = 5; x // I am a single line comment, yay!").is_ok());
assert!(engine.eval::<i64>("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::<i64>("let x = -5; x").unwrap(), -5);
assert_eq!(engine.eval::<i64>("fn n(x) { -x } n(5)").unwrap(), -5);
assert_eq!(engine.eval::<i64>("5 - -(-5)").unwrap(), 0);
}
#[test]
fn test_not() {
let mut engine = Engine::new();
assert_eq!(engine.eval::<bool>("let not_true = !true; not_true").unwrap(), false);
assert_eq!(engine.eval::<bool>("fn not(x) { !x } not(false)").unwrap(), true);
// TODO - do we allow stacking unary operators directly? e.g '!!!!!!!true'
assert_eq!(engine.eval::<bool>("!(!(!(!(true))))").unwrap(), true)
}
#[test]
fn test_loop() {
let mut engine = Engine::new();
assert!(
engine.eval::<bool>("
let x = 0;
let i = 0;
loop {
if i < 10 {
x = x + i;
i = i + 1;
}
else {
break;
}
}
x == 45
").unwrap()
)
}
#[test]
fn test_increment() {
let mut engine = Engine::new();
if let Ok(result) = engine.eval::<i64>("let x = 1; x += 2; x") {
assert_eq!(result, 3);
} else {
assert!(false);
}
if let Ok(result) = engine.eval::<String>("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::<i64>("let x = 10; x -= 7; x") {
assert_eq!(result, 3);
} else {
assert!(false);
}
if let Ok(_) = engine.eval::<String>("let s = \"test\"; s -= \"ing\"; s") {
assert!(false);
} else {
assert!(true);
}
}