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` * Integer: `i32`, `u32`, `i64` (default), `u64`
* Floating-point: `f32`, `f64` (default) * Floating-point: `f32`, `f64` (default)
* Character: `char`
* Boolean: `bool` * Boolean: `bool`
* Array: `rhai::Array` * Array: `rhai::Array`
* Dynamic (i.e. can be anything): `rhai::Dynamic` * 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 x = 42;
let y = x * 100.0; // error: cannot multiply i64 with f64 let y = x * 100.0; // error: cannot multiply i64 with f64
let y = x.to_float() * 100.0; // works 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 # 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 # 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. 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 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(4); // 4 elements
y.push(5); // 5 elements y.push(5); // 5 elements
@ -526,9 +552,46 @@ let last = 'Davis';
let full_name = name + " " + middle_initial + ". " + last; let full_name = name + " " + middle_initial + ". " + last;
full_name == "Bob C. Davis"; full_name == "Bob C. Davis";
// String building with different types
let age = 42; let age = 42;
let name_and_age = full_name + ": age " + age; // String building with different types let record = full_name + ": age " + age;
name_and_age == "Bob C. Davis: age 42"; 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 ## Print and Debug

View File

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

View File

@ -54,6 +54,8 @@ pub enum ParseErrorType {
FnMissingParams, FnMissingParams,
} }
type PERR = ParseErrorType;
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Position { pub struct Position {
line: usize, line: usize,
@ -77,13 +79,11 @@ impl Position {
} }
} }
type PERR = ParseErrorType;
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct ParseError(ParseErrorType, Position); pub struct ParseError(PERR, Position);
impl ParseError { impl ParseError {
pub fn error_type(&self) -> &ParseErrorType { pub fn error_type(&self) -> &PERR {
&self.0 &self.0
} }
pub fn line(&self) -> usize { 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>("'y'"), Ok('y'));
assert_eq!(engine.eval::<char>("'\\u2764'"), Ok('❤')); 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>("''") { match engine.eval::<char>("''") {
Err(_) => (), Err(_) => (),