Disallow overriding keywords.
This commit is contained in:
parent
fc10df7d63
commit
67d277aa21
@ -20,6 +20,7 @@ Breaking changes
|
|||||||
* `NativeCallContext::call_fn_dynamic_raw` no longer has the `pub_only` parameter.
|
* `NativeCallContext::call_fn_dynamic_raw` no longer has the `pub_only` parameter.
|
||||||
* `Module::update_fn_metadata` input parameter is changed.
|
* `Module::update_fn_metadata` input parameter is changed.
|
||||||
* Function keywords (e.g. `type_of`, `eval`, `Fn`) can no longer be overloaded. It is more trouble than worth. To disable these keywords, use `Engine::disable_symbol`.
|
* Function keywords (e.g. `type_of`, `eval`, `Fn`) can no longer be overloaded. It is more trouble than worth. To disable these keywords, use `Engine::disable_symbol`.
|
||||||
|
* `is_def_var` and `is_def_fn` are now reserved keywords.
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
------------
|
------------
|
||||||
|
@ -187,6 +187,8 @@ pub const KEYWORD_FN_PTR_CURRY: &str = "curry";
|
|||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
pub const KEYWORD_IS_SHARED: &str = "is_shared";
|
pub const KEYWORD_IS_SHARED: &str = "is_shared";
|
||||||
pub const KEYWORD_IS_DEF_VAR: &str = "is_def_var";
|
pub const KEYWORD_IS_DEF_VAR: &str = "is_def_var";
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
pub const KEYWORD_IS_DEF_FN: &str = "is_def_fn";
|
||||||
pub const KEYWORD_THIS: &str = "this";
|
pub const KEYWORD_THIS: &str = "this";
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
pub const FN_GET: &str = "get$";
|
pub const FN_GET: &str = "get$";
|
||||||
|
689
src/fn_call.rs
689
src/fn_call.rs
@ -644,119 +644,156 @@ impl Engine {
|
|||||||
ensure_no_data_race(fn_name, args, is_ref)?;
|
ensure_no_data_race(fn_name, args, is_ref)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let hash_fn =
|
// These may be redirected from method style calls.
|
||||||
calc_native_fn_hash(empty(), fn_name, args.iter().map(|a| a.type_id())).unwrap();
|
|
||||||
|
|
||||||
match fn_name {
|
match fn_name {
|
||||||
// type_of
|
// Handle type_of()
|
||||||
KEYWORD_TYPE_OF if args.len() == 1 => Ok((
|
KEYWORD_TYPE_OF if args.len() == 1 => {
|
||||||
self.map_type_name(args[0].type_name()).to_string().into(),
|
return Ok((
|
||||||
false,
|
self.map_type_name(args[0].type_name()).to_string().into(),
|
||||||
)),
|
false,
|
||||||
|
));
|
||||||
// Fn/eval - reaching this point it must be a method-style call, mostly like redirected
|
}
|
||||||
// by a function pointer so it isn't caught at parse time.
|
|
||||||
KEYWORD_FN_PTR | KEYWORD_EVAL if args.len() == 1 => EvalAltResult::ErrorRuntime(
|
|
||||||
format!(
|
|
||||||
"'{}' should not be called in method style. Try {}(...);",
|
|
||||||
fn_name, fn_name
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
pos,
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
|
|
||||||
|
// Handle is_def_fn()
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
_ if hash_script.is_some() => {
|
crate::engine::KEYWORD_IS_DEF_FN
|
||||||
let (func, source) = self
|
if args.len() == 2 && args[0].is::<FnPtr>() && args[1].is::<INT>() =>
|
||||||
.resolve_function(mods, state, lib, fn_name, hash_script.unwrap(), args, false)
|
{
|
||||||
.as_ref()
|
let fn_name = args[0].as_str().unwrap();
|
||||||
.map(|(f, s)| (Some(f.clone()), s.clone()))
|
let num_params = args[1].as_int().unwrap();
|
||||||
.unwrap_or((None, None));
|
|
||||||
|
|
||||||
if let Some(func) = func {
|
return Ok((
|
||||||
// Script function call
|
if num_params < 0 {
|
||||||
assert!(func.is_script());
|
Dynamic::FALSE
|
||||||
|
|
||||||
let func = func.get_fn_def();
|
|
||||||
|
|
||||||
let scope: &mut Scope = &mut Default::default();
|
|
||||||
|
|
||||||
// Move captured variables into scope
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
|
||||||
if let Some(captured) = _capture_scope {
|
|
||||||
if !func.externals.is_empty() {
|
|
||||||
captured
|
|
||||||
.into_iter()
|
|
||||||
.filter(|(name, _, _)| func.externals.iter().any(|ex| ex == name))
|
|
||||||
.for_each(|(name, value, _)| {
|
|
||||||
// Consume the scope values.
|
|
||||||
scope.push_dynamic(name, value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = if _is_method {
|
|
||||||
// Method call of script function - map first argument to `this`
|
|
||||||
let (first, rest) = args.split_first_mut().unwrap();
|
|
||||||
|
|
||||||
let orig_source = mem::take(&mut state.source);
|
|
||||||
state.source = source;
|
|
||||||
|
|
||||||
let level = _level + 1;
|
|
||||||
|
|
||||||
let result = self.call_script_fn(
|
|
||||||
scope,
|
|
||||||
mods,
|
|
||||||
state,
|
|
||||||
lib,
|
|
||||||
&mut Some(*first),
|
|
||||||
func,
|
|
||||||
rest,
|
|
||||||
pos,
|
|
||||||
level,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Restore the original source
|
|
||||||
state.source = orig_source;
|
|
||||||
|
|
||||||
result?
|
|
||||||
} else {
|
} else {
|
||||||
// Normal call of script function
|
let hash_script =
|
||||||
// The first argument is a reference?
|
calc_script_fn_hash(empty(), fn_name, num_params as usize);
|
||||||
let mut backup: ArgBackup = Default::default();
|
self.has_override(Some(mods), lib, None, hash_script).into()
|
||||||
backup.change_first_arg_to_copy(is_ref, args);
|
},
|
||||||
|
false,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let orig_source = mem::take(&mut state.source);
|
// Handle is_shared()
|
||||||
state.source = source;
|
#[cfg(not(feature = "no_closure"))]
|
||||||
|
crate::engine::KEYWORD_IS_SHARED if args.len() == 1 => {
|
||||||
let level = _level + 1;
|
return Err(Box::new(EvalAltResult::ErrorRuntime(
|
||||||
|
format!(
|
||||||
let result = self.call_script_fn(
|
"'{}' should not be called this way. Try {}(...);",
|
||||||
scope, mods, state, lib, &mut None, func, args, pos, level,
|
fn_name, fn_name
|
||||||
);
|
|
||||||
|
|
||||||
// Restore the original source
|
|
||||||
state.source = orig_source;
|
|
||||||
|
|
||||||
// Restore the original reference
|
|
||||||
backup.restore_first_arg(args);
|
|
||||||
|
|
||||||
result?
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((result, false))
|
|
||||||
} else {
|
|
||||||
// Native function call
|
|
||||||
self.call_native_fn(
|
|
||||||
mods, state, lib, fn_name, hash_fn, args, is_ref, false, pos,
|
|
||||||
)
|
)
|
||||||
|
.into(),
|
||||||
|
pos,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR if args.len() == 1 => {
|
||||||
|
return Err(Box::new(EvalAltResult::ErrorRuntime(
|
||||||
|
format!(
|
||||||
|
"'{}' should not be called this way. Try {}(...);",
|
||||||
|
fn_name, fn_name
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
pos,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY if !args.is_empty() => {
|
||||||
|
return Err(Box::new(EvalAltResult::ErrorRuntime(
|
||||||
|
format!(
|
||||||
|
"'{}' should not be called this way. Try {}(...);",
|
||||||
|
fn_name, fn_name
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
pos,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
if let Some((func, source)) = hash_script.and_then(|hash| {
|
||||||
|
self.resolve_function(mods, state, lib, fn_name, hash, args, false)
|
||||||
|
.as_ref()
|
||||||
|
.map(|(f, s)| (f.clone(), s.clone()))
|
||||||
|
}) {
|
||||||
|
// Script function call
|
||||||
|
assert!(func.is_script());
|
||||||
|
|
||||||
|
let func = func.get_fn_def();
|
||||||
|
|
||||||
|
let scope: &mut Scope = &mut Default::default();
|
||||||
|
|
||||||
|
// Move captured variables into scope
|
||||||
|
#[cfg(not(feature = "no_closure"))]
|
||||||
|
if let Some(captured) = _capture_scope {
|
||||||
|
if !func.externals.is_empty() {
|
||||||
|
captured
|
||||||
|
.into_iter()
|
||||||
|
.filter(|(name, _, _)| func.externals.iter().any(|ex| ex == name))
|
||||||
|
.for_each(|(name, value, _)| {
|
||||||
|
// Consume the scope values.
|
||||||
|
scope.push_dynamic(name, value);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Native function call
|
let result = if _is_method {
|
||||||
_ => self.call_native_fn(mods, state, lib, fn_name, hash_fn, args, is_ref, false, pos),
|
// Method call of script function - map first argument to `this`
|
||||||
|
let (first, rest) = args.split_first_mut().unwrap();
|
||||||
|
|
||||||
|
let orig_source = mem::take(&mut state.source);
|
||||||
|
state.source = source;
|
||||||
|
|
||||||
|
let level = _level + 1;
|
||||||
|
|
||||||
|
let result = self.call_script_fn(
|
||||||
|
scope,
|
||||||
|
mods,
|
||||||
|
state,
|
||||||
|
lib,
|
||||||
|
&mut Some(*first),
|
||||||
|
func,
|
||||||
|
rest,
|
||||||
|
pos,
|
||||||
|
level,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Restore the original source
|
||||||
|
state.source = orig_source;
|
||||||
|
|
||||||
|
result?
|
||||||
|
} else {
|
||||||
|
// Normal call of script function
|
||||||
|
// The first argument is a reference?
|
||||||
|
let mut backup: ArgBackup = Default::default();
|
||||||
|
backup.change_first_arg_to_copy(is_ref, args);
|
||||||
|
|
||||||
|
let orig_source = mem::take(&mut state.source);
|
||||||
|
state.source = source;
|
||||||
|
|
||||||
|
let level = _level + 1;
|
||||||
|
|
||||||
|
let result =
|
||||||
|
self.call_script_fn(scope, mods, state, lib, &mut None, func, args, pos, level);
|
||||||
|
|
||||||
|
// Restore the original source
|
||||||
|
state.source = orig_source;
|
||||||
|
|
||||||
|
// Restore the original reference
|
||||||
|
backup.restore_first_arg(args);
|
||||||
|
|
||||||
|
result?
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok((result, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Native function call
|
||||||
|
let hash_fn =
|
||||||
|
calc_native_fn_hash(empty(), fn_name, args.iter().map(|a| a.type_id())).unwrap();
|
||||||
|
self.call_native_fn(mods, state, lib, fn_name, hash_fn, args, is_ref, false, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a list of statements with no `this` pointer.
|
/// Evaluate a list of statements with no `this` pointer.
|
||||||
@ -845,114 +882,116 @@ impl Engine {
|
|||||||
let obj = target.as_mut();
|
let obj = target.as_mut();
|
||||||
let mut fn_name = fn_name;
|
let mut fn_name = fn_name;
|
||||||
|
|
||||||
let (result, updated) = if fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() {
|
let (result, updated) = match fn_name {
|
||||||
// FnPtr call
|
KEYWORD_FN_PTR_CALL if obj.is::<FnPtr>() => {
|
||||||
let fn_ptr = obj.read_lock::<FnPtr>().unwrap();
|
// FnPtr call
|
||||||
// Redirect function name
|
let fn_ptr = obj.read_lock::<FnPtr>().unwrap();
|
||||||
let fn_name = fn_ptr.fn_name();
|
// Redirect function name
|
||||||
let args_len = call_args.len() + fn_ptr.curry().len();
|
let fn_name = fn_ptr.fn_name();
|
||||||
// Recalculate hash
|
let args_len = call_args.len() + fn_ptr.curry().len();
|
||||||
let hash = hash_script.and_then(|_| calc_script_fn_hash(empty(), fn_name, args_len));
|
// Recalculate hash
|
||||||
// Arguments are passed as-is, adding the curried arguments
|
let hash =
|
||||||
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
|
hash_script.and_then(|_| calc_script_fn_hash(empty(), fn_name, args_len));
|
||||||
let mut arg_values = curry
|
// Arguments are passed as-is, adding the curried arguments
|
||||||
.iter_mut()
|
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
|
||||||
.chain(call_args.iter_mut())
|
let mut arg_values = curry
|
||||||
.collect::<StaticVec<_>>();
|
.iter_mut()
|
||||||
let args = arg_values.as_mut();
|
.chain(call_args.iter_mut())
|
||||||
|
.collect::<StaticVec<_>>();
|
||||||
|
let args = arg_values.as_mut();
|
||||||
|
|
||||||
// Map it to name(args) in function-call style
|
// Map it to name(args) in function-call style
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
mods, state, lib, fn_name, hash, args, false, false, pos, None, level,
|
mods, state, lib, fn_name, hash, args, false, false, pos, None, level,
|
||||||
)
|
|
||||||
} else if fn_name == KEYWORD_FN_PTR_CALL
|
|
||||||
&& call_args.len() > 0
|
|
||||||
&& call_args[0].is::<FnPtr>()
|
|
||||||
{
|
|
||||||
// FnPtr call on object
|
|
||||||
let fn_ptr = call_args.remove(0).cast::<FnPtr>();
|
|
||||||
// Redirect function name
|
|
||||||
let fn_name = fn_ptr.fn_name();
|
|
||||||
let args_len = call_args.len() + fn_ptr.curry().len();
|
|
||||||
// Recalculate hash
|
|
||||||
let hash = hash_script.and_then(|_| calc_script_fn_hash(empty(), fn_name, args_len));
|
|
||||||
// Replace the first argument with the object pointer, adding the curried arguments
|
|
||||||
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
|
|
||||||
let mut arg_values = once(obj)
|
|
||||||
.chain(curry.iter_mut())
|
|
||||||
.chain(call_args.iter_mut())
|
|
||||||
.collect::<StaticVec<_>>();
|
|
||||||
let args = arg_values.as_mut();
|
|
||||||
|
|
||||||
// Map it to name(args) in function-call style
|
|
||||||
self.exec_fn_call(
|
|
||||||
mods, state, lib, fn_name, hash, args, is_ref, true, pos, None, level,
|
|
||||||
)
|
|
||||||
} else if fn_name == KEYWORD_FN_PTR_CURRY && obj.is::<FnPtr>() {
|
|
||||||
// Curry call
|
|
||||||
let fn_ptr = obj.read_lock::<FnPtr>().unwrap();
|
|
||||||
Ok((
|
|
||||||
FnPtr::new_unchecked(
|
|
||||||
fn_ptr.get_fn_name().clone(),
|
|
||||||
fn_ptr
|
|
||||||
.curry()
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.chain(call_args.into_iter())
|
|
||||||
.collect(),
|
|
||||||
)
|
)
|
||||||
.into(),
|
|
||||||
false,
|
|
||||||
))
|
|
||||||
} else if {
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
|
||||||
{
|
|
||||||
fn_name == crate::engine::KEYWORD_IS_SHARED && call_args.is_empty()
|
|
||||||
}
|
}
|
||||||
#[cfg(feature = "no_closure")]
|
KEYWORD_FN_PTR_CALL if call_args.len() > 0 && call_args[0].is::<FnPtr>() => {
|
||||||
false
|
// FnPtr call on object
|
||||||
} {
|
let fn_ptr = call_args.remove(0).cast::<FnPtr>();
|
||||||
// is_shared call
|
// Redirect function name
|
||||||
Ok((target.is_shared().into(), false))
|
let fn_name = fn_ptr.fn_name();
|
||||||
} else {
|
let args_len = call_args.len() + fn_ptr.curry().len();
|
||||||
let _redirected;
|
// Recalculate hash
|
||||||
let mut hash = hash_script;
|
let hash =
|
||||||
|
hash_script.and_then(|_| calc_script_fn_hash(empty(), fn_name, args_len));
|
||||||
|
// Replace the first argument with the object pointer, adding the curried arguments
|
||||||
|
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
|
||||||
|
let mut arg_values = once(obj)
|
||||||
|
.chain(curry.iter_mut())
|
||||||
|
.chain(call_args.iter_mut())
|
||||||
|
.collect::<StaticVec<_>>();
|
||||||
|
let args = arg_values.as_mut();
|
||||||
|
|
||||||
// Check if it is a map method call in OOP style
|
// Map it to name(args) in function-call style
|
||||||
#[cfg(not(feature = "no_object"))]
|
self.exec_fn_call(
|
||||||
if let Some(map) = obj.read_lock::<Map>() {
|
mods, state, lib, fn_name, hash, args, is_ref, true, pos, None, level,
|
||||||
if let Some(val) = map.get(fn_name) {
|
)
|
||||||
if let Some(fn_ptr) = val.read_lock::<FnPtr>() {
|
}
|
||||||
// Remap the function name
|
KEYWORD_FN_PTR_CURRY if obj.is::<FnPtr>() => {
|
||||||
_redirected = fn_ptr.get_fn_name().clone();
|
// Curry call
|
||||||
fn_name = &_redirected;
|
let fn_ptr = obj.read_lock::<FnPtr>().unwrap();
|
||||||
// Add curried arguments
|
Ok((
|
||||||
|
FnPtr::new_unchecked(
|
||||||
|
fn_ptr.get_fn_name().clone(),
|
||||||
fn_ptr
|
fn_ptr
|
||||||
.curry()
|
.curry()
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.enumerate()
|
.chain(call_args.into_iter())
|
||||||
.for_each(|(i, v)| call_args.insert(i, v));
|
.collect(),
|
||||||
// Recalculate the hash based on the new function name and new arguments
|
)
|
||||||
hash = hash_script
|
.into(),
|
||||||
.and_then(|_| calc_script_fn_hash(empty(), fn_name, call_args.len()));
|
false,
|
||||||
}
|
))
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if hash_script.is_none() {
|
|
||||||
hash = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attached object pointer in front of the arguments
|
// Handle is_shared()
|
||||||
let mut arg_values = once(obj)
|
#[cfg(not(feature = "no_closure"))]
|
||||||
.chain(call_args.iter_mut())
|
crate::engine::KEYWORD_IS_SHARED if call_args.is_empty() => {
|
||||||
.collect::<StaticVec<_>>();
|
return Ok((target.is_shared().into(), false));
|
||||||
let args = arg_values.as_mut();
|
}
|
||||||
|
|
||||||
self.exec_fn_call(
|
_ => {
|
||||||
mods, state, lib, fn_name, hash, args, is_ref, true, pos, None, level,
|
let _redirected;
|
||||||
)
|
let mut hash = hash_script;
|
||||||
|
|
||||||
|
// Check if it is a map method call in OOP style
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
if let Some(map) = obj.read_lock::<Map>() {
|
||||||
|
if let Some(val) = map.get(fn_name) {
|
||||||
|
if let Some(fn_ptr) = val.read_lock::<FnPtr>() {
|
||||||
|
// Remap the function name
|
||||||
|
_redirected = fn_ptr.get_fn_name().clone();
|
||||||
|
fn_name = &_redirected;
|
||||||
|
// Add curried arguments
|
||||||
|
fn_ptr
|
||||||
|
.curry()
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.enumerate()
|
||||||
|
.for_each(|(i, v)| call_args.insert(i, v));
|
||||||
|
// Recalculate the hash based on the new function name and new arguments
|
||||||
|
hash = hash_script.and_then(|_| {
|
||||||
|
calc_script_fn_hash(empty(), fn_name, call_args.len())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if hash_script.is_none() {
|
||||||
|
hash = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attached object pointer in front of the arguments
|
||||||
|
let mut arg_values = once(obj)
|
||||||
|
.chain(call_args.iter_mut())
|
||||||
|
.collect::<StaticVec<_>>();
|
||||||
|
let args = arg_values.as_mut();
|
||||||
|
|
||||||
|
self.exec_fn_call(
|
||||||
|
mods, state, lib, fn_name, hash, args, is_ref, true, pos, None, level,
|
||||||
|
)
|
||||||
|
}
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
// Propagate the changed value back to the source if necessary
|
// Propagate the changed value back to the source if necessary
|
||||||
@ -986,128 +1025,158 @@ impl Engine {
|
|||||||
let mut curry = StaticVec::new();
|
let mut curry = StaticVec::new();
|
||||||
let mut name = fn_name;
|
let mut name = fn_name;
|
||||||
|
|
||||||
if name == KEYWORD_FN_PTR_CALL && args_expr.len() >= 1 {
|
match name {
|
||||||
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
|
// Handle call()
|
||||||
|
KEYWORD_FN_PTR_CALL if args_expr.len() >= 1 => {
|
||||||
|
let fn_ptr =
|
||||||
|
self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
|
||||||
|
|
||||||
if !fn_ptr.is::<FnPtr>() {
|
if !fn_ptr.is::<FnPtr>() {
|
||||||
return Err(self.make_type_mismatch_err::<FnPtr>(
|
return Err(self.make_type_mismatch_err::<FnPtr>(
|
||||||
self.map_type_name(fn_ptr.type_name()),
|
self.map_type_name(fn_ptr.type_name()),
|
||||||
args_expr[0].position(),
|
args_expr[0].position(),
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let fn_ptr = fn_ptr.cast::<FnPtr>();
|
||||||
|
curry.extend(fn_ptr.curry().iter().cloned());
|
||||||
|
|
||||||
|
// Redirect function name
|
||||||
|
redirected = fn_ptr.take_data().0;
|
||||||
|
name = &redirected;
|
||||||
|
|
||||||
|
// Skip the first argument
|
||||||
|
args_expr = &args_expr.as_ref()[1..];
|
||||||
|
|
||||||
|
// Recalculate hash
|
||||||
|
let args_len = args_expr.len() + curry.len();
|
||||||
|
hash_script = calc_script_fn_hash(empty(), name, args_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
let fn_ptr = fn_ptr.cast::<FnPtr>();
|
// Handle Fn()
|
||||||
curry.extend(fn_ptr.curry().iter().cloned());
|
KEYWORD_FN_PTR if args_expr.len() == 1 => {
|
||||||
|
// Fn - only in function call style
|
||||||
// Redirect function name
|
return self
|
||||||
redirected = fn_ptr.take_data().0;
|
.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?
|
||||||
name = &redirected;
|
.take_immutable_string()
|
||||||
|
.map_err(|typ| {
|
||||||
// Skip the first argument
|
self.make_type_mismatch_err::<ImmutableString>(typ, args_expr[0].position())
|
||||||
args_expr = &args_expr.as_ref()[1..];
|
})
|
||||||
|
.and_then(|s| FnPtr::try_from(s))
|
||||||
// Recalculate hash
|
.map(Into::<Dynamic>::into)
|
||||||
let args_len = args_expr.len() + curry.len();
|
.map_err(|err| err.fill_position(args_expr[0].position()));
|
||||||
hash_script = calc_script_fn_hash(empty(), name, args_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle Fn()
|
|
||||||
if name == KEYWORD_FN_PTR && args_expr.len() == 1 {
|
|
||||||
// Fn - only in function call style
|
|
||||||
return self
|
|
||||||
.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?
|
|
||||||
.take_immutable_string()
|
|
||||||
.map_err(|typ| {
|
|
||||||
self.make_type_mismatch_err::<ImmutableString>(typ, args_expr[0].position())
|
|
||||||
})
|
|
||||||
.and_then(|s| FnPtr::try_from(s))
|
|
||||||
.map(Into::<Dynamic>::into)
|
|
||||||
.map_err(|err| err.fill_position(args_expr[0].position()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle curry()
|
|
||||||
if name == KEYWORD_FN_PTR_CURRY && args_expr.len() > 1 {
|
|
||||||
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
|
|
||||||
|
|
||||||
if !fn_ptr.is::<FnPtr>() {
|
|
||||||
return Err(self.make_type_mismatch_err::<FnPtr>(
|
|
||||||
self.map_type_name(fn_ptr.type_name()),
|
|
||||||
args_expr[0].position(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let (name, mut fn_curry) = fn_ptr.cast::<FnPtr>().take_data();
|
// Handle curry()
|
||||||
|
KEYWORD_FN_PTR_CURRY if args_expr.len() > 1 => {
|
||||||
|
let fn_ptr =
|
||||||
|
self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
|
||||||
|
|
||||||
// Append the new curried arguments to the existing list.
|
if !fn_ptr.is::<FnPtr>() {
|
||||||
|
return Err(self.make_type_mismatch_err::<FnPtr>(
|
||||||
|
self.map_type_name(fn_ptr.type_name()),
|
||||||
|
args_expr[0].position(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
args_expr
|
let (name, mut fn_curry) = fn_ptr.cast::<FnPtr>().take_data();
|
||||||
.iter()
|
|
||||||
.skip(1)
|
// Append the new curried arguments to the existing list.
|
||||||
.try_for_each(|expr| -> Result<(), Box<EvalAltResult>> {
|
|
||||||
fn_curry.push(self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?);
|
args_expr.iter().skip(1).try_for_each(
|
||||||
Ok(())
|
|expr| -> Result<(), Box<EvalAltResult>> {
|
||||||
|
fn_curry
|
||||||
|
.push(self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?);
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
return Ok(FnPtr::new_unchecked(name, fn_curry).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle is_shared()
|
||||||
|
#[cfg(not(feature = "no_closure"))]
|
||||||
|
crate::engine::KEYWORD_IS_SHARED if args_expr.len() == 1 => {
|
||||||
|
let value =
|
||||||
|
self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
|
||||||
|
return Ok(value.is_shared().into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle is_def_fn()
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
crate::engine::KEYWORD_IS_DEF_FN if args_expr.len() == 2 => {
|
||||||
|
let fn_name =
|
||||||
|
self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
|
||||||
|
let fn_name = fn_name.as_str().map_err(|err| {
|
||||||
|
self.make_type_mismatch_err::<ImmutableString>(err, args_expr[0].position())
|
||||||
|
})?;
|
||||||
|
let num_params =
|
||||||
|
self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[1], level)?;
|
||||||
|
let num_params = num_params.as_int().map_err(|err| {
|
||||||
|
self.make_type_mismatch_err::<INT>(err, args_expr[0].position())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
return Ok(FnPtr::new_unchecked(name, fn_curry).into());
|
if num_params < 0 {
|
||||||
}
|
return Ok(Dynamic::FALSE);
|
||||||
|
} else {
|
||||||
// Handle is_shared()
|
let hash_script = calc_script_fn_hash(empty(), fn_name, num_params as usize);
|
||||||
#[cfg(not(feature = "no_closure"))]
|
return Ok(self.has_override(Some(mods), lib, None, hash_script).into());
|
||||||
if name == crate::engine::KEYWORD_IS_SHARED && args_expr.len() == 1 {
|
}
|
||||||
let value = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
|
|
||||||
|
|
||||||
return Ok(value.is_shared().into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle is_def_var()
|
|
||||||
if name == KEYWORD_IS_DEF_VAR && args_expr.len() == 1 {
|
|
||||||
let var_name =
|
|
||||||
self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
|
|
||||||
let var_name = var_name.as_str().map_err(|err| {
|
|
||||||
self.make_type_mismatch_err::<ImmutableString>(err, args_expr[0].position())
|
|
||||||
})?;
|
|
||||||
return Ok(scope.contains(var_name).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle eval()
|
|
||||||
if name == KEYWORD_EVAL && args_expr.len() == 1 {
|
|
||||||
let script_expr = &args_expr[0];
|
|
||||||
let script_pos = script_expr.position();
|
|
||||||
|
|
||||||
// eval - only in function call style
|
|
||||||
let prev_len = scope.len();
|
|
||||||
let script = self.eval_expr(scope, mods, state, lib, this_ptr, script_expr, level)?;
|
|
||||||
let script = script
|
|
||||||
.as_str()
|
|
||||||
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, script_pos))?;
|
|
||||||
let result = self.eval_script_expr_in_place(
|
|
||||||
scope,
|
|
||||||
mods,
|
|
||||||
state,
|
|
||||||
lib,
|
|
||||||
script,
|
|
||||||
script_pos,
|
|
||||||
level + 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
// IMPORTANT! If the eval defines new variables in the current scope,
|
|
||||||
// all variable offsets from this point on will be mis-aligned.
|
|
||||||
if scope.len() != prev_len {
|
|
||||||
state.always_search = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.map_err(|err| {
|
// Handle is_def_var()
|
||||||
Box::new(EvalAltResult::ErrorInFunctionCall(
|
KEYWORD_IS_DEF_VAR if args_expr.len() == 1 => {
|
||||||
KEYWORD_EVAL.to_string(),
|
let var_name =
|
||||||
state
|
self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
|
||||||
.source
|
let var_name = var_name.as_str().map_err(|err| {
|
||||||
.as_ref()
|
self.make_type_mismatch_err::<ImmutableString>(err, args_expr[0].position())
|
||||||
.map_or_else(|| "", |s| s.as_str())
|
})?;
|
||||||
.to_string(),
|
return Ok(scope.contains(var_name).into());
|
||||||
err,
|
}
|
||||||
pos,
|
|
||||||
))
|
// Handle eval()
|
||||||
});
|
KEYWORD_EVAL if args_expr.len() == 1 => {
|
||||||
|
let script_expr = &args_expr[0];
|
||||||
|
let script_pos = script_expr.position();
|
||||||
|
|
||||||
|
// eval - only in function call style
|
||||||
|
let prev_len = scope.len();
|
||||||
|
let script =
|
||||||
|
self.eval_expr(scope, mods, state, lib, this_ptr, script_expr, level)?;
|
||||||
|
let script = script.as_str().map_err(|typ| {
|
||||||
|
self.make_type_mismatch_err::<ImmutableString>(typ, script_pos)
|
||||||
|
})?;
|
||||||
|
let result = self.eval_script_expr_in_place(
|
||||||
|
scope,
|
||||||
|
mods,
|
||||||
|
state,
|
||||||
|
lib,
|
||||||
|
script,
|
||||||
|
script_pos,
|
||||||
|
level + 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
// IMPORTANT! If the eval defines new variables in the current scope,
|
||||||
|
// all variable offsets from this point on will be mis-aligned.
|
||||||
|
if scope.len() != prev_len {
|
||||||
|
state.always_search = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.map_err(|err| {
|
||||||
|
Box::new(EvalAltResult::ErrorInFunctionCall(
|
||||||
|
KEYWORD_EVAL.to_string(),
|
||||||
|
state
|
||||||
|
.source
|
||||||
|
.as_ref()
|
||||||
|
.map_or_else(|| "", |s| s.as_str())
|
||||||
|
.to_string(),
|
||||||
|
err,
|
||||||
|
pos,
|
||||||
|
))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal function call - except for Fn, curry, call and eval (handled above)
|
// Normal function call - except for Fn, curry, call and eval (handled above)
|
||||||
|
18
src/token.rs
18
src/token.rs
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use crate::engine::{
|
use crate::engine::{
|
||||||
KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY,
|
KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY,
|
||||||
KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
|
KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
|
||||||
};
|
};
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
@ -21,6 +21,9 @@ use crate::ast::FloatWrapper;
|
|||||||
#[cfg(feature = "decimal")]
|
#[cfg(feature = "decimal")]
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
use crate::engine::KEYWORD_IS_DEF_FN;
|
||||||
|
|
||||||
type LERR = LexError;
|
type LERR = LexError;
|
||||||
|
|
||||||
/// Separator character for numbers.
|
/// Separator character for numbers.
|
||||||
@ -579,7 +582,12 @@ impl Token {
|
|||||||
| "async" | "await" | "yield" => Reserved(syntax.into()),
|
| "async" | "await" | "yield" => Reserved(syntax.into()),
|
||||||
|
|
||||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS => Reserved(syntax.into()),
|
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS | KEYWORD_IS_DEF_VAR => {
|
||||||
|
Reserved(syntax.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
KEYWORD_IS_DEF_FN => Reserved(syntax.into()),
|
||||||
|
|
||||||
_ => return None,
|
_ => return None,
|
||||||
})
|
})
|
||||||
@ -1624,7 +1632,11 @@ fn get_identifier(
|
|||||||
pub fn is_keyword_function(name: &str) -> bool {
|
pub fn is_keyword_function(name: &str) -> bool {
|
||||||
match name {
|
match name {
|
||||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY => true,
|
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR => true,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
KEYWORD_IS_DEF_FN => true,
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,6 +102,14 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
|
|||||||
"#
|
"#
|
||||||
)?);
|
)?);
|
||||||
|
|
||||||
|
assert!(engine.eval::<bool>(
|
||||||
|
r#"
|
||||||
|
let a = 41;
|
||||||
|
let foo = |x| { a += x };
|
||||||
|
is_shared(a)
|
||||||
|
"#
|
||||||
|
)?);
|
||||||
|
|
||||||
engine.register_fn("plus_one", |x: INT| x + 1);
|
engine.register_fn("plus_one", |x: INT| x + 1);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -37,32 +37,6 @@ fn test_constant_scope() -> Result<(), Box<EvalAltResult>> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_var_is_def() -> Result<(), Box<EvalAltResult>> {
|
|
||||||
let engine = Engine::new();
|
|
||||||
|
|
||||||
assert!(engine.eval::<bool>(
|
|
||||||
r#"
|
|
||||||
let x = 42;
|
|
||||||
is_def_var("x")
|
|
||||||
"#
|
|
||||||
)?);
|
|
||||||
assert!(!engine.eval::<bool>(
|
|
||||||
r#"
|
|
||||||
let x = 42;
|
|
||||||
is_def_var("y")
|
|
||||||
"#
|
|
||||||
)?);
|
|
||||||
assert!(engine.eval::<bool>(
|
|
||||||
r#"
|
|
||||||
const x = 42;
|
|
||||||
is_def_var("x")
|
|
||||||
"#
|
|
||||||
)?);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_constant_mut() -> Result<(), Box<EvalAltResult>> {
|
fn test_constant_mut() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
@ -46,6 +46,9 @@ fn test_type_of() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert_eq!(engine.eval::<String>(r#"type_of("hello")"#)?, "string");
|
assert_eq!(engine.eval::<String>(r#"type_of("hello")"#)?, "string");
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
assert_eq!(engine.eval::<String>(r#""hello".type_of()"#)?, "string");
|
||||||
|
|
||||||
#[cfg(not(feature = "only_i32"))]
|
#[cfg(not(feature = "only_i32"))]
|
||||||
assert_eq!(engine.eval::<String>("let x = 123; type_of(x)")?, "i64");
|
assert_eq!(engine.eval::<String>("let x = 123; type_of(x)")?, "i64");
|
||||||
|
|
||||||
|
@ -19,6 +19,32 @@ fn test_var_scope() -> Result<(), Box<EvalAltResult>> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_var_is_def() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let engine = Engine::new();
|
||||||
|
|
||||||
|
assert!(engine.eval::<bool>(
|
||||||
|
r#"
|
||||||
|
let x = 42;
|
||||||
|
is_def_var("x")
|
||||||
|
"#
|
||||||
|
)?);
|
||||||
|
assert!(!engine.eval::<bool>(
|
||||||
|
r#"
|
||||||
|
let x = 42;
|
||||||
|
is_def_var("y")
|
||||||
|
"#
|
||||||
|
)?);
|
||||||
|
assert!(engine.eval::<bool>(
|
||||||
|
r#"
|
||||||
|
const x = 42;
|
||||||
|
is_def_var("x")
|
||||||
|
"#
|
||||||
|
)?);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_scope_eval() -> Result<(), Box<EvalAltResult>> {
|
fn test_scope_eval() -> Result<(), Box<EvalAltResult>> {
|
||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
Loading…
Reference in New Issue
Block a user