Fix bug in index expressions.
This commit is contained in:
parent
e1242df5c8
commit
2bcc51cc45
@ -1051,8 +1051,7 @@ impl Engine {
|
|||||||
let mut state = State::new();
|
let mut state = State::new();
|
||||||
let args = args.as_mut();
|
let args = args.as_mut();
|
||||||
|
|
||||||
let result =
|
let result = self.call_script_fn(scope, &mut state, &lib, name, fn_def, args, pos, 0)?;
|
||||||
self.call_script_fn(Some(scope), &mut state, &lib, name, fn_def, args, pos, 0)?;
|
|
||||||
|
|
||||||
let return_type = self.map_type_name(result.type_name());
|
let return_type = self.map_type_name(result.type_name());
|
||||||
|
|
||||||
|
151
src/engine.rs
151
src/engine.rs
@ -432,11 +432,11 @@ fn default_print(s: &str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Search for a variable within the scope
|
/// Search for a variable within the scope
|
||||||
fn search_scope<'a>(
|
fn search_scope<'s, 'a>(
|
||||||
scope: &'a mut Scope,
|
scope: &'s mut Scope,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
expr: &'a Expr,
|
expr: &'a Expr,
|
||||||
) -> Result<(&'a mut Dynamic, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> {
|
) -> Result<(&'s mut Dynamic, &'a str, ScopeEntryType, Position), Box<EvalAltResult>> {
|
||||||
let ((name, pos), modules, hash_var, index) = match expr {
|
let ((name, pos), modules, hash_var, index) = match expr {
|
||||||
Expr::Variable(x) => x.as_ref(),
|
Expr::Variable(x) => x.as_ref(),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
@ -592,7 +592,7 @@ impl Engine {
|
|||||||
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
||||||
pub(crate) fn call_fn_raw(
|
pub(crate) fn call_fn_raw(
|
||||||
&self,
|
&self,
|
||||||
scope: Option<&mut Scope>,
|
scope: &mut Scope,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &FunctionsLib,
|
lib: &FunctionsLib,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
@ -774,7 +774,7 @@ impl Engine {
|
|||||||
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
||||||
pub(crate) fn call_script_fn(
|
pub(crate) fn call_script_fn(
|
||||||
&self,
|
&self,
|
||||||
scope: Option<&mut Scope>,
|
scope: &mut Scope,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &FunctionsLib,
|
lib: &FunctionsLib,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
@ -786,88 +786,42 @@ impl Engine {
|
|||||||
let orig_scope_level = state.scope_level;
|
let orig_scope_level = state.scope_level;
|
||||||
state.scope_level += 1;
|
state.scope_level += 1;
|
||||||
|
|
||||||
match scope {
|
let scope_len = scope.len();
|
||||||
// Extern scope passed in which is not empty
|
|
||||||
Some(scope) if !scope.is_empty() => {
|
|
||||||
let scope_len = scope.len();
|
|
||||||
|
|
||||||
// Put arguments into scope as variables
|
// Put arguments into scope as variables
|
||||||
// Actually consume the arguments instead of cloning them
|
// Actually consume the arguments instead of cloning them
|
||||||
scope.extend(
|
scope.extend(
|
||||||
fn_def
|
fn_def
|
||||||
.params
|
.params
|
||||||
.iter()
|
.iter()
|
||||||
.zip(args.iter_mut().map(|v| mem::take(*v)))
|
.zip(args.iter_mut().map(|v| mem::take(*v)))
|
||||||
.map(|(name, value)| {
|
.map(|(name, value)| {
|
||||||
let var_name = unsafe_cast_var_name_to_lifetime(name.as_str(), state);
|
let var_name = unsafe_cast_var_name_to_lifetime(name.as_str(), state);
|
||||||
(var_name, ScopeEntryType::Normal, value)
|
(var_name, ScopeEntryType::Normal, value)
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Evaluate the function at one higher level of call depth
|
// Evaluate the function at one higher level of call depth
|
||||||
let result = self
|
let result = self
|
||||||
.eval_stmt(scope, state, lib, &fn_def.body, level + 1)
|
.eval_stmt(scope, state, lib, &fn_def.body, level + 1)
|
||||||
.or_else(|err| match *err {
|
.or_else(|err| match *err {
|
||||||
// Convert return statement to return value
|
// Convert return statement to return value
|
||||||
EvalAltResult::Return(x, _) => Ok(x),
|
EvalAltResult::Return(x, _) => Ok(x),
|
||||||
EvalAltResult::ErrorInFunctionCall(name, err, _) => {
|
EvalAltResult::ErrorInFunctionCall(name, err, _) => Err(Box::new(
|
||||||
Err(Box::new(EvalAltResult::ErrorInFunctionCall(
|
EvalAltResult::ErrorInFunctionCall(format!("{} > {}", fn_name, name), err, pos),
|
||||||
format!("{} > {}", fn_name, name),
|
)),
|
||||||
err,
|
_ => Err(Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||||
pos,
|
fn_name.to_string(),
|
||||||
)))
|
err,
|
||||||
}
|
pos,
|
||||||
_ => Err(Box::new(EvalAltResult::ErrorInFunctionCall(
|
))),
|
||||||
fn_name.to_string(),
|
});
|
||||||
err,
|
|
||||||
pos,
|
|
||||||
))),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove all local variables
|
// Remove all local variables
|
||||||
scope.rewind(scope_len);
|
scope.rewind(scope_len);
|
||||||
state.scope_level = orig_scope_level;
|
state.scope_level = orig_scope_level;
|
||||||
|
|
||||||
return result;
|
result
|
||||||
}
|
|
||||||
// No new scope - create internal scope
|
|
||||||
_ => {
|
|
||||||
let mut scope = Scope::new();
|
|
||||||
|
|
||||||
// Put arguments into scope as variables
|
|
||||||
// Actually consume the arguments instead of cloning them
|
|
||||||
scope.extend(
|
|
||||||
fn_def
|
|
||||||
.params
|
|
||||||
.iter()
|
|
||||||
.zip(args.iter_mut().map(|v| mem::take(*v)))
|
|
||||||
.map(|(name, value)| (name, ScopeEntryType::Normal, value)),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Evaluate the function at one higher level of call depth
|
|
||||||
let result = self
|
|
||||||
.eval_stmt(&mut scope, state, lib, &fn_def.body, level + 1)
|
|
||||||
.or_else(|err| match *err {
|
|
||||||
// Convert return statement to return value
|
|
||||||
EvalAltResult::Return(x, _) => Ok(x),
|
|
||||||
EvalAltResult::ErrorInFunctionCall(name, err, _) => {
|
|
||||||
Err(Box::new(EvalAltResult::ErrorInFunctionCall(
|
|
||||||
format!("{} > {}", fn_name, name),
|
|
||||||
err,
|
|
||||||
pos,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
_ => Err(Box::new(EvalAltResult::ErrorInFunctionCall(
|
|
||||||
fn_name.to_string(),
|
|
||||||
err,
|
|
||||||
pos,
|
|
||||||
))),
|
|
||||||
});
|
|
||||||
|
|
||||||
state.scope_level = orig_scope_level;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has a system function an override?
|
// Has a system function an override?
|
||||||
@ -925,9 +879,12 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Normal function call
|
// Normal function call
|
||||||
_ => self.call_fn_raw(
|
_ => {
|
||||||
None, state, lib, fn_name, hashes, args, is_ref, def_val, pos, level,
|
let mut scope = Scope::new();
|
||||||
),
|
self.call_fn_raw(
|
||||||
|
&mut scope, state, lib, fn_name, hashes, args, is_ref, def_val, pos, level,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1252,14 +1209,16 @@ impl Engine {
|
|||||||
Expr::FnCall(_) => unreachable!(),
|
Expr::FnCall(_) => unreachable!(),
|
||||||
Expr::Property(_) => idx_values.push(()), // Store a placeholder - no need to copy the property name
|
Expr::Property(_) => idx_values.push(()), // Store a placeholder - no need to copy the property name
|
||||||
Expr::Index(x) | Expr::Dot(x) => {
|
Expr::Index(x) | Expr::Dot(x) => {
|
||||||
|
let (lhs, rhs, _) = x.as_ref();
|
||||||
|
|
||||||
// Evaluate in left-to-right order
|
// Evaluate in left-to-right order
|
||||||
let lhs_val = match x.0 {
|
let lhs_val = match lhs {
|
||||||
Expr::Property(_) => Default::default(), // Store a placeholder in case of a property
|
Expr::Property(_) => Default::default(), // Store a placeholder in case of a property
|
||||||
_ => self.eval_expr(scope, state, lib, &x.0, level)?,
|
_ => self.eval_expr(scope, state, lib, lhs, level)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Push in reverse order
|
// Push in reverse order
|
||||||
self.eval_indexed_chain(scope, state, lib, &x.1, idx_values, size, level)?;
|
self.eval_indexed_chain(scope, state, lib, rhs, idx_values, size, level)?;
|
||||||
|
|
||||||
idx_values.push(lhs_val);
|
idx_values.push(lhs_val);
|
||||||
}
|
}
|
||||||
@ -1380,6 +1339,7 @@ impl Engine {
|
|||||||
Dynamic(Union::Array(mut rhs_value)) => {
|
Dynamic(Union::Array(mut rhs_value)) => {
|
||||||
let op = "==";
|
let op = "==";
|
||||||
let def_value = false.into();
|
let def_value = false.into();
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
// Call the `==` operator to compare each value
|
// Call the `==` operator to compare each value
|
||||||
for value in rhs_value.iter_mut() {
|
for value in rhs_value.iter_mut() {
|
||||||
@ -1394,7 +1354,7 @@ impl Engine {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let (r, _) = self.call_fn_raw(
|
let (r, _) = self.call_fn_raw(
|
||||||
None, state, lib, op, hashes, args, false, def_value, pos, level,
|
&mut scope, state, lib, op, hashes, args, false, def_value, pos, level,
|
||||||
)?;
|
)?;
|
||||||
if r.as_bool().unwrap_or(false) {
|
if r.as_bool().unwrap_or(false) {
|
||||||
return Ok(true.into());
|
return Ok(true.into());
|
||||||
@ -1432,6 +1392,8 @@ impl Engine {
|
|||||||
self.inc_operations(state, expr.position())?;
|
self.inc_operations(state, expr.position())?;
|
||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
|
Expr::Expr(x) => self.eval_expr(scope, state, lib, x.as_ref(), level),
|
||||||
|
|
||||||
Expr::IntegerConstant(x) => Ok(x.0.into()),
|
Expr::IntegerConstant(x) => Ok(x.0.into()),
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(x) => Ok(x.0.into()),
|
Expr::FloatConstant(x) => Ok(x.0.into()),
|
||||||
@ -1710,9 +1672,8 @@ impl Engine {
|
|||||||
Ok(x) if x.is_script() => {
|
Ok(x) if x.is_script() => {
|
||||||
let args = args.as_mut();
|
let args = args.as_mut();
|
||||||
let fn_def = x.get_fn_def();
|
let fn_def = x.get_fn_def();
|
||||||
let result =
|
let mut scope = Scope::new();
|
||||||
self.call_script_fn(None, state, lib, name, fn_def, args, *pos, level)?;
|
self.call_script_fn(&mut scope, state, lib, name, fn_def, args, *pos, level)
|
||||||
Ok(result)
|
|
||||||
}
|
}
|
||||||
Ok(x) => x.get_native_fn()(args.as_mut()).map_err(|err| err.new_position(*pos)),
|
Ok(x) => x.get_native_fn()(args.as_mut()).map_err(|err| err.new_position(*pos)),
|
||||||
Err(err)
|
Err(err)
|
||||||
@ -1772,9 +1733,9 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a statement
|
/// Evaluate a statement
|
||||||
pub(crate) fn eval_stmt<'s>(
|
pub(crate) fn eval_stmt(
|
||||||
&self,
|
&self,
|
||||||
scope: &mut Scope<'s>,
|
scope: &mut Scope,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &FunctionsLib,
|
lib: &FunctionsLib,
|
||||||
stmt: &Stmt,
|
stmt: &Stmt,
|
||||||
|
@ -122,7 +122,7 @@ fn call_fn_with_constant_arguments(
|
|||||||
state
|
state
|
||||||
.engine
|
.engine
|
||||||
.call_fn_raw(
|
.call_fn_raw(
|
||||||
None,
|
&mut Scope::new(),
|
||||||
&mut Default::default(),
|
&mut Default::default(),
|
||||||
state.lib,
|
state.lib,
|
||||||
fn_name,
|
fn_name,
|
||||||
@ -138,7 +138,7 @@ fn call_fn_with_constant_arguments(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Optimize a statement.
|
/// Optimize a statement.
|
||||||
fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt {
|
fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
||||||
match stmt {
|
match stmt {
|
||||||
// if expr { Noop }
|
// if expr { Noop }
|
||||||
Stmt::IfThenElse(x) if matches!(x.1, Stmt::Noop(_)) => {
|
Stmt::IfThenElse(x) if matches!(x.1, Stmt::Noop(_)) => {
|
||||||
@ -359,11 +359,13 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Optimize an expression.
|
/// Optimize an expression.
|
||||||
fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
||||||
// These keywords are handled specially
|
// These keywords are handled specially
|
||||||
const DONT_EVAL_KEYWORDS: [&str; 3] = [KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_EVAL];
|
const DONT_EVAL_KEYWORDS: [&str; 3] = [KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_EVAL];
|
||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
|
// expr - do not promote because there is a reason it is wrapped in an `Expr::Expr`
|
||||||
|
Expr::Expr(x) => Expr::Expr(Box::new(optimize_expr(*x, state))),
|
||||||
// ( stmt )
|
// ( stmt )
|
||||||
Expr::Stmt(x) => match optimize_stmt(x.0, state, true) {
|
Expr::Stmt(x) => match optimize_stmt(x.0, state, true) {
|
||||||
// ( Noop ) -> ()
|
// ( Noop ) -> ()
|
||||||
|
@ -23,6 +23,7 @@ use crate::stdlib::{
|
|||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
format,
|
format,
|
||||||
iter::{empty, Peekable},
|
iter::{empty, Peekable},
|
||||||
|
mem,
|
||||||
num::NonZeroUsize,
|
num::NonZeroUsize,
|
||||||
ops::{Add, Deref, DerefMut},
|
ops::{Add, Deref, DerefMut},
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
@ -391,6 +392,8 @@ pub enum Expr {
|
|||||||
Property(Box<((String, String, String), Position)>),
|
Property(Box<((String, String, String), Position)>),
|
||||||
/// { stmt }
|
/// { stmt }
|
||||||
Stmt(Box<(Stmt, Position)>),
|
Stmt(Box<(Stmt, Position)>),
|
||||||
|
/// Wrapped expression - should not be optimized away.
|
||||||
|
Expr(Box<Expr>),
|
||||||
/// func(expr, ... ) - ((function name, native_only, position), optional modules, hash, arguments, optional default value)
|
/// func(expr, ... ) - ((function name, native_only, position), optional modules, hash, arguments, optional default value)
|
||||||
/// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls
|
/// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls
|
||||||
/// and the function names are predictable, so no need to allocate a new `String`.
|
/// and the function names are predictable, so no need to allocate a new `String`.
|
||||||
@ -441,6 +444,8 @@ impl Expr {
|
|||||||
/// Panics when the expression is not constant.
|
/// Panics when the expression is not constant.
|
||||||
pub fn get_constant_value(&self) -> Dynamic {
|
pub fn get_constant_value(&self) -> Dynamic {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Expr(x) => x.get_constant_value(),
|
||||||
|
|
||||||
Self::IntegerConstant(x) => x.0.into(),
|
Self::IntegerConstant(x) => x.0.into(),
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Self::FloatConstant(x) => x.0.into(),
|
Self::FloatConstant(x) => x.0.into(),
|
||||||
@ -475,6 +480,8 @@ impl Expr {
|
|||||||
/// Panics when the expression is not constant.
|
/// Panics when the expression is not constant.
|
||||||
pub fn get_constant_str(&self) -> String {
|
pub fn get_constant_str(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Expr(x) => x.get_constant_str(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Self::FloatConstant(x) => x.0.to_string(),
|
Self::FloatConstant(x) => x.0.to_string(),
|
||||||
|
|
||||||
@ -494,6 +501,8 @@ impl Expr {
|
|||||||
/// Get the `Position` of the expression.
|
/// Get the `Position` of the expression.
|
||||||
pub fn position(&self) -> Position {
|
pub fn position(&self) -> Position {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Expr(x) => x.position(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Self::FloatConstant(x) => x.1,
|
Self::FloatConstant(x) => x.1,
|
||||||
|
|
||||||
@ -519,6 +528,11 @@ impl Expr {
|
|||||||
/// Override the `Position` of the expression.
|
/// Override the `Position` of the expression.
|
||||||
pub(crate) fn set_position(mut self, new_pos: Position) -> Self {
|
pub(crate) fn set_position(mut self, new_pos: Position) -> Self {
|
||||||
match &mut self {
|
match &mut self {
|
||||||
|
Self::Expr(ref mut x) => {
|
||||||
|
let expr = mem::take(x);
|
||||||
|
*x = Box::new(expr.set_position(new_pos));
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Self::FloatConstant(x) => x.1 = new_pos,
|
Self::FloatConstant(x) => x.1 = new_pos,
|
||||||
|
|
||||||
@ -550,6 +564,8 @@ impl Expr {
|
|||||||
/// A pure expression has no side effects.
|
/// A pure expression has no side effects.
|
||||||
pub fn is_pure(&self) -> bool {
|
pub fn is_pure(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Expr(x) => x.is_pure(),
|
||||||
|
|
||||||
Self::Array(x) => x.0.iter().all(Self::is_pure),
|
Self::Array(x) => x.0.iter().all(Self::is_pure),
|
||||||
|
|
||||||
Self::Index(x) | Self::And(x) | Self::Or(x) | Self::In(x) => {
|
Self::Index(x) | Self::And(x) | Self::Or(x) | Self::In(x) => {
|
||||||
@ -568,6 +584,8 @@ impl Expr {
|
|||||||
/// Is the expression a constant?
|
/// Is the expression a constant?
|
||||||
pub fn is_constant(&self) -> bool {
|
pub fn is_constant(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Expr(x) => x.is_constant(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Self::FloatConstant(_) => true,
|
Self::FloatConstant(_) => true,
|
||||||
|
|
||||||
@ -598,6 +616,8 @@ impl Expr {
|
|||||||
/// Is a particular token allowed as a postfix operator to this expression?
|
/// Is a particular token allowed as a postfix operator to this expression?
|
||||||
pub fn is_valid_postfix(&self, token: &Token) -> bool {
|
pub fn is_valid_postfix(&self, token: &Token) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Expr(x) => x.is_valid_postfix(token),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Self::FloatConstant(_) => false,
|
Self::FloatConstant(_) => false,
|
||||||
|
|
||||||
@ -996,7 +1016,7 @@ fn parse_index_chain<'a>(
|
|||||||
(Token::LeftBracket, _) => {
|
(Token::LeftBracket, _) => {
|
||||||
let idx_pos = eat_token(input, Token::LeftBracket);
|
let idx_pos = eat_token(input, Token::LeftBracket);
|
||||||
// Recursively parse the indexing chain, right-binding each
|
// Recursively parse the indexing chain, right-binding each
|
||||||
let idx = parse_index_chain(
|
let idx_expr = parse_index_chain(
|
||||||
input,
|
input,
|
||||||
state,
|
state,
|
||||||
idx_expr,
|
idx_expr,
|
||||||
@ -1005,10 +1025,20 @@ fn parse_index_chain<'a>(
|
|||||||
allow_stmt_expr,
|
allow_stmt_expr,
|
||||||
)?;
|
)?;
|
||||||
// Indexing binds to right
|
// Indexing binds to right
|
||||||
Ok(Expr::Index(Box::new((lhs, idx, pos))))
|
Ok(Expr::Index(Box::new((lhs, idx_expr, pos))))
|
||||||
}
|
}
|
||||||
// Otherwise terminate the indexing chain
|
// Otherwise terminate the indexing chain
|
||||||
_ => Ok(Expr::Index(Box::new((lhs, idx_expr, pos)))),
|
_ => {
|
||||||
|
match idx_expr {
|
||||||
|
// Terminate with an `Expr::Expr` wrapper to prevent the last index expression
|
||||||
|
// inside brackets to be mis-parsed as another level of indexing, or a
|
||||||
|
// dot expression/function call to be mis-parsed as following the indexing chain.
|
||||||
|
Expr::Index(_) | Expr::Dot(_) | Expr::FnCall(_) => Ok(Expr::Index(
|
||||||
|
Box::new((lhs, Expr::Expr(Box::new(idx_expr)), pos)),
|
||||||
|
)),
|
||||||
|
_ => Ok(Expr::Index(Box::new((lhs, idx_expr, pos)))),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(*pos)),
|
(Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(*pos)),
|
||||||
|
@ -20,21 +20,20 @@ fn test_string() -> Result<(), Box<EvalAltResult>> {
|
|||||||
assert!(engine.eval::<bool>(r#"let y = "hello, world!"; 'w' in y"#)?);
|
assert!(engine.eval::<bool>(r#"let y = "hello, world!"; 'w' in y"#)?);
|
||||||
assert!(!engine.eval::<bool>(r#"let y = "hello, world!"; "hey" in y"#)?);
|
assert!(!engine.eval::<bool>(r#"let y = "hello, world!"; "hey" in y"#)?);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_stdlib"))]
|
|
||||||
assert_eq!(engine.eval::<String>(r#""foo" + 123"#)?, "foo123");
|
assert_eq!(engine.eval::<String>(r#""foo" + 123"#)?, "foo123");
|
||||||
|
|
||||||
#[cfg(not(feature = "no_stdlib"))]
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
assert_eq!(engine.eval::<String>("(42).to_string()")?, "42");
|
assert_eq!(engine.eval::<String>("(42).to_string()")?, "42");
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
assert_eq!(engine.eval::<char>(r#"let y = "hello"; y[y.len-1]"#)?, 'o');
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
#[cfg(not(feature = "no_stdlib"))]
|
|
||||||
assert_eq!(engine.eval::<String>(r#""foo" + 123.4556"#)?, "foo123.4556");
|
assert_eq!(engine.eval::<String>(r#""foo" + 123.4556"#)?, "foo123.4556");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_stdlib"))]
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_string_substring() -> Result<(), Box<EvalAltResult>> {
|
fn test_string_substring() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
Loading…
Reference in New Issue
Block a user