Refine native/script code splits.
This commit is contained in:
parent
d97f3f7ec4
commit
c9184db4d2
@ -273,7 +273,7 @@ impl Engine {
|
|||||||
.into_err(Position::NONE));
|
.into_err(Position::NONE));
|
||||||
}
|
}
|
||||||
// Identifier in first position
|
// Identifier in first position
|
||||||
_ if segments.is_empty() && is_valid_identifier(s.chars()) => {
|
_ if segments.is_empty() && is_valid_identifier(s) => {
|
||||||
// Make it a custom keyword/symbol if it is disabled or reserved
|
// Make it a custom keyword/symbol if it is disabled or reserved
|
||||||
if (!self.disabled_symbols.is_empty() && self.disabled_symbols.contains(s))
|
if (!self.disabled_symbols.is_empty() && self.disabled_symbols.contains(s))
|
||||||
|| token.map_or(false, |v| v.is_reserved())
|
|| token.map_or(false, |v| v.is_reserved())
|
||||||
|
@ -111,9 +111,11 @@ impl CustomExpr {
|
|||||||
pub struct FnCallHashes {
|
pub struct FnCallHashes {
|
||||||
/// Pre-calculated hash for a script-defined function (zero if native functions only).
|
/// Pre-calculated hash for a script-defined function (zero if native functions only).
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub script: u64,
|
script: u64,
|
||||||
/// Pre-calculated hash for a native Rust function with no parameter types.
|
/// Pre-calculated hash for a native Rust function with no parameter types.
|
||||||
pub native: u64,
|
///
|
||||||
|
/// This hash can never be zero.
|
||||||
|
native: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for FnCallHashes {
|
impl fmt::Debug for FnCallHashes {
|
||||||
@ -148,7 +150,7 @@ impl From<u64> for FnCallHashes {
|
|||||||
|
|
||||||
impl FnCallHashes {
|
impl FnCallHashes {
|
||||||
/// Create a [`FnCallHashes`] with only the native Rust hash.
|
/// Create a [`FnCallHashes`] with only the native Rust hash.
|
||||||
#[inline]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn from_native(hash: u64) -> Self {
|
pub const fn from_native(hash: u64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -158,7 +160,7 @@ impl FnCallHashes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Create a [`FnCallHashes`] with both native Rust and script function hashes.
|
/// Create a [`FnCallHashes`] with both native Rust and script function hashes.
|
||||||
#[inline]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn from_all(#[cfg(not(feature = "no_function"))] script: u64, native: u64) -> Self {
|
pub const fn from_all(#[cfg(not(feature = "no_function"))] script: u64, native: u64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -167,16 +169,29 @@ impl FnCallHashes {
|
|||||||
native: if native == 0 { ALT_ZERO_HASH } else { native },
|
native: if native == 0 { ALT_ZERO_HASH } else { native },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Is this [`FnCallHashes`] native Rust only?
|
/// Is this [`FnCallHashes`] native-only?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn is_native_only(&self) -> bool {
|
pub const fn is_native_only(&self) -> bool {
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
return self.script == 0;
|
return self.script == 0;
|
||||||
|
|
||||||
#[cfg(feature = "no_function")]
|
#[cfg(feature = "no_function")]
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
/// Get the native hash.
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn native(&self) -> u64 {
|
||||||
|
self.native
|
||||||
|
}
|
||||||
|
/// Get the script hash.
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn script(&self) -> u64 {
|
||||||
|
assert!(self.script != 0);
|
||||||
|
self.script
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// _(internals)_ A function call.
|
/// _(internals)_ A function call.
|
||||||
@ -196,9 +211,6 @@ pub struct FnCallExpr {
|
|||||||
pub capture_parent_scope: bool,
|
pub capture_parent_scope: bool,
|
||||||
/// Is this function call a native operator?
|
/// Is this function call a native operator?
|
||||||
pub operator_token: Option<Token>,
|
pub operator_token: Option<Token>,
|
||||||
/// Can this function call be a scripted function?
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
pub can_be_script: bool,
|
|
||||||
/// [Position] of the function name.
|
/// [Position] of the function name.
|
||||||
pub pos: Position,
|
pub pos: Position,
|
||||||
}
|
}
|
||||||
@ -221,10 +233,6 @@ impl fmt::Debug for FnCallExpr {
|
|||||||
if self.capture_parent_scope {
|
if self.capture_parent_scope {
|
||||||
ff.field("capture_parent_scope", &self.capture_parent_scope);
|
ff.field("capture_parent_scope", &self.capture_parent_scope);
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
if self.can_be_script {
|
|
||||||
ff.field("can_be_script", &self.can_be_script);
|
|
||||||
}
|
|
||||||
ff.field("pos", &self.pos);
|
ff.field("pos", &self.pos);
|
||||||
ff.finish()
|
ff.finish()
|
||||||
}
|
}
|
||||||
@ -691,8 +699,6 @@ impl Expr {
|
|||||||
args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(),
|
args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(),
|
||||||
capture_parent_scope: false,
|
capture_parent_scope: false,
|
||||||
operator_token: None,
|
operator_token: None,
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
can_be_script: true,
|
|
||||||
pos,
|
pos,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
|
@ -223,16 +223,9 @@ impl Engine {
|
|||||||
hashes,
|
hashes,
|
||||||
args,
|
args,
|
||||||
operator_token,
|
operator_token,
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
can_be_script,
|
|
||||||
..
|
..
|
||||||
} = expr;
|
} = expr;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
let native = !can_be_script;
|
|
||||||
#[cfg(feature = "no_function")]
|
|
||||||
let native = true;
|
|
||||||
|
|
||||||
// Short-circuit native binary operator call if under Fast Operators mode
|
// Short-circuit native binary operator call if under Fast Operators mode
|
||||||
if operator_token.is_some() && self.fast_operators() && args.len() == 2 {
|
if operator_token.is_some() && self.fast_operators() && args.len() == 2 {
|
||||||
let mut lhs = self
|
let mut lhs = self
|
||||||
@ -257,8 +250,7 @@ impl Engine {
|
|||||||
|
|
||||||
return self
|
return self
|
||||||
.exec_fn_call(
|
.exec_fn_call(
|
||||||
None, global, caches, lib, name, native, *hashes, operands, false, false, pos,
|
None, global, caches, lib, name, *hashes, operands, false, false, pos, level,
|
||||||
level,
|
|
||||||
)
|
)
|
||||||
.map(|(v, ..)| v);
|
.map(|(v, ..)| v);
|
||||||
}
|
}
|
||||||
@ -266,7 +258,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
if !expr.namespace.is_empty() {
|
if !expr.namespace.is_empty() {
|
||||||
// Qualified function call
|
// Qualified function call
|
||||||
let hash = hashes.native;
|
let hash = hashes.native();
|
||||||
let namespace = &expr.namespace;
|
let namespace = &expr.namespace;
|
||||||
|
|
||||||
return self.make_qualified_function_call(
|
return self.make_qualified_function_call(
|
||||||
@ -287,7 +279,6 @@ impl Engine {
|
|||||||
lib,
|
lib,
|
||||||
this_ptr,
|
this_ptr,
|
||||||
name,
|
name,
|
||||||
native,
|
|
||||||
first_arg,
|
first_arg,
|
||||||
args,
|
args,
|
||||||
*hashes,
|
*hashes,
|
||||||
|
178
src/func/call.rs
178
src/func/call.rs
@ -11,9 +11,9 @@ use crate::engine::{
|
|||||||
use crate::eval::{Caches, FnResolutionCacheEntry, GlobalRuntimeState};
|
use crate::eval::{Caches, FnResolutionCacheEntry, GlobalRuntimeState};
|
||||||
use crate::tokenizer::Token;
|
use crate::tokenizer::Token;
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnArgsVec, FnPtr,
|
calc_fn_hash, calc_fn_params_hash, combine_hashes, is_valid_function_name, Dynamic, Engine,
|
||||||
ImmutableString, Module, OptimizationLevel, Position, RhaiError, RhaiResult, RhaiResultOf,
|
FnArgsVec, FnPtr, ImmutableString, Module, OptimizationLevel, Position, RhaiError, RhaiResult,
|
||||||
Scope, ERR,
|
RhaiResultOf, Scope, ERR,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use hashbrown::hash_map::Entry;
|
use hashbrown::hash_map::Entry;
|
||||||
@ -570,7 +570,6 @@ impl Engine {
|
|||||||
caches: &mut Caches,
|
caches: &mut Caches,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
_native_only: bool,
|
|
||||||
hashes: FnCallHashes,
|
hashes: FnCallHashes,
|
||||||
args: &mut FnCallArgs,
|
args: &mut FnCallArgs,
|
||||||
is_ref_mut: bool,
|
is_ref_mut: bool,
|
||||||
@ -590,55 +589,58 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
ensure_no_data_race(fn_name, args, is_ref_mut)?;
|
ensure_no_data_race(fn_name, args, is_ref_mut)?;
|
||||||
|
|
||||||
// These may be redirected from method style calls.
|
|
||||||
match fn_name {
|
|
||||||
// Handle type_of()
|
|
||||||
KEYWORD_TYPE_OF if args.len() == 1 => {
|
|
||||||
let typ = self.map_type_name(args[0].type_name()).to_string().into();
|
|
||||||
return Ok((typ, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle is_def_fn()
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
crate::engine::KEYWORD_IS_DEF_FN
|
|
||||||
if args.len() == 2 && args[0].is::<FnPtr>() && args[1].is::<crate::INT>() =>
|
|
||||||
{
|
|
||||||
let fn_name = args[0].read_lock::<ImmutableString>().expect("`FnPtr`");
|
|
||||||
let num_params = args[1].as_int().expect("`INT`");
|
|
||||||
|
|
||||||
return Ok((
|
|
||||||
if num_params < 0 || num_params > crate::MAX_USIZE_INT {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
let hash_script = calc_fn_hash(None, fn_name.as_str(), num_params as usize);
|
|
||||||
self.has_script_fn(Some(global), caches, lib, hash_script)
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
false,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle is_shared()
|
|
||||||
#[cfg(not(feature = "no_closure"))]
|
|
||||||
crate::engine::KEYWORD_IS_SHARED if args.len() == 1 => {
|
|
||||||
return no_method_err(fn_name, pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR if args.len() == 1 => {
|
|
||||||
return no_method_err(fn_name, pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY if !args.is_empty() => {
|
|
||||||
return no_method_err(fn_name, pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
let level = level + 1;
|
let level = level + 1;
|
||||||
|
|
||||||
|
// These may be redirected from method style calls.
|
||||||
|
if hashes.is_native_only() {
|
||||||
|
match fn_name {
|
||||||
|
// Handle type_of()
|
||||||
|
KEYWORD_TYPE_OF if args.len() == 1 => {
|
||||||
|
let typ = self.map_type_name(args[0].type_name()).to_string().into();
|
||||||
|
return Ok((typ, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle is_def_fn()
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
crate::engine::KEYWORD_IS_DEF_FN
|
||||||
|
if args.len() == 2 && args[0].is::<FnPtr>() && args[1].is::<crate::INT>() =>
|
||||||
|
{
|
||||||
|
let fn_name = args[0].read_lock::<ImmutableString>().expect("`FnPtr`");
|
||||||
|
let num_params = args[1].as_int().expect("`INT`");
|
||||||
|
|
||||||
|
return Ok((
|
||||||
|
if num_params < 0 || num_params > crate::MAX_USIZE_INT {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
let hash_script =
|
||||||
|
calc_fn_hash(None, fn_name.as_str(), num_params as usize);
|
||||||
|
self.has_script_fn(Some(global), caches, lib, hash_script)
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
false,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle is_shared()
|
||||||
|
#[cfg(not(feature = "no_closure"))]
|
||||||
|
crate::engine::KEYWORD_IS_SHARED if args.len() == 1 => {
|
||||||
|
return no_method_err(fn_name, pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR if args.len() == 1 => {
|
||||||
|
return no_method_err(fn_name, pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY if !args.is_empty() => {
|
||||||
|
return no_method_err(fn_name, pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
if !_native_only {
|
if !hashes.is_native_only() {
|
||||||
// Script-defined function call?
|
// Script-defined function call?
|
||||||
let local_entry = &mut None;
|
let local_entry = &mut None;
|
||||||
|
|
||||||
@ -649,7 +651,7 @@ impl Engine {
|
|||||||
local_entry,
|
local_entry,
|
||||||
lib,
|
lib,
|
||||||
fn_name,
|
fn_name,
|
||||||
hashes.script,
|
hashes.script(),
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
None,
|
None,
|
||||||
@ -719,7 +721,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Native function call
|
// Native function call
|
||||||
let hash = hashes.native;
|
let hash = hashes.native();
|
||||||
self.call_native_fn(
|
self.call_native_fn(
|
||||||
global, caches, lib, fn_name, hash, args, is_ref_mut, false, pos, level,
|
global, caches, lib, fn_name, hash, args, is_ref_mut, false, pos, level,
|
||||||
)
|
)
|
||||||
@ -808,9 +810,13 @@ impl Engine {
|
|||||||
let fn_ptr = target.read_lock::<FnPtr>().expect("`FnPtr`");
|
let fn_ptr = target.read_lock::<FnPtr>().expect("`FnPtr`");
|
||||||
// Redirect function name
|
// Redirect function name
|
||||||
let fn_name = fn_ptr.fn_name();
|
let fn_name = fn_ptr.fn_name();
|
||||||
let args_len = call_args.len() + fn_ptr.curry().len();
|
|
||||||
// Recalculate hashes
|
// Recalculate hashes
|
||||||
let new_hash = calc_fn_hash(None, fn_name, args_len).into();
|
let args_len = call_args.len() + fn_ptr.curry().len();
|
||||||
|
let new_hash = if !fn_ptr.is_anonymous() && !is_valid_function_name(fn_name) {
|
||||||
|
FnCallHashes::from_native(calc_fn_hash(None, fn_name, args_len))
|
||||||
|
} else {
|
||||||
|
calc_fn_hash(None, fn_name, args_len).into()
|
||||||
|
};
|
||||||
// Arguments are passed as-is, adding the curried arguments
|
// Arguments are passed as-is, adding the curried arguments
|
||||||
let mut curry = FnArgsVec::with_capacity(fn_ptr.curry().len());
|
let mut curry = FnArgsVec::with_capacity(fn_ptr.curry().len());
|
||||||
curry.extend(fn_ptr.curry().iter().cloned());
|
curry.extend(fn_ptr.curry().iter().cloned());
|
||||||
@ -825,7 +831,6 @@ impl Engine {
|
|||||||
caches,
|
caches,
|
||||||
lib,
|
lib,
|
||||||
fn_name,
|
fn_name,
|
||||||
false,
|
|
||||||
new_hash,
|
new_hash,
|
||||||
&mut args,
|
&mut args,
|
||||||
false,
|
false,
|
||||||
@ -845,20 +850,25 @@ impl Engine {
|
|||||||
|
|
||||||
// FnPtr call on object
|
// FnPtr call on object
|
||||||
let fn_ptr = mem::take(&mut call_args[0]).cast::<FnPtr>();
|
let fn_ptr = mem::take(&mut call_args[0]).cast::<FnPtr>();
|
||||||
|
let is_anon = fn_ptr.is_anonymous();
|
||||||
call_args = &mut call_args[1..];
|
call_args = &mut call_args[1..];
|
||||||
|
|
||||||
// Redirect function name
|
// Redirect function name
|
||||||
let fn_name = fn_ptr.fn_name();
|
let (fn_name, fn_curry) = fn_ptr.take_data();
|
||||||
let args_len = call_args.len() + fn_ptr.curry().len();
|
|
||||||
// Recalculate hash
|
// Recalculate hash
|
||||||
let new_hash = FnCallHashes::from_all(
|
let args_len = call_args.len() + fn_curry.len();
|
||||||
#[cfg(not(feature = "no_function"))]
|
let new_hash = if !is_anon && !is_valid_function_name(&fn_name) {
|
||||||
calc_fn_hash(None, fn_name, args_len),
|
FnCallHashes::from_native(calc_fn_hash(None, &fn_name, args_len + 1))
|
||||||
calc_fn_hash(None, fn_name, args_len + 1),
|
} else {
|
||||||
);
|
FnCallHashes::from_all(
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
calc_fn_hash(None, &fn_name, args_len),
|
||||||
|
calc_fn_hash(None, &fn_name, args_len + 1),
|
||||||
|
)
|
||||||
|
};
|
||||||
// Replace the first argument with the object pointer, adding the curried arguments
|
// Replace the first argument with the object pointer, adding the curried arguments
|
||||||
let mut curry = FnArgsVec::with_capacity(fn_ptr.curry().len());
|
let mut curry = FnArgsVec::with_capacity(fn_curry.len());
|
||||||
curry.extend(fn_ptr.curry().iter().cloned());
|
curry.extend(fn_curry.into_iter());
|
||||||
let mut args = FnArgsVec::with_capacity(curry.len() + call_args.len() + 1);
|
let mut args = FnArgsVec::with_capacity(curry.len() + call_args.len() + 1);
|
||||||
args.push(target.as_mut());
|
args.push(target.as_mut());
|
||||||
args.extend(curry.iter_mut());
|
args.extend(curry.iter_mut());
|
||||||
@ -870,8 +880,7 @@ impl Engine {
|
|||||||
global,
|
global,
|
||||||
caches,
|
caches,
|
||||||
lib,
|
lib,
|
||||||
fn_name,
|
&fn_name,
|
||||||
false,
|
|
||||||
new_hash,
|
new_hash,
|
||||||
&mut args,
|
&mut args,
|
||||||
is_ref_mut,
|
is_ref_mut,
|
||||||
@ -939,11 +948,19 @@ impl Engine {
|
|||||||
call_args = &mut _arg_values;
|
call_args = &mut _arg_values;
|
||||||
}
|
}
|
||||||
// Recalculate the hash based on the new function name and new arguments
|
// Recalculate the hash based on the new function name and new arguments
|
||||||
hash = FnCallHashes::from_all(
|
hash = if !fn_ptr.is_anonymous() && !is_valid_function_name(&fn_name) {
|
||||||
#[cfg(not(feature = "no_function"))]
|
FnCallHashes::from_native(calc_fn_hash(
|
||||||
calc_fn_hash(None, fn_name, call_args.len()),
|
None,
|
||||||
calc_fn_hash(None, fn_name, call_args.len() + 1),
|
fn_name,
|
||||||
);
|
call_args.len() + 1,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
FnCallHashes::from_all(
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
calc_fn_hash(None, fn_name, call_args.len()),
|
||||||
|
calc_fn_hash(None, fn_name, call_args.len() + 1),
|
||||||
|
)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -959,7 +976,6 @@ impl Engine {
|
|||||||
caches,
|
caches,
|
||||||
lib,
|
lib,
|
||||||
fn_name,
|
fn_name,
|
||||||
false,
|
|
||||||
hash,
|
hash,
|
||||||
&mut args,
|
&mut args,
|
||||||
is_ref_mut,
|
is_ref_mut,
|
||||||
@ -987,7 +1003,6 @@ impl Engine {
|
|||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
native_only: bool,
|
|
||||||
first_arg: Option<&Expr>,
|
first_arg: Option<&Expr>,
|
||||||
args_expr: &[Expr],
|
args_expr: &[Expr],
|
||||||
hashes: FnCallHashes,
|
hashes: FnCallHashes,
|
||||||
@ -996,7 +1011,6 @@ impl Engine {
|
|||||||
pos: Position,
|
pos: Position,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> RhaiResult {
|
) -> RhaiResult {
|
||||||
let native = native_only;
|
|
||||||
let mut first_arg = first_arg;
|
let mut first_arg = first_arg;
|
||||||
let mut a_expr = args_expr;
|
let mut a_expr = args_expr;
|
||||||
let mut total_args = if first_arg.is_some() { 1 } else { 0 } + a_expr.len();
|
let mut total_args = if first_arg.is_some() { 1 } else { 0 } + a_expr.len();
|
||||||
@ -1020,10 +1034,12 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let fn_ptr = arg_value.cast::<FnPtr>();
|
let fn_ptr = arg_value.cast::<FnPtr>();
|
||||||
curry.extend(fn_ptr.curry().iter().cloned());
|
let is_anon = fn_ptr.is_anonymous();
|
||||||
|
let (fn_name, fn_curry) = fn_ptr.take_data();
|
||||||
|
curry.extend(fn_curry.into_iter());
|
||||||
|
|
||||||
// Redirect function name
|
// Redirect function name
|
||||||
redirected = fn_ptr.take_data().0;
|
redirected = fn_name;
|
||||||
name = &redirected;
|
name = &redirected;
|
||||||
|
|
||||||
// Shift the arguments
|
// Shift the arguments
|
||||||
@ -1035,7 +1051,8 @@ impl Engine {
|
|||||||
|
|
||||||
// Recalculate hash
|
// Recalculate hash
|
||||||
let args_len = total_args + curry.len();
|
let args_len = total_args + curry.len();
|
||||||
hashes = if hashes.is_native_only() {
|
|
||||||
|
hashes = if !is_anon && !is_valid_function_name(name) {
|
||||||
FnCallHashes::from_native(calc_fn_hash(None, name, args_len))
|
FnCallHashes::from_native(calc_fn_hash(None, name, args_len))
|
||||||
} else {
|
} else {
|
||||||
calc_fn_hash(None, name, args_len).into()
|
calc_fn_hash(None, name, args_len).into()
|
||||||
@ -1193,8 +1210,8 @@ impl Engine {
|
|||||||
|
|
||||||
return self
|
return self
|
||||||
.exec_fn_call(
|
.exec_fn_call(
|
||||||
scope, global, caches, lib, name, native, hashes, &mut args, is_ref_mut, false,
|
scope, global, caches, lib, name, hashes, &mut args, is_ref_mut, false, pos,
|
||||||
pos, level,
|
level,
|
||||||
)
|
)
|
||||||
.map(|(v, ..)| v);
|
.map(|(v, ..)| v);
|
||||||
}
|
}
|
||||||
@ -1256,8 +1273,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
None, global, caches, lib, name, native, hashes, &mut args, is_ref_mut, false, pos,
|
None, global, caches, lib, name, hashes, &mut args, is_ref_mut, false, pos, level,
|
||||||
level,
|
|
||||||
)
|
)
|
||||||
.map(|(v, ..)| v)
|
.map(|(v, ..)| v)
|
||||||
}
|
}
|
||||||
|
@ -416,7 +416,6 @@ impl<'a> NativeCallContext<'a> {
|
|||||||
caches,
|
caches,
|
||||||
self.lib,
|
self.lib,
|
||||||
fn_name,
|
fn_name,
|
||||||
false,
|
|
||||||
hash,
|
hash,
|
||||||
args,
|
args,
|
||||||
is_ref_mut,
|
is_ref_mut,
|
||||||
|
@ -1142,7 +1142,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Overloaded operators can override built-in.
|
// Overloaded operators can override built-in.
|
||||||
_ if x.args.len() == 2 && x.operator_token.is_some() && (state.engine.fast_operators() || !has_native_fn_override(state.engine, x.hashes.native, &arg_types)) => {
|
_ if x.args.len() == 2 && x.operator_token.is_some() && (state.engine.fast_operators() || !has_native_fn_override(state.engine, x.hashes.native(), &arg_types)) => {
|
||||||
if let Some(result) = get_builtin_binary_op_fn(x.operator_token.as_ref().unwrap(), &arg_values[0], &arg_values[1])
|
if let Some(result) = get_builtin_binary_op_fn(x.operator_token.as_ref().unwrap(), &arg_values[0], &arg_values[1])
|
||||||
.and_then(|f| {
|
.and_then(|f| {
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
@ -469,7 +469,7 @@ fn parse_var_name(input: &mut TokenStream) -> ParseResult<(SmartString, Position
|
|||||||
// Variable name
|
// Variable name
|
||||||
(Token::Identifier(s), pos) => Ok((*s, pos)),
|
(Token::Identifier(s), pos) => Ok((*s, pos)),
|
||||||
// Reserved keyword
|
// Reserved keyword
|
||||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
(Token::Reserved(s), pos) if is_valid_identifier(s.as_str()) => {
|
||||||
Err(PERR::Reserved(s.to_string()).into_err(pos))
|
Err(PERR::Reserved(s.to_string()).into_err(pos))
|
||||||
}
|
}
|
||||||
// Bad identifier
|
// Bad identifier
|
||||||
@ -486,7 +486,7 @@ fn parse_symbol(input: &mut TokenStream) -> ParseResult<(SmartString, Position)>
|
|||||||
// Symbol
|
// Symbol
|
||||||
(token, pos) if token.is_standard_symbol() => Ok((token.literal_syntax().into(), pos)),
|
(token, pos) if token.is_standard_symbol() => Ok((token.literal_syntax().into(), pos)),
|
||||||
// Reserved symbol
|
// Reserved symbol
|
||||||
(Token::Reserved(s), pos) if !is_valid_identifier(s.chars()) => Ok((*s, pos)),
|
(Token::Reserved(s), pos) if !is_valid_identifier(s.as_str()) => Ok((*s, pos)),
|
||||||
// Bad symbol
|
// Bad symbol
|
||||||
(Token::LexError(err), pos) => Err(err.into_err(pos)),
|
(Token::LexError(err), pos) => Err(err.into_err(pos)),
|
||||||
// Not a symbol
|
// Not a symbol
|
||||||
@ -599,9 +599,7 @@ impl Engine {
|
|||||||
#[cfg(feature = "no_module")]
|
#[cfg(feature = "no_module")]
|
||||||
let hash = calc_fn_hash(None, &id, 0);
|
let hash = calc_fn_hash(None, &id, 0);
|
||||||
|
|
||||||
let is_valid_function_name = is_valid_function_name(&id);
|
let hashes = if is_valid_function_name(&id) {
|
||||||
|
|
||||||
let hashes = if is_valid_function_name {
|
|
||||||
hash.into()
|
hash.into()
|
||||||
} else {
|
} else {
|
||||||
FnCallHashes::from_native(hash)
|
FnCallHashes::from_native(hash)
|
||||||
@ -613,8 +611,6 @@ impl Engine {
|
|||||||
name: state.get_interned_string(id),
|
name: state.get_interned_string(id),
|
||||||
capture_parent_scope,
|
capture_parent_scope,
|
||||||
operator_token: None,
|
operator_token: None,
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
can_be_script: is_valid_function_name,
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
namespace,
|
namespace,
|
||||||
hashes,
|
hashes,
|
||||||
@ -671,9 +667,7 @@ impl Engine {
|
|||||||
#[cfg(feature = "no_module")]
|
#[cfg(feature = "no_module")]
|
||||||
let hash = calc_fn_hash(None, &id, args.len());
|
let hash = calc_fn_hash(None, &id, args.len());
|
||||||
|
|
||||||
let is_valid_function_name = is_valid_function_name(&id);
|
let hashes = if is_valid_function_name(&id) {
|
||||||
|
|
||||||
let hashes = if is_valid_function_name {
|
|
||||||
hash.into()
|
hash.into()
|
||||||
} else {
|
} else {
|
||||||
FnCallHashes::from_native(hash)
|
FnCallHashes::from_native(hash)
|
||||||
@ -685,8 +679,6 @@ impl Engine {
|
|||||||
name: state.get_interned_string(id),
|
name: state.get_interned_string(id),
|
||||||
capture_parent_scope,
|
capture_parent_scope,
|
||||||
operator_token: None,
|
operator_token: None,
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
can_be_script: is_valid_function_name,
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
namespace,
|
namespace,
|
||||||
hashes,
|
hashes,
|
||||||
@ -1012,7 +1004,7 @@ impl Engine {
|
|||||||
(Token::InterpolatedString(..), pos) => {
|
(Token::InterpolatedString(..), pos) => {
|
||||||
return Err(PERR::PropertyExpected.into_err(pos))
|
return Err(PERR::PropertyExpected.into_err(pos))
|
||||||
}
|
}
|
||||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
(Token::Reserved(s), pos) if is_valid_identifier(s.as_str()) => {
|
||||||
return Err(PERR::Reserved(s.to_string()).into_err(pos));
|
return Err(PERR::Reserved(s.to_string()).into_err(pos));
|
||||||
}
|
}
|
||||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||||
@ -1944,8 +1936,6 @@ impl Engine {
|
|||||||
pos,
|
pos,
|
||||||
operator_token: Some(token),
|
operator_token: Some(token),
|
||||||
capture_parent_scope: false,
|
capture_parent_scope: false,
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
can_be_script: false,
|
|
||||||
}
|
}
|
||||||
.into_fn_call_expr(pos))
|
.into_fn_call_expr(pos))
|
||||||
}
|
}
|
||||||
@ -1976,8 +1966,6 @@ impl Engine {
|
|||||||
pos,
|
pos,
|
||||||
operator_token: Some(token),
|
operator_token: Some(token),
|
||||||
capture_parent_scope: false,
|
capture_parent_scope: false,
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
can_be_script: false,
|
|
||||||
}
|
}
|
||||||
.into_fn_call_expr(pos))
|
.into_fn_call_expr(pos))
|
||||||
}
|
}
|
||||||
@ -2001,8 +1989,6 @@ impl Engine {
|
|||||||
pos,
|
pos,
|
||||||
operator_token: Some(token),
|
operator_token: Some(token),
|
||||||
capture_parent_scope: false,
|
capture_parent_scope: false,
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
can_be_script: false,
|
|
||||||
}
|
}
|
||||||
.into_fn_call_expr(pos))
|
.into_fn_call_expr(pos))
|
||||||
}
|
}
|
||||||
@ -2217,11 +2203,15 @@ impl Engine {
|
|||||||
// lhs.func(...)
|
// lhs.func(...)
|
||||||
(lhs, Expr::FnCall(mut func, func_pos)) => {
|
(lhs, Expr::FnCall(mut func, func_pos)) => {
|
||||||
// Recalculate hash
|
// Recalculate hash
|
||||||
func.hashes = FnCallHashes::from_all(
|
func.hashes = if is_valid_function_name(&func.name) {
|
||||||
#[cfg(not(feature = "no_function"))]
|
FnCallHashes::from_all(
|
||||||
calc_fn_hash(None, &func.name, func.args.len()),
|
#[cfg(not(feature = "no_function"))]
|
||||||
calc_fn_hash(None, &func.name, func.args.len() + 1),
|
calc_fn_hash(None, &func.name, func.args.len()),
|
||||||
);
|
calc_fn_hash(None, &func.name, func.args.len() + 1),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
FnCallHashes::from_native(calc_fn_hash(None, &func.name, func.args.len() + 1))
|
||||||
|
};
|
||||||
|
|
||||||
let rhs = Expr::MethodCall(func, func_pos);
|
let rhs = Expr::MethodCall(func, func_pos);
|
||||||
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos))
|
Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos))
|
||||||
@ -2263,11 +2253,19 @@ impl Engine {
|
|||||||
// lhs.func().dot_rhs or lhs.func()[idx_rhs]
|
// lhs.func().dot_rhs or lhs.func()[idx_rhs]
|
||||||
Expr::FnCall(mut func, func_pos) => {
|
Expr::FnCall(mut func, func_pos) => {
|
||||||
// Recalculate hash
|
// Recalculate hash
|
||||||
func.hashes = FnCallHashes::from_all(
|
func.hashes = if is_valid_function_name(&func.name) {
|
||||||
#[cfg(not(feature = "no_function"))]
|
FnCallHashes::from_all(
|
||||||
calc_fn_hash(None, &func.name, func.args.len()),
|
#[cfg(not(feature = "no_function"))]
|
||||||
calc_fn_hash(None, &func.name, func.args.len() + 1),
|
calc_fn_hash(None, &func.name, func.args.len()),
|
||||||
);
|
calc_fn_hash(None, &func.name, func.args.len() + 1),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
FnCallHashes::from_native(calc_fn_hash(
|
||||||
|
None,
|
||||||
|
&func.name,
|
||||||
|
func.args.len() + 1,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
let new_lhs = BinaryExpr {
|
let new_lhs = BinaryExpr {
|
||||||
lhs: Expr::MethodCall(func, func_pos),
|
lhs: Expr::MethodCall(func, func_pos),
|
||||||
@ -2322,7 +2320,7 @@ impl Engine {
|
|||||||
.get(&**c)
|
.get(&**c)
|
||||||
.copied()
|
.copied()
|
||||||
.ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*current_pos))?,
|
.ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*current_pos))?,
|
||||||
Token::Reserved(c) if !is_valid_identifier(c.chars()) => {
|
Token::Reserved(c) if !is_valid_identifier(c) => {
|
||||||
return Err(PERR::UnknownOperator(c.to_string()).into_err(*current_pos))
|
return Err(PERR::UnknownOperator(c.to_string()).into_err(*current_pos))
|
||||||
}
|
}
|
||||||
_ => current_op.precedence(),
|
_ => current_op.precedence(),
|
||||||
@ -2347,7 +2345,7 @@ impl Engine {
|
|||||||
.get(&**c)
|
.get(&**c)
|
||||||
.copied()
|
.copied()
|
||||||
.ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*next_pos))?,
|
.ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*next_pos))?,
|
||||||
Token::Reserved(c) if !is_valid_identifier(c.chars()) => {
|
Token::Reserved(c) if !is_valid_identifier(c) => {
|
||||||
return Err(PERR::UnknownOperator(c.to_string()).into_err(*next_pos))
|
return Err(PERR::UnknownOperator(c.to_string()).into_err(*next_pos))
|
||||||
}
|
}
|
||||||
_ => next_op.precedence(),
|
_ => next_op.precedence(),
|
||||||
@ -2371,8 +2369,8 @@ impl Engine {
|
|||||||
|
|
||||||
let op = op_token.syntax();
|
let op = op_token.syntax();
|
||||||
let hash = calc_fn_hash(None, &op, 2);
|
let hash = calc_fn_hash(None, &op, 2);
|
||||||
let is_function = is_valid_function_name(&op);
|
let is_valid_script_function = is_valid_function_name(&op);
|
||||||
let operator_token = if is_function {
|
let operator_token = if is_valid_script_function {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(op_token.clone())
|
Some(op_token.clone())
|
||||||
@ -2387,8 +2385,6 @@ impl Engine {
|
|||||||
pos,
|
pos,
|
||||||
operator_token,
|
operator_token,
|
||||||
capture_parent_scope: false,
|
capture_parent_scope: false,
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
can_be_script: is_function,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut args = StaticVec::new_const();
|
let mut args = StaticVec::new_const();
|
||||||
@ -2474,7 +2470,7 @@ impl Engine {
|
|||||||
let pos = args[0].start_position();
|
let pos = args[0].start_position();
|
||||||
|
|
||||||
FnCallExpr {
|
FnCallExpr {
|
||||||
hashes: if is_function {
|
hashes: if is_valid_script_function {
|
||||||
hash.into()
|
hash.into()
|
||||||
} else {
|
} else {
|
||||||
FnCallHashes::from_native(hash)
|
FnCallHashes::from_native(hash)
|
||||||
@ -3732,8 +3728,6 @@ impl Engine {
|
|||||||
pos,
|
pos,
|
||||||
operator_token: None,
|
operator_token: None,
|
||||||
capture_parent_scope: false,
|
capture_parent_scope: false,
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
can_be_script: false,
|
|
||||||
}
|
}
|
||||||
.into_fn_call_expr(pos);
|
.into_fn_call_expr(pos);
|
||||||
|
|
||||||
|
@ -2199,7 +2199,7 @@ fn get_identifier(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_valid_identifier = is_valid_identifier(identifier.chars());
|
let is_valid_identifier = is_valid_identifier(&identifier);
|
||||||
|
|
||||||
if let Some(token) = Token::lookup_from_syntax(&identifier) {
|
if let Some(token) = Token::lookup_from_syntax(&identifier) {
|
||||||
return (token, start_pos);
|
return (token, start_pos);
|
||||||
@ -2233,10 +2233,10 @@ pub fn is_keyword_function(name: &str) -> bool {
|
|||||||
/// _(internals)_ Is a text string a valid identifier?
|
/// _(internals)_ Is a text string a valid identifier?
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_valid_identifier(name: impl Iterator<Item = char>) -> bool {
|
pub fn is_valid_identifier(name: &str) -> bool {
|
||||||
let mut first_alphabetic = false;
|
let mut first_alphabetic = false;
|
||||||
|
|
||||||
for ch in name {
|
for ch in name.chars() {
|
||||||
match ch {
|
match ch {
|
||||||
'_' => (),
|
'_' => (),
|
||||||
_ if is_id_first_alphabetic(ch) => first_alphabetic = true,
|
_ if is_id_first_alphabetic(ch) => first_alphabetic = true,
|
||||||
@ -2254,7 +2254,7 @@ pub fn is_valid_identifier(name: impl Iterator<Item = char>) -> bool {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_valid_function_name(name: &str) -> bool {
|
pub fn is_valid_function_name(name: &str) -> bool {
|
||||||
is_valid_identifier(name.chars())
|
is_valid_identifier(name) && !is_keyword_function(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is a character valid to start an identifier?
|
/// Is a character valid to start an identifier?
|
||||||
@ -2433,7 +2433,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||||||
(.., true) => unreachable!("no custom operators"),
|
(.., true) => unreachable!("no custom operators"),
|
||||||
// Reserved keyword that is not custom and disabled.
|
// Reserved keyword that is not custom and disabled.
|
||||||
(token, false) if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token) => {
|
(token, false) if !self.engine.disabled_symbols.is_empty() && self.engine.disabled_symbols.contains(token) => {
|
||||||
let msg = format!("reserved {} '{token}' is disabled", if is_valid_identifier(token.chars()) { "keyword"} else {"symbol"});
|
let msg = format!("reserved {} '{token}' is disabled", if is_valid_identifier(token) { "keyword"} else {"symbol"});
|
||||||
Token::LexError(LERR::ImproperSymbol(s.to_string(), msg).into())
|
Token::LexError(LERR::ImproperSymbol(s.to_string(), msg).into())
|
||||||
},
|
},
|
||||||
// Reserved keyword/operator that is not custom.
|
// Reserved keyword/operator that is not custom.
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
//! The `FnPtr` type.
|
//! The `FnPtr` type.
|
||||||
|
|
||||||
use crate::tokenizer::is_valid_identifier;
|
|
||||||
use crate::types::dynamic::Variant;
|
use crate::types::dynamic::Variant;
|
||||||
use crate::{
|
use crate::{
|
||||||
Dynamic, Engine, FuncArgs, ImmutableString, Module, NativeCallContext, Position, RhaiError,
|
is_valid_function_name, Dynamic, Engine, FuncArgs, ImmutableString, Module, NativeCallContext,
|
||||||
RhaiResult, RhaiResultOf, StaticVec, AST, ERR,
|
Position, RhaiError, RhaiResult, RhaiResultOf, StaticVec, AST, ERR,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -254,7 +253,7 @@ impl TryFrom<ImmutableString> for FnPtr {
|
|||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn try_from(value: ImmutableString) -> RhaiResultOf<Self> {
|
fn try_from(value: ImmutableString) -> RhaiResultOf<Self> {
|
||||||
if is_valid_identifier(value.chars()) {
|
if is_valid_function_name(&value) {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name: value,
|
name: value,
|
||||||
curry: StaticVec::new_const(),
|
curry: StaticVec::new_const(),
|
||||||
|
@ -238,7 +238,7 @@ impl fmt::Display for ParseErrorType {
|
|||||||
Self::AssignmentToInvalidLHS(s) => f.write_str(s),
|
Self::AssignmentToInvalidLHS(s) => f.write_str(s),
|
||||||
|
|
||||||
Self::LiteralTooLarge(typ, max) => write!(f, "{typ} exceeds the maximum limit ({max})"),
|
Self::LiteralTooLarge(typ, max) => write!(f, "{typ} exceeds the maximum limit ({max})"),
|
||||||
Self::Reserved(s) if is_valid_identifier(s.chars()) => write!(f, "'{s}' is a reserved keyword"),
|
Self::Reserved(s) if is_valid_identifier(s.as_str()) => write!(f, "'{s}' is a reserved keyword"),
|
||||||
Self::Reserved(s) => write!(f, "'{s}' is a reserved symbol"),
|
Self::Reserved(s) => write!(f, "'{s}' is a reserved symbol"),
|
||||||
Self::UnexpectedEOF => f.write_str("Script is incomplete"),
|
Self::UnexpectedEOF => f.write_str("Script is incomplete"),
|
||||||
Self::WrongSwitchIntegerCase => f.write_str("Integer switch case cannot follow a range case"),
|
Self::WrongSwitchIntegerCase => f.write_str("Integer switch case cannot follow a range case"),
|
||||||
|
Loading…
Reference in New Issue
Block a user