Properly handle char types.

This commit is contained in:
Stephen Chung 2020-03-01 13:30:22 +08:00
parent 5f135353c0
commit e93fd7d3fe
4 changed files with 401 additions and 198 deletions

View File

@ -124,6 +124,7 @@ The following primitive types are supported natively:
* Integer: `i32`, `u32`, `i64` (default), `u64`
* Floating-point: `f32`, `f64` (default)
* Character: `char`
* Boolean: `bool`
* Array: `rhai::Array`
* Dynamic (i.e. can be anything): `rhai::Dynamic`
@ -138,6 +139,10 @@ There is a `to_float` function to convert a supported number to an `f64`, and a
let x = 42;
let y = x * 100.0; // error: cannot multiply i64 with f64
let y = x.to_float() * 100.0; // works
let z = y.to_int() + x; // works
let c = 'X'; // character
print("c is '" + c + "' and its code is " + c.to_int());
```
# Working with functions
@ -335,6 +340,23 @@ if let Ok(result) = engine.eval::<i64>("let a = new_ts(); a.x = 500; a.x") {
}
```
### WARNING: Gotcha's with Getters
When you _get_ a property, the value is cloned. Any update to it downstream will **NOT** be reflected back to the custom type.
This can introduce subtle bugs. For example:
```rust
fn change(s) {
s = 42;
}
let a = new_ts();
a.x = 500;
a.x.change(); // Only a COPY of 'a.x' is changed. 'a.x' is NOT changed.
a.x == 500;
```
# Maintaining state
By default, Rhai treats each engine invocation as a fresh one, persisting only the functions that have been defined but no top-level state. This gives each one a fairly clean starting place. Sometimes, though, you want to continue using the same top-level state from one invocation to the next.
@ -460,6 +482,10 @@ y[1] = 42;
print(y[1]); // prints 42
let foo = [1, 2, 3][0]; // a syntax error for now - cannot index into literals
let foo = ts.list[0]; // a syntax error for now - cannot index into properties
let foo = y[0]; // this works
y.push(4); // 4 elements
y.push(5); // 5 elements
@ -526,9 +552,46 @@ let last = 'Davis';
let full_name = name + " " + middle_initial + ". " + last;
full_name == "Bob C. Davis";
// String building with different types
let age = 42;
let name_and_age = full_name + ": age " + age; // String building with different types
name_and_age == "Bob C. Davis: age 42";
let record = full_name + ": age " + age;
record == "Bob C. Davis: age 42";
// Strings can be indexed to get a character
let c = record[4];
c == 'C';
let c = "foo"[0]; // a syntax error for now - cannot index into literals
let c = ts.s[0]; // a syntax error for now - cannot index into properties
let c = record[0]; // this works
// Unlike Rust, Rhai strings can be modified
record[4] = 'Z';
record == "Bob Z. Davis: age 42";
```
The following standard functions operate on strings:
* `len` - returns the number of characters (not number of bytes) in the string
* `pad` - pads the string with an character until a specified number of characters
* `truncate` - cuts off the string at exactly a specified number of characters
* `replace` - replaces a substring with another
```rust
let full_name == "Bob C. Davis";
full_name.len() == 12;
full_name.pad(15, '$');
full_name.len() = 15;
full_name == "Bob C. Davis$$$";
full_name.truncate(6);
full_name.len() = 6;
full_name == "Bob C.";
full_name.replace("Bob", "John");
full_name.len() = 7;
full_name = "John C.";
```
## Print and Debug

View File

@ -4,7 +4,7 @@ use std::collections::HashMap;
use std::error::Error;
use std::fmt;
use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub};
use std::{convert::TryInto, sync::Arc};
use std::sync::Arc;
use crate::any::{Any, AnyExt, Dynamic, Variant};
use crate::call::FunArgs;
@ -20,16 +20,17 @@ pub enum EvalAltResult {
ErrorParseError(ParseError),
ErrorFunctionNotFound(String),
ErrorFunctionArgMismatch,
ErrorArrayOutOfBounds(usize, i64),
ErrorArrayMismatch,
ErrorIndexMismatch,
ErrorIfGuardMismatch,
ErrorForMismatch,
ErrorArrayBounds(usize, i64),
ErrorStringBounds(usize, i64),
ErrorIndexing,
ErrorIndexExpr,
ErrorIfGuard,
ErrorFor,
ErrorVariableNotFound(String),
ErrorAssignmentToUnknownLHS,
ErrorMismatchOutputType(String),
ErrorCantOpenScriptFile(String),
ErrorMalformedDotExpression,
ErrorDotExpr,
LoopBreak,
Return(Dynamic),
}
@ -54,18 +55,21 @@ impl PartialEq for EvalAltResult {
(ErrorParseError(ref a), ErrorParseError(ref b)) => a == b,
(ErrorFunctionNotFound(ref a), ErrorFunctionNotFound(ref b)) => a == b,
(ErrorFunctionArgMismatch, ErrorFunctionArgMismatch) => true,
(ErrorIndexMismatch, ErrorIndexMismatch) => true,
(ErrorArrayMismatch, ErrorArrayMismatch) => true,
(ErrorArrayOutOfBounds(max1, index1), ErrorArrayOutOfBounds(max2, index2)) => {
(ErrorIndexExpr, ErrorIndexExpr) => true,
(ErrorIndexing, ErrorIndexing) => true,
(ErrorArrayBounds(max1, index1), ErrorArrayBounds(max2, index2)) => {
max1 == max2 && index1 == index2
}
(ErrorIfGuardMismatch, ErrorIfGuardMismatch) => true,
(ErrorForMismatch, ErrorForMismatch) => true,
(ErrorStringBounds(max1, index1), ErrorStringBounds(max2, index2)) => {
max1 == max2 && index1 == index2
}
(ErrorIfGuard, ErrorIfGuard) => true,
(ErrorFor, ErrorFor) => true,
(ErrorVariableNotFound(ref a), ErrorVariableNotFound(ref b)) => a == b,
(ErrorAssignmentToUnknownLHS, ErrorAssignmentToUnknownLHS) => true,
(ErrorMismatchOutputType(ref a), ErrorMismatchOutputType(ref b)) => a == b,
(ErrorCantOpenScriptFile(ref a), ErrorCantOpenScriptFile(ref b)) => a == b,
(ErrorMalformedDotExpression, ErrorMalformedDotExpression) => true,
(ErrorDotExpr, ErrorDotExpr) => true,
(LoopBreak, LoopBreak) => true,
_ => false,
}
@ -75,29 +79,32 @@ impl PartialEq for EvalAltResult {
impl Error for EvalAltResult {
fn description(&self) -> &str {
match self {
EvalAltResult::ErrorParseError(ref p) => p.description(),
EvalAltResult::ErrorFunctionNotFound(_) => "Function not found",
EvalAltResult::ErrorFunctionArgMismatch => "Function argument types do not match",
EvalAltResult::ErrorIndexMismatch => "Array access expects integer index",
EvalAltResult::ErrorArrayMismatch => "Indexing can only be performed on an array",
EvalAltResult::ErrorArrayOutOfBounds(_, ref index) if *index < 0 => {
Self::ErrorParseError(ref p) => p.description(),
Self::ErrorFunctionNotFound(_) => "Function not found",
Self::ErrorFunctionArgMismatch => "Function argument types do not match",
Self::ErrorIndexExpr => "Indexing into an array or string expects an integer index",
Self::ErrorIndexing => "Indexing can only be performed on an array or a string",
Self::ErrorArrayBounds(_, ref index) if *index < 0 => {
"Array access expects non-negative index"
}
EvalAltResult::ErrorArrayOutOfBounds(ref max, _) if *max == 0 => {
"Access of empty array"
Self::ErrorArrayBounds(ref max, _) if *max == 0 => "Access of empty array",
Self::ErrorArrayBounds(_, _) => "Array index out of bounds",
Self::ErrorStringBounds(_, ref index) if *index < 0 => {
"Indexing a string expects a non-negative index"
}
EvalAltResult::ErrorArrayOutOfBounds(_, _) => "Array index out of bounds",
EvalAltResult::ErrorIfGuardMismatch => "If guards expect boolean expression",
EvalAltResult::ErrorForMismatch => "For loops expect array",
EvalAltResult::ErrorVariableNotFound(_) => "Variable not found",
EvalAltResult::ErrorAssignmentToUnknownLHS => {
Self::ErrorStringBounds(ref max, _) if *max == 0 => "Indexing of empty string",
Self::ErrorStringBounds(_, _) => "String index out of bounds",
Self::ErrorIfGuard => "If guards expect boolean expression",
Self::ErrorFor => "For loops expect array",
Self::ErrorVariableNotFound(_) => "Variable not found",
Self::ErrorAssignmentToUnknownLHS => {
"Assignment to an unsupported left-hand side expression"
}
EvalAltResult::ErrorMismatchOutputType(_) => "Output type is incorrect",
EvalAltResult::ErrorCantOpenScriptFile(_) => "Cannot open script file",
EvalAltResult::ErrorMalformedDotExpression => "Malformed dot expression",
EvalAltResult::LoopBreak => "[Not Error] Breaks out of loop",
EvalAltResult::Return(_) => "[Not Error] Function returns value",
Self::ErrorMismatchOutputType(_) => "Output type is incorrect",
Self::ErrorCantOpenScriptFile(_) => "Cannot open script file",
Self::ErrorDotExpr => "Malformed dot expression",
Self::LoopBreak => "[Not Error] Breaks out of loop",
Self::Return(_) => "[Not Error] Function returns value",
}
}
@ -113,13 +120,22 @@ impl fmt::Display for EvalAltResult {
} else {
match self {
EvalAltResult::ErrorParseError(ref p) => write!(f, "Syntax error: {}", p),
EvalAltResult::ErrorArrayOutOfBounds(_, index) if *index < 0 => {
EvalAltResult::ErrorArrayBounds(_, index) if *index < 0 => {
write!(f, "{}: {} < 0", self.description(), index)
}
EvalAltResult::ErrorArrayOutOfBounds(max, _) if *max == 0 => {
EvalAltResult::ErrorArrayBounds(max, _) if *max == 0 => {
write!(f, "{}", self.description())
}
EvalAltResult::ErrorArrayOutOfBounds(max, index) => {
EvalAltResult::ErrorArrayBounds(max, index) => {
write!(f, "{} (max {}): {}", self.description(), max - 1, index)
}
EvalAltResult::ErrorStringBounds(_, index) if *index < 0 => {
write!(f, "{}: {} < 0", self.description(), index)
}
EvalAltResult::ErrorStringBounds(max, _) if *max == 0 => {
write!(f, "{}", self.description())
}
EvalAltResult::ErrorStringBounds(max, index) => {
write!(f, "{} (max {}): {}", self.description(), max - 1, index)
}
err => write!(f, "{}", err.description()),
@ -222,14 +238,14 @@ impl Engine {
self.fns.get(&spec1)
})
.ok_or_else(|| {
let typenames = args
let type_names = args
.iter()
.map(|x| (*(&**x).into_dynamic()).type_name())
.collect::<Vec<_>>();
EvalAltResult::ErrorFunctionNotFound(format!(
"{} ({})",
ident,
typenames.join(", ")
type_names.join(", ")
))
})
.and_then(move |f| match **f {
@ -350,35 +366,65 @@ impl Engine {
self.call_fn_raw(get_fn_name, vec![this_ptr])
}
Expr::Index(id, idx_raw) => {
let idx = self.eval_expr(scope, idx_raw)?;
let idx = self
.eval_expr(scope, idx_raw)?
.downcast_ref::<i64>()
.map(|i| *i)
.ok_or(EvalAltResult::ErrorIndexExpr)?;
let get_fn_name = "get$".to_string() + id;
let mut val = self.call_fn_raw(get_fn_name, vec![this_ptr])?;
((*val).downcast_mut() as Option<&mut Array>)
.ok_or(EvalAltResult::ErrorArrayMismatch)
.and_then(|arr| {
idx.downcast_ref::<i64>()
.map(|idx| (arr, *idx))
.ok_or(EvalAltResult::ErrorIndexMismatch)
})
.and_then(|(arr, idx)| match idx {
x if x < 0 => Err(EvalAltResult::ErrorArrayOutOfBounds(0, x)),
x => arr
.get(x as usize)
if let Some(arr) = (*val).downcast_mut() as Option<&mut Array> {
if idx < 0 {
Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx))
} else {
arr.get(idx as usize)
.cloned()
.ok_or(EvalAltResult::ErrorArrayOutOfBounds(arr.len(), x)),
})
.ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx))
}
} else if let Some(s) = (*val).downcast_mut() as Option<&mut String> {
if idx < 0 {
Err(EvalAltResult::ErrorStringBounds(s.chars().count(), idx))
} else {
s.chars()
.nth(idx as usize)
.map(|ch| Box::new(ch) as Dynamic)
.ok_or_else(|| EvalAltResult::ErrorStringBounds(s.chars().count(), idx))
}
} else {
Err(EvalAltResult::ErrorIndexing)
}
}
Expr::Dot(inner_lhs, inner_rhs) => match **inner_lhs {
Expr::Identifier(ref id) => {
let get_fn_name = "get$".to_string() + id;
self.call_fn_raw(get_fn_name, vec![this_ptr])
.and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), inner_rhs))
let value = self
.call_fn_raw(get_fn_name, vec![this_ptr])
.and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), inner_rhs))?;
// TODO - Should propagate changes back in this scenario:
//
// fn update(p) { p = something_else; }
// obj.prop.update();
//
// Right now, a copy of the object's property value is mutated, but not propagated
// back to the property via $set.
Ok(value)
}
_ => Err(EvalAltResult::ErrorMalformedDotExpression),
Expr::Index(_, _) => {
// TODO - Handle Expr::Index for these scenarios:
//
// let x = obj.prop[2].x;
// obj.prop[3] = 42;
//
Err(EvalAltResult::ErrorDotExpr)
}
_ => Err(EvalAltResult::ErrorDotExpr),
},
_ => Err(EvalAltResult::ErrorMalformedDotExpression),
_ => Err(EvalAltResult::ErrorDotExpr),
}
}
@ -399,32 +445,64 @@ impl Engine {
.and_then(move |(idx, &mut (_, ref mut val))| map(val.as_mut()).map(|val| (idx, val)))
}
fn array_value(
fn indexed_value(
&self,
scope: &mut Scope,
id: &str,
idx: &Expr,
) -> Result<(usize, usize, Dynamic), EvalAltResult> {
let idx_boxed = self
) -> Result<(bool, usize, usize, Dynamic), EvalAltResult> {
let idx = *self
.eval_expr(scope, idx)?
.downcast::<i64>()
.map_err(|_| EvalAltResult::ErrorIndexMismatch)?;
let idx_raw = *idx_boxed;
let idx = match idx_raw {
x if x < 0 => return Err(EvalAltResult::ErrorArrayOutOfBounds(0, x)),
x => x as usize,
};
let (idx_sc, val) = Self::search_scope(scope, id, |val| {
((*val).downcast_mut() as Option<&mut Array>)
.ok_or(EvalAltResult::ErrorArrayMismatch)
.and_then(|arr| {
arr.get(idx)
.cloned()
.ok_or(EvalAltResult::ErrorArrayOutOfBounds(arr.len(), idx_raw))
})
})?;
.map_err(|_| EvalAltResult::ErrorIndexExpr)?;
Ok((idx_sc, idx, val))
let mut is_array = false;
Self::search_scope(scope, id, |val| {
if let Some(arr) = (*val).downcast_mut() as Option<&mut Array> {
is_array = true;
return if idx < 0 {
Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx))
} else {
arr.get(idx as usize)
.cloned()
.ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx))
};
}
if let Some(s) = (*val).downcast_mut() as Option<&mut String> {
is_array = false;
return if idx < 0 {
Err(EvalAltResult::ErrorStringBounds(s.chars().count(), idx))
} else {
s.chars()
.nth(idx as usize)
.map(|ch| Box::new(ch) as Dynamic)
.ok_or_else(|| EvalAltResult::ErrorStringBounds(s.chars().count(), idx))
};
}
Err(EvalAltResult::ErrorIndexing)
})
.map(|(idx_sc, val)| (is_array, idx_sc, idx as usize, val))
}
fn str_replace_char(s: &mut String, idx: usize, new_ch: char) {
// The new character
let ch = s.chars().nth(idx).unwrap();
// See if changed - if so, update the String
if ch == new_ch {
return;
}
// Collect all the characters after the index
let mut chars: Vec<char> = s.chars().collect();
chars[idx] = new_ch;
s.truncate(0);
chars.iter().for_each(|&ch| s.push(ch));
}
fn get_dot_val(
@ -445,16 +523,27 @@ impl Engine {
value
}
Expr::Index(id, idx_raw) => {
let (sc_idx, idx, mut target) = self.array_value(scope, id, idx_raw)?;
let (is_array, sc_idx, idx, mut target) = self.indexed_value(scope, id, idx_raw)?;
let value = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs);
// In case the expression mutated `target`, we need to reassign it because
// of the above `clone`.
scope[sc_idx].1.downcast_mut::<Array>().unwrap()[idx] = target;
if is_array {
scope[sc_idx].1.downcast_mut::<Array>().unwrap()[idx] = target;
} else {
// Target should be a char
let new_ch = *target.downcast::<char>().unwrap();
// Root should be a String
let s = scope[sc_idx].1.downcast_mut::<String>().unwrap();
Self::str_replace_char(s, idx, new_ch);
}
value
}
_ => Err(EvalAltResult::ErrorMalformedDotExpression),
_ => Err(EvalAltResult::ErrorDotExpr),
}
}
@ -483,9 +572,9 @@ impl Engine {
self.call_fn_raw(set_fn_name, vec![this_ptr, v.as_mut()])
})
}
_ => Err(EvalAltResult::ErrorMalformedDotExpression),
_ => Err(EvalAltResult::ErrorDotExpr),
},
_ => Err(EvalAltResult::ErrorMalformedDotExpression),
_ => Err(EvalAltResult::ErrorDotExpr),
}
}
@ -508,16 +597,26 @@ impl Engine {
value
}
Expr::Index(id, idx_raw) => {
let (sc_idx, idx, mut target) = self.array_value(scope, id, idx_raw)?;
let (is_array, sc_idx, idx, mut target) = self.indexed_value(scope, id, idx_raw)?;
let value = self.set_dot_val_helper(target.as_mut(), dot_rhs, source_val);
// In case the expression mutated `target`, we need to reassign it because
// of the above `clone`.
scope[sc_idx].1.downcast_mut::<Array>().unwrap()[idx] = target;
if is_array {
scope[sc_idx].1.downcast_mut::<Array>().unwrap()[idx] = target;
} else {
// Target should be a char
let new_ch = *target.downcast::<char>().unwrap();
// Root should be a String
let s = scope[sc_idx].1.downcast_mut::<String>().unwrap();
Self::str_replace_char(s, idx, new_ch);
}
value
}
_ => Err(EvalAltResult::ErrorMalformedDotExpression),
_ => Err(EvalAltResult::ErrorDotExpr),
}
}
@ -528,58 +627,72 @@ impl Engine {
Expr::StringConstant(s) => Ok(Box::new(s.clone())),
Expr::CharConstant(c) => Ok(Box::new(*c)),
Expr::Identifier(id) => {
for &mut (ref name, ref mut val) in &mut scope.iter_mut().rev() {
if id == name {
return Ok(val.clone());
}
match scope.iter().rev().filter(|(name, _)| id == name).next() {
Some((_, val)) => Ok(val.clone()),
_ => Err(EvalAltResult::ErrorVariableNotFound(id.clone())),
}
Err(EvalAltResult::ErrorVariableNotFound(id.clone()))
}
Expr::Index(id, idx_raw) => self.array_value(scope, id, idx_raw).map(|(_, _, x)| x),
Expr::Index(id, idx_raw) => {
self.indexed_value(scope, id, idx_raw).map(|(_, _, _, x)| x)
}
Expr::Assignment(ref id, rhs) => {
let rhs_val = self.eval_expr(scope, rhs)?;
match **id {
Expr::Identifier(ref n) => {
for &mut (ref name, ref mut val) in &mut scope.iter_mut().rev() {
if n == name {
match scope.iter_mut().rev().filter(|(name, _)| n == name).next() {
Some((_, val)) => {
*val = rhs_val;
return Ok(Box::new(()));
Ok(Box::new(()))
}
_ => Err(EvalAltResult::ErrorVariableNotFound(n.clone())),
}
Err(EvalAltResult::ErrorVariableNotFound(n.clone()))
}
Expr::Index(ref id, ref idx_raw) => {
let idx = self.eval_expr(scope, &idx_raw)?;
let idx = *match self.eval_expr(scope, &idx_raw)?.downcast_ref::<i64>() {
Some(x) => x,
_ => return Err(EvalAltResult::ErrorIndexExpr),
};
for &mut (ref name, ref mut val) in &mut scope.iter_mut().rev() {
if id == name {
return if let Some(&i) = idx.downcast_ref::<i64>() {
if let Some(arr_typed) =
(*val).downcast_mut() as Option<&mut Array>
{
if i < 0 {
Err(EvalAltResult::ErrorArrayOutOfBounds(0, i))
} else if i as usize >= arr_typed.len() {
Err(EvalAltResult::ErrorArrayOutOfBounds(
arr_typed.len(),
i,
))
} else {
arr_typed[i as usize] = rhs_val;
Ok(Box::new(()))
}
} else {
Err(EvalAltResult::ErrorIndexMismatch)
}
} else {
Err(EvalAltResult::ErrorIndexMismatch)
};
}
let variable = &mut scope
.iter_mut()
.rev()
.filter(|(name, _)| id == name)
.map(|(_, val)| val)
.next();
let val = match variable {
Some(v) => v,
_ => return Err(EvalAltResult::ErrorVariableNotFound(id.clone())),
};
if let Some(arr) = val.downcast_mut() as Option<&mut Array> {
return if idx < 0 {
Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx))
} else if idx as usize >= arr.len() {
Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx))
} else {
arr[idx as usize] = rhs_val;
Ok(Box::new(()))
};
}
Err(EvalAltResult::ErrorVariableNotFound(id.clone()))
if let Some(s) = val.downcast_mut() as Option<&mut String> {
let s_len = s.chars().count();
return if idx < 0 {
Err(EvalAltResult::ErrorStringBounds(s_len, idx))
} else if idx as usize >= s_len {
Err(EvalAltResult::ErrorStringBounds(s_len, idx))
} else {
// Should be a char
let new_ch = *rhs_val.downcast::<char>().unwrap();
Self::str_replace_char(s, idx as usize, new_ch);
Ok(Box::new(()))
};
}
return Err(EvalAltResult::ErrorIndexExpr);
}
Expr::Dot(ref dot_lhs, ref dot_rhs) => {
self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val)
@ -591,10 +704,11 @@ impl Engine {
Expr::Array(contents) => {
let mut arr = Vec::new();
for item in &(*contents) {
contents.iter().try_for_each(|item| {
let arg = self.eval_expr(scope, item)?;
arr.push(arg);
}
Ok(())
})?;
Ok(Box::new(arr))
}
@ -615,13 +729,13 @@ impl Engine {
fn eval_stmt(&self, scope: &mut Scope, stmt: &Stmt) -> Result<Dynamic, EvalAltResult> {
match stmt {
Stmt::Expr(e) => self.eval_expr(scope, e),
Stmt::Block(b) => {
Stmt::Expr(expr) => self.eval_expr(scope, expr),
Stmt::Block(block) => {
let prev_len = scope.len();
let mut last_result: Result<Dynamic, EvalAltResult> = Ok(Box::new(()));
for s in b.iter() {
last_result = self.eval_stmt(scope, s);
for block_stmt in block.iter() {
last_result = self.eval_stmt(scope, block_stmt);
if let Err(x) = last_result {
last_result = Err(x);
break;
@ -634,37 +748,32 @@ impl Engine {
last_result
}
Stmt::If(guard, body) => {
let guard_result = self.eval_expr(scope, guard)?;
match guard_result.downcast::<bool>() {
Ok(g) => {
if *g {
self.eval_stmt(scope, body)
} else {
Ok(Box::new(()))
}
Stmt::If(guard, body) => self
.eval_expr(scope, guard)?
.downcast::<bool>()
.map_err(|_| EvalAltResult::ErrorIfGuard)
.and_then(|guard_val| {
if *guard_val {
self.eval_stmt(scope, body)
} else {
Ok(Box::new(()))
}
Err(_) => Err(EvalAltResult::ErrorIfGuardMismatch),
}
}
Stmt::IfElse(guard, body, else_body) => {
let guard_result = self.eval_expr(scope, guard)?;
match guard_result.downcast::<bool>() {
Ok(g) => {
if *g {
self.eval_stmt(scope, body)
} else {
self.eval_stmt(scope, else_body)
}
}),
Stmt::IfElse(guard, body, else_body) => self
.eval_expr(scope, guard)?
.downcast::<bool>()
.map_err(|_| EvalAltResult::ErrorIfGuard)
.and_then(|guard_val| {
if *guard_val {
self.eval_stmt(scope, body)
} else {
self.eval_stmt(scope, else_body)
}
Err(_) => Err(EvalAltResult::ErrorIfGuardMismatch),
}
}
}),
Stmt::While(guard, body) => loop {
let guard_result = self.eval_expr(scope, guard)?;
match guard_result.downcast::<bool>() {
Ok(g) => {
if *g {
match self.eval_expr(scope, guard)?.downcast::<bool>() {
Ok(guard_val) => {
if *guard_val {
match self.eval_stmt(scope, body) {
Err(EvalAltResult::LoopBreak) => return Ok(Box::new(())),
Err(x) => return Err(x),
@ -674,7 +783,7 @@ impl Engine {
return Ok(Box::new(()));
}
}
Err(_) => return Err(EvalAltResult::ErrorIfGuardMismatch),
Err(_) => return Err(EvalAltResult::ErrorIfGuard),
}
},
Stmt::Loop(body) => loop {
@ -701,7 +810,7 @@ impl Engine {
scope.remove(idx);
Ok(Box::new(()))
} else {
return Err(EvalAltResult::ErrorForMismatch);
return Err(EvalAltResult::ErrorFor);
}
}
Stmt::Break => Err(EvalAltResult::LoopBreak),
@ -711,13 +820,12 @@ impl Engine {
Err(EvalAltResult::Return(result))
}
Stmt::Let(name, init) => {
match init {
Some(v) => {
let i = self.eval_expr(scope, v)?;
scope.push((name.clone(), i));
}
None => scope.push((name.clone(), Box::new(()))),
};
if let Some(v) = init {
let i = self.eval_expr(scope, v)?;
scope.push((name.clone(), i));
} else {
scope.push((name.clone(), Box::new(())));
}
Ok(Box::new(()))
}
}
@ -734,38 +842,38 @@ impl Engine {
}
/// Compile a file into an AST
pub fn compile_file(fname: &str) -> Result<AST, EvalAltResult> {
pub fn compile_file(filename: &str) -> Result<AST, EvalAltResult> {
use std::fs::File;
use std::io::prelude::*;
if let Ok(mut f) = File::open(fname) {
if let Ok(mut f) = File::open(filename) {
let mut contents = String::new();
if f.read_to_string(&mut contents).is_ok() {
Self::compile(&contents).map_err(|err| EvalAltResult::ErrorParseError(err))
} else {
Err(EvalAltResult::ErrorCantOpenScriptFile(fname.to_owned()))
Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned()))
}
} else {
Err(EvalAltResult::ErrorCantOpenScriptFile(fname.to_owned()))
Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned()))
}
}
/// 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, filename: &str) -> Result<T, EvalAltResult> {
use std::fs::File;
use std::io::prelude::*;
if let Ok(mut f) = File::open(fname) {
if let Ok(mut f) = File::open(filename) {
let mut contents = String::new();
if f.read_to_string(&mut contents).is_ok() {
self.eval::<T>(&contents)
} else {
Err(EvalAltResult::ErrorCantOpenScriptFile(fname.to_owned()))
Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned()))
}
} else {
Err(EvalAltResult::ErrorCantOpenScriptFile(fname.to_owned()))
Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned()))
}
}
@ -830,11 +938,11 @@ 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, filename: &str) -> Result<(), EvalAltResult> {
use std::fs::File;
use std::io::prelude::*;
if let Ok(mut f) = File::open(fname) {
if let Ok(mut f) = File::open(filename) {
let mut contents = String::new();
if f.read_to_string(&mut contents).is_ok() {
@ -844,10 +952,10 @@ impl Engine {
Ok(())
}
} else {
Err(EvalAltResult::ErrorCantOpenScriptFile(fname.to_owned()))
Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned()))
}
} else {
Err(EvalAltResult::ErrorCantOpenScriptFile(fname.to_owned()))
Err(EvalAltResult::ErrorCantOpenScriptFile(filename.to_owned()))
}
}
@ -1040,12 +1148,12 @@ impl Engine {
reg_op!(engine, "*", mul, i32, i64, u32, u64, f32, f64);
reg_op!(engine, "/", div, i32, i64, u32, u64, f32, f64);
reg_cmp!(engine, "<", lt, i32, i64, u32, u64, String, f64);
reg_cmp!(engine, "<=", lte, i32, i64, u32, u64, String, f64);
reg_cmp!(engine, ">", gt, i32, i64, u32, u64, String, f64);
reg_cmp!(engine, ">=", gte, i32, i64, u32, u64, String, f64);
reg_cmp!(engine, "==", eq, i32, i64, u32, u64, bool, String, f64);
reg_cmp!(engine, "!=", ne, i32, i64, u32, u64, bool, String, f64);
reg_cmp!(engine, "<", lt, i32, i64, u32, u64, String, char, f32, f64);
reg_cmp!(engine, "<=", lte, i32, i64, u32, u64, String, char, f32, f64);
reg_cmp!(engine, ">", gt, i32, i64, u32, u64, String, char, f32, f64);
reg_cmp!(engine, ">=", gte, i32, i64, u32, u64, String, char, f32, f64);
reg_cmp!(engine, "==", eq, i32, i64, u32, u64, bool, String, char, f32, f64);
reg_cmp!(engine, "!=", ne, i32, i64, u32, u64, bool, String, char, f32, f64);
reg_op!(engine, "||", or, bool);
reg_op!(engine, "&&", and, bool);
@ -1084,6 +1192,7 @@ impl Engine {
engine.register_fn("to_int", |x: u64| x as i64);
engine.register_fn("to_int", |x: f32| x as i64);
engine.register_fn("to_int", |x: f64| x as i64);
engine.register_fn("to_int", |ch: char| ch as i64);
// Register print and debug
fn print_debug<T: Debug>(x: T) -> String {
@ -1094,15 +1203,15 @@ impl Engine {
}
reg_func1!(engine, "print", print, String, i32, i64, u32, u64);
reg_func1!(engine, "print", print, String, f32, f64, bool, String);
reg_func1!(engine, "print", print, String, f32, f64, bool, char, String);
reg_func1!(engine, "print", print_debug, String, Array);
engine.register_fn("print", |_: ()| println!());
reg_func1!(engine, "debug", print_debug, String, i32, i64, u32, u64);
reg_func1!(engine, "debug", print_debug, String, f32, f64, bool, String);
reg_func1!(engine, "debug", print_debug, String, Array, ());
reg_func1!(engine, "debug", print_debug, String, f32, f64, bool, char);
reg_func1!(engine, "debug", print_debug, String, String, Array, ());
// Register array functions
// Register array utility functions
fn push<T: Any>(list: &mut Array, item: T) {
list.push(Box::new(item));
}
@ -1115,11 +1224,11 @@ impl Engine {
}
reg_func2x!(engine, "push", push, &mut Array, (), i32, i64, u32, u64);
reg_func2x!(engine, "push", push, &mut Array, (), f32, f64, bool);
reg_func2x!(engine, "push", push, &mut Array, (), f32, f64, bool, char);
reg_func2x!(engine, "push", push, &mut Array, (), String, Array, ());
reg_func3!(engine, "pad", pad, &mut Array, i64, (), i32, i64);
reg_func3!(engine, "pad", pad, &mut Array, i64, (), u32, u64);
reg_func3!(engine, "pad", pad, &mut Array, i64, (), f32, f64, bool);
reg_func3!(engine, "pad", pad, &mut Array, i64, (), i32, u32, f32);
reg_func3!(engine, "pad", pad, &mut Array, i64, (), i64, u64, f64);
reg_func3!(engine, "pad", pad, &mut Array, i64, (), bool, char);
reg_func3!(engine, "pad", pad, &mut Array, i64, (), String, Array, ());
engine.register_dynamic_fn("pop", |list: &mut Array| list.pop().unwrap_or(Box::new(())));
@ -1130,9 +1239,7 @@ impl Engine {
Box::new(())
}
});
engine.register_fn("len", |list: &mut Array| -> i64 {
list.len().try_into().unwrap()
});
engine.register_fn("len", |list: &mut Array| -> i64 { list.len() as i64 });
engine.register_fn("truncate", |list: &mut Array, len: i64| {
if len >= 0 {
list.truncate(len as usize);
@ -1147,14 +1254,37 @@ impl Engine {
format!("{}{}", x, y)
}
reg_func2x!(engine, "+", append, String, String, i32, i64, u32, u64, f32, f64, bool);
reg_func2x!(engine, "+", append, String, String, i32, i64, u32, u64, f32, f64, bool, char);
engine.register_fn("+", |x: String, y: Array| format!("{}{:?}", x, y));
engine.register_fn("+", |x: String, _: ()| format!("{}", x));
reg_func2y!(engine, "+", prepend, String, String, i32, i64, u32, u64, f32, f64, bool);
reg_func2y!(engine, "+", prepend, String, String, i32, i64, u32, u64, f32, f64, bool, char);
engine.register_fn("+", |x: Array, y: String| format!("{:?}{}", x, y));
engine.register_fn("+", |_: (), y: String| format!("{}", y));
// Register string utility functions
engine.register_fn("len", |s: &mut String| -> i64 { s.chars().count() as i64 });
engine.register_fn("truncate", |s: &mut String, len: i64| {
if len >= 0 {
s.truncate(len as usize);
}
});
engine.register_fn("pad", |s: &mut String, len: i64, ch: char| {
let gap = s.chars().count() - len as usize;
for _ in 0..gap {
s.push(ch);
}
});
engine.register_fn(
"replace",
|s: &mut String, pattern: String, replace: String| {
let new_str = s.replace(&pattern, &replace);
s.truncate(0);
s.push_str(&new_str);
},
);
// Register array iterator
engine.register_iterator::<Array, _>(|a| {
Box::new(a.downcast_ref::<Array>().unwrap().clone().into_iter())

View File

@ -54,6 +54,8 @@ pub enum ParseErrorType {
FnMissingParams,
}
type PERR = ParseErrorType;
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Position {
line: usize,
@ -77,13 +79,11 @@ impl Position {
}
}
type PERR = ParseErrorType;
#[derive(Debug, PartialEq, Clone)]
pub struct ParseError(ParseErrorType, Position);
pub struct ParseError(PERR, Position);
impl ParseError {
pub fn error_type(&self) -> &ParseErrorType {
pub fn error_type(&self) -> &PERR {
&self.0
}
pub fn line(&self) -> usize {

View File

@ -6,6 +6,16 @@ fn test_chars() {
assert_eq!(engine.eval::<char>("'y'"), Ok('y'));
assert_eq!(engine.eval::<char>("'\\u2764'"), Ok('❤'));
assert_eq!(engine.eval::<char>(r#"let x="hello"; x[2]"#), Ok('l'));
assert_eq!(
engine.eval::<String>(r#"let x="hello"; x[2]='$'; x"#),
Ok("he$lo".into())
);
match engine.eval::<char>("'\\uhello'") {
Err(_) => (),
_ => assert!(false),
}
match engine.eval::<char>("''") {
Err(_) => (),