sync'd latest master
This commit is contained in:
parent
18c6892df3
commit
e5e58fce98
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rhai"
|
||||
version = "0.5.1"
|
||||
version = "0.6.2"
|
||||
authors = ["Jonathan Turner", "Lukáš Hozda"]
|
||||
description = "Embedded scripting for Rust"
|
||||
homepage = "https://github.com/jonathandturner/rhai"
|
||||
|
50
README.md
50
README.md
@ -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
|
||||
- `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
|
||||
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.
|
||||
@ -289,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:
|
||||
@ -336,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:
|
||||
/*/*/*/*/**/*/*/*/*/
|
||||
*/
|
||||
```
|
||||
|
4
TODO
4
TODO
@ -1,11 +1,7 @@
|
||||
pre 1.0:
|
||||
- loop
|
||||
- binary ops
|
||||
- unary minus and negation
|
||||
- bool ops
|
||||
- basic threads
|
||||
- stdlib
|
||||
- comments
|
||||
- floats
|
||||
- REPL (consume functions)
|
||||
1.0:
|
||||
|
10
scripts/comments.rhai
Normal file
10
scripts/comments.rhai
Normal 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
8
scripts/loop.rhai
Normal file
@ -0,0 +1,8 @@
|
||||
let x = 10;
|
||||
|
||||
// simulate do..while using loop
|
||||
loop {
|
||||
print(x);
|
||||
x = x - 1;
|
||||
if x <= 0 { break; }
|
||||
}
|
@ -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::{PartialOrd, PartialEq};
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -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::<i64>("40 + 2") {
|
||||
/// println!("Answer: {}", result); // prints 42
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Engine {
|
||||
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>)>;
|
||||
|
||||
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<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) {
|
||||
fn clone_helper<T: Clone>(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<T: Clone + Any, U: Clone + Any, F>(&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<T: Clone + Any, U: Clone + Any, F>(&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<T: Clone + Any, U: Clone + Any, F, G>(&mut self,
|
||||
name: &str,
|
||||
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::Return => Err(EvalAltResult::Return(Box::new(()))),
|
||||
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> {
|
||||
use std::fs::File;
|
||||
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> {
|
||||
let mut scope: Scope = Vec::new();
|
||||
|
||||
self.eval_with_scope(&mut scope, input)
|
||||
}
|
||||
|
||||
/// Evaluate with own scope
|
||||
pub fn eval_with_scope<T: Any + Clone>(&mut self,
|
||||
scope: &mut Scope,
|
||||
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> {
|
||||
use std::fs::File;
|
||||
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> {
|
||||
let mut scope: Scope = Scope::new();
|
||||
|
||||
@ -1269,6 +1326,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);
|
||||
|
||||
@ -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) {
|
||||
engine.register_type::<i32>();
|
||||
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 {
|
||||
($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 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 neg<T: Neg>(x: T) -> <T as Neg>::Output { -x }
|
||||
fn lt<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 }
|
||||
@ -1338,6 +1409,7 @@ impl Engine {
|
||||
fn ne<T: PartialEq>(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);
|
||||
@ -1355,6 +1427,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);
|
||||
@ -1363,6 +1438,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() };
|
||||
|
||||
|
38
src/lib.rs
38
src/lib.rs
@ -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
|
||||
#![allow(unknown_lints,
|
||||
@ -16,3 +51,4 @@ mod tests;
|
||||
|
||||
pub use engine::{Engine, Scope};
|
||||
pub use fn_register::FnRegister;
|
||||
|
||||
|
222
src/parser.rs
222
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,12 +20,9 @@ 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"
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&Error> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for LexError {
|
||||
@ -88,6 +86,7 @@ pub enum Stmt {
|
||||
If(Box<Expr>, Box<Stmt>),
|
||||
IfElse(Box<Expr>, Box<Stmt>, Box<Stmt>),
|
||||
While(Box<Expr>, Box<Stmt>),
|
||||
Loop(Box<Stmt>),
|
||||
Var(String, Option<Box<Expr>>),
|
||||
Block(Vec<Stmt>),
|
||||
Expr(Box<Expr>),
|
||||
@ -112,7 +111,7 @@ pub enum Expr {
|
||||
False,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Token {
|
||||
IntConst(i64),
|
||||
FloatConst(f64),
|
||||
@ -126,7 +125,9 @@ pub enum Token {
|
||||
LSquare,
|
||||
RSquare,
|
||||
Plus,
|
||||
UnaryPlus,
|
||||
Minus,
|
||||
UnaryMinus,
|
||||
Multiply,
|
||||
Divide,
|
||||
Semicolon,
|
||||
@ -140,6 +141,7 @@ pub enum Token {
|
||||
If,
|
||||
Else,
|
||||
While,
|
||||
Loop,
|
||||
LessThan,
|
||||
GreaterThan,
|
||||
Bang,
|
||||
@ -154,10 +156,98 @@ pub enum Token {
|
||||
Fn,
|
||||
Break,
|
||||
Return,
|
||||
PlusEquals,
|
||||
MinusEquals,
|
||||
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> {
|
||||
last: Token,
|
||||
char_stream: Peekable<Chars<'a>>,
|
||||
}
|
||||
|
||||
@ -264,12 +354,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<Self::Item> {
|
||||
fn inner_next(&mut self) -> Option<Token> {
|
||||
while let Some(c) = self.char_stream.next() {
|
||||
match c {
|
||||
'0'...'9' => {
|
||||
@ -330,6 +416,7 @@ impl<'a> Iterator for 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),
|
||||
@ -366,10 +453,57 @@ 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)
|
||||
},
|
||||
_ 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::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),
|
||||
@ -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 {
|
||||
TokenIterator { char_stream: input.chars().peekable() }
|
||||
TokenIterator { last: Token::LexErr(LexError::Nothing), char_stream: input.chars().peekable() }
|
||||
}
|
||||
|
||||
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
|
||||
@ -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>>,
|
||||
prec: i32,
|
||||
lhs: Expr)
|
||||
@ -605,7 +768,7 @@ fn parse_binop<'a>(input: &mut Peekable<TokenIterator<'a>>,
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@ -626,6 +789,20 @@ fn parse_binop<'a>(input: &mut Peekable<TokenIterator<'a>>,
|
||||
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]),
|
||||
@ -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> {
|
||||
let lhs = try!(parse_primary(input));
|
||||
let lhs = try!(parse_unary(input));
|
||||
|
||||
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)))
|
||||
}
|
||||
|
||||
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> {
|
||||
input.next();
|
||||
|
||||
@ -739,6 +924,7 @@ fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
|
||||
match input.peek() {
|
||||
Some(&Token::If) => parse_if(input),
|
||||
Some(&Token::While) => parse_while(input),
|
||||
Some(&Token::Loop) => parse_loop(input),
|
||||
Some(&Token::Break) => {
|
||||
input.next();
|
||||
Ok(Stmt::Break)
|
||||
|
92
src/tests.rs
92
src/tests.rs
@ -469,4 +469,94 @@ fn struct_with_float() {
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user