Properly handle char types.
This commit is contained in:
parent
5f135353c0
commit
e93fd7d3fe
67
README.md
67
README.md
@ -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
|
||||||
|
488
src/engine.rs
488
src/engine.rs
@ -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`.
|
||||||
|
|
||||||
|
if is_array {
|
||||||
scope[sc_idx].1.downcast_mut::<Array>().unwrap()[idx] = target;
|
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`.
|
||||||
|
if is_array {
|
||||||
scope[sc_idx].1.downcast_mut::<Array>().unwrap()[idx] = target;
|
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.indexed_value(scope, id, idx_raw).map(|(_, _, _, x)| x)
|
||||||
}
|
}
|
||||||
Expr::Index(id, idx_raw) => self.array_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;
|
||||||
|
|
||||||
return Ok(Box::new(()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(EvalAltResult::ErrorVariableNotFound(n.clone()))
|
|
||||||
}
|
|
||||||
Expr::Index(ref id, ref idx_raw) => {
|
|
||||||
let idx = self.eval_expr(scope, &idx_raw)?;
|
|
||||||
|
|
||||||
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(()))
|
Ok(Box::new(()))
|
||||||
}
|
}
|
||||||
} else {
|
_ => Err(EvalAltResult::ErrorVariableNotFound(n.clone())),
|
||||||
Err(EvalAltResult::ErrorIndexMismatch)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
Expr::Index(ref id, ref idx_raw) => {
|
||||||
|
let idx = *match self.eval_expr(scope, &idx_raw)?.downcast_ref::<i64>() {
|
||||||
|
Some(x) => x,
|
||||||
|
_ => return Err(EvalAltResult::ErrorIndexExpr),
|
||||||
|
};
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
Err(EvalAltResult::ErrorIndexMismatch)
|
arr[idx as usize] = rhs_val;
|
||||||
|
Ok(Box::new(()))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(()))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(EvalAltResult::ErrorVariableNotFound(id.clone()))
|
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| {
|
||||||
|
if *guard_val {
|
||||||
self.eval_stmt(scope, body)
|
self.eval_stmt(scope, body)
|
||||||
} else {
|
} else {
|
||||||
Ok(Box::new(()))
|
Ok(Box::new(()))
|
||||||
}
|
}
|
||||||
}
|
}),
|
||||||
Err(_) => Err(EvalAltResult::ErrorIfGuardMismatch),
|
Stmt::IfElse(guard, body, else_body) => self
|
||||||
}
|
.eval_expr(scope, guard)?
|
||||||
}
|
.downcast::<bool>()
|
||||||
Stmt::IfElse(guard, body, else_body) => {
|
.map_err(|_| EvalAltResult::ErrorIfGuard)
|
||||||
let guard_result = self.eval_expr(scope, guard)?;
|
.and_then(|guard_val| {
|
||||||
match guard_result.downcast::<bool>() {
|
if *guard_val {
|
||||||
Ok(g) => {
|
|
||||||
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())
|
||||||
|
@ -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 {
|
||||||
|
@ -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(_) => (),
|
||||||
|
Loading…
Reference in New Issue
Block a user