Disallow overriding keywords.

This commit is contained in:
Stephen Chung 2021-03-01 22:44:56 +08:00
parent fc10df7d63
commit 67d277aa21
8 changed files with 434 additions and 339 deletions

View File

@ -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
------------ ------------

View File

@ -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$";

View File

@ -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)

View File

@ -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,
} }
} }

View File

@ -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!(

View File

@ -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>> {

View File

@ -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");

View File

@ -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();