sync'd latest master
This commit is contained in:
parent
18c6892df3
commit
e5e58fce98
@ -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"
|
||||||
|
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
|
- `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
4
TODO
@ -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
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 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() };
|
||||||
|
|
||||||
|
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
|
// 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;
|
||||||
|
|
||||||
|
222
src/parser.rs
222
src/parser.rs
@ -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)
|
||||||
|
90
src/tests.rs
90
src/tests.rs
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user