Build-in certain common operators.

This commit is contained in:
Stephen Chung 2020-05-23 18:59:28 +08:00
parent a743c47345
commit b49e1e199a
9 changed files with 336 additions and 147 deletions

View File

@ -9,10 +9,18 @@ Regression
* Do not optimize script with `eval_expression` - it is assumed to be one-off and short.
Breaking changes
----------------
* `Engine::compile_XXX` functions now return `ParseError` instead of `Box<ParseError>`.
* The `RegisterDynamicFn` trait is merged into the `RegisterResutlFn` trait which now always returns
`Result<Dynamic, Box<EvalAltResult>>`.
* Default maximum limit on levels of nested function calls is fine-tuned and set to a different value.
New features
------------
* Set limits on maximum level of nesting expressions and statements to avoid panics during parsing.
* Set limit on maximum level of nesting expressions and statements to avoid panics during parsing.
* New `EvalPackage` to disable `eval`.
* More benchmarks.

View File

@ -29,7 +29,7 @@ fn bench_engine_new_raw_core(bench: &mut Bencher) {
#[bench]
fn bench_engine_register_fn(bench: &mut Bencher) {
fn hello(a: INT, b: Array, c: Map) -> bool {
fn hello(_a: INT, _b: Array, _c: Map) -> bool {
true
}

View File

@ -86,7 +86,7 @@ fn bench_eval_call_expression(bench: &mut Bencher) {
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;
let mut engine = Engine::new();
let engine = Engine::new();
bench.iter(|| engine.eval_expression::<bool>(script).unwrap());
}
@ -101,7 +101,7 @@ fn bench_eval_call(bench: &mut Bencher) {
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;
let mut engine = Engine::new();
let engine = Engine::new();
bench.iter(|| engine.eval::<bool>(script).unwrap());
}

View File

@ -7,13 +7,16 @@ use crate::fn_native::{FnCallArgs, Shared};
use crate::module::Module;
use crate::optimize::OptimizationLevel;
use crate::packages::{CorePackage, Package, PackageLibrary, PackagesCollection, StandardPackage};
use crate::parser::{Expr, FnAccess, FnDef, ReturnType, Stmt, AST};
use crate::parser::{Expr, FnAccess, FnDef, ReturnType, Stmt, AST, INT};
use crate::r#unsafe::{unsafe_cast_var_name_to_lifetime, unsafe_mut_cast_to_lifetime};
use crate::result::EvalAltResult;
use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::token::Position;
use crate::utils::StaticVec;
#[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT;
#[cfg(not(feature = "no_module"))]
use crate::module::{resolvers, ModuleRef, ModuleResolver};
@ -706,6 +709,14 @@ impl Engine {
});
}
// If it is a 2-operand operator, see if it is built in
if args.len() == 2 && args[0].type_id() == args[1].type_id() {
match run_builtin_op(fn_name, args[0], args[1])? {
Some(v) => return Ok((v, false)),
None => (),
}
}
// Return default value (if any)
if let Some(val) = def_val {
return Ok((val.clone(), false));
@ -873,6 +884,7 @@ impl Engine {
&self,
state: &mut State,
fn_name: &str,
native_only: bool,
hash_fn_def: u64,
args: &mut FnCallArgs,
is_ref: bool,
@ -887,7 +899,7 @@ impl Engine {
args.len(),
args.iter().map(|a| a.type_id()),
);
let hashes = (hash_fn, hash_fn_def);
let hashes = (hash_fn, if native_only { 0 } else { hash_fn_def });
match fn_name {
// type_of
@ -1003,7 +1015,7 @@ impl Engine {
match rhs {
// xxx.fn_name(arg_expr_list)
Expr::FnCall(x) if x.1.is_none() => {
let ((name, pos), _, hash_fn_def, _, def_val) = x.as_ref();
let ((name, native, pos), _, hash, _, def_val) = x.as_ref();
let def_val = def_val.as_ref();
let mut arg_values: StaticVec<_> = once(obj)
@ -1016,7 +1028,7 @@ impl Engine {
.collect();
let args = arg_values.as_mut();
self.exec_fn_call(state, name, *hash_fn_def, args, is_ref, def_val, *pos, 0)
self.exec_fn_call(state, name, *native, *hash, args, is_ref, def_val, *pos, 0)
}
// xxx.module::fn_name(...) - syntax error
Expr::FnCall(_) => unreachable!(),
@ -1045,14 +1057,14 @@ impl Engine {
Expr::Property(x) if new_val.is_some() => {
let ((_, _, setter), pos) = x.as_ref();
let mut args = [obj, new_val.as_mut().unwrap()];
self.exec_fn_call(state, setter, 0, &mut args, is_ref, None, *pos, 0)
self.exec_fn_call(state, setter, true, 0, &mut args, is_ref, None, *pos, 0)
.map(|(v, _)| (v, true))
}
// xxx.id
Expr::Property(x) => {
let ((_, getter, _), pos) = x.as_ref();
let mut args = [obj];
self.exec_fn_call(state, getter, 0, &mut args, is_ref, None, *pos, 0)
self.exec_fn_call(state, getter, true, 0, &mut args, is_ref, None, *pos, 0)
.map(|(v, _)| (v, false))
}
#[cfg(not(feature = "no_object"))]
@ -1083,7 +1095,8 @@ impl Engine {
let (mut val, updated) = if let Expr::Property(p) = &x.0 {
let ((_, getter, _), _) = p.as_ref();
self.exec_fn_call(state, getter, 0, &mut args[..1], is_ref, None, x.2, 0)?
let args = &mut args[..1];
self.exec_fn_call(state, getter, true, 0, args, is_ref, None, x.2, 0)?
} else {
// Syntax error
return Err(Box::new(EvalAltResult::ErrorDotExpr(
@ -1104,7 +1117,7 @@ impl Engine {
let ((_, _, setter), _) = p.as_ref();
// Re-use args because the first &mut parameter will not be consumed
args[1] = val;
self.exec_fn_call(state, setter, 0, args, is_ref, None, x.2, 0)
self.exec_fn_call(state, setter, true, 0, args, is_ref, None, x.2, 0)
.or_else(|err| match *err {
// If there is no setter, no need to feed it back because the property is read-only
EvalAltResult::ErrorDotExpr(_, _) => Ok(Default::default()),
@ -1306,7 +1319,7 @@ impl Engine {
_ => {
let type_name = self.map_type_name(val.type_name());
let args = &mut [val, &mut idx];
self.exec_fn_call(state, FUNC_INDEXER, 0, args, is_ref, None, op_pos, 0)
self.exec_fn_call(state, FUNC_INDEXER, true, 0, args, is_ref, None, op_pos, 0)
.map(|(v, _)| v.into())
.map_err(|_| {
Box::new(EvalAltResult::ErrorIndexingType(type_name.into(), op_pos))
@ -1334,7 +1347,6 @@ impl Engine {
Dynamic(Union::Array(mut rhs_value)) => {
let op = "==";
let def_value = false.into();
let hash_fn_def = calc_fn_hash(empty(), op, 2, empty());
// Call the `==` operator to compare each value
for value in rhs_value.iter_mut() {
@ -1345,7 +1357,7 @@ impl Engine {
// Qualifiers (none) + function name + argument `TypeId`'s.
let hash_fn =
calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id()));
let hashes = (hash_fn, hash_fn_def);
let hashes = (hash_fn, 0);
let (r, _) = self
.call_fn_raw(None, state, op, hashes, args, true, def_value, pos, level)?;
@ -1488,7 +1500,7 @@ impl Engine {
// Normal function call
Expr::FnCall(x) if x.1.is_none() => {
let ((name, pos), _, hash_fn_def, args_expr, def_val) = x.as_ref();
let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref();
let def_val = def_val.as_ref();
let mut arg_values = args_expr
@ -1501,7 +1513,7 @@ impl Engine {
if name == KEYWORD_EVAL && args.len() == 1 && args.get(0).is::<String>() {
let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::<String>()));
if !self.has_override(state, (hash_fn, *hash_fn_def)) {
if !self.has_override(state, (hash_fn, *hash)) {
// eval - only in function call style
let prev_len = scope.len();
let pos = args_expr.get(0).position();
@ -1521,14 +1533,16 @@ impl Engine {
// Normal function call - except for eval (handled above)
let args = args.as_mut();
self.exec_fn_call(state, name, *hash_fn_def, args, false, def_val, *pos, level)
.map(|(v, _)| v)
self.exec_fn_call(
state, name, *native, *hash, args, false, def_val, *pos, level,
)
.map(|(v, _)| v)
}
// Module-qualified function call
#[cfg(not(feature = "no_module"))]
Expr::FnCall(x) if x.1.is_some() => {
let ((name, pos), modules, hash_fn_def, args_expr, def_val) = x.as_ref();
let ((name, _, pos), modules, hash_fn_def, args_expr, def_val) = x.as_ref();
let modules = modules.as_ref().unwrap();
let mut arg_values = args_expr
@ -1934,3 +1948,125 @@ impl Engine {
.unwrap_or(name)
}
}
/// Build in certain common operator implementations to avoid the cost of searching through the functions space.
fn run_builtin_op(
op: &str,
x: &Dynamic,
y: &Dynamic,
) -> Result<Option<Dynamic>, Box<EvalAltResult>> {
use crate::packages::arithmetic::*;
if x.type_id() == TypeId::of::<INT>() {
let x = x.downcast_ref::<INT>().unwrap().clone();
let y = y.downcast_ref::<INT>().unwrap().clone();
#[cfg(not(feature = "unchecked"))]
match op {
"+" => return add(x, y).map(Into::<Dynamic>::into).map(Some),
"-" => return sub(x, y).map(Into::<Dynamic>::into).map(Some),
"*" => return mul(x, y).map(Into::<Dynamic>::into).map(Some),
"/" => return div(x, y).map(Into::<Dynamic>::into).map(Some),
"%" => return modulo(x, y).map(Into::<Dynamic>::into).map(Some),
"~" => return pow_i_i(x, y).map(Into::<Dynamic>::into).map(Some),
">>" => return shr(x, y).map(Into::<Dynamic>::into).map(Some),
"<<" => return shl(x, y).map(Into::<Dynamic>::into).map(Some),
_ => (),
}
#[cfg(feature = "unchecked")]
match op {
"+" => return Ok(Some((x + y).into())),
"-" => return Ok(Some((x - y).into())),
"*" => return Ok(Some((x * y).into())),
"/" => return Ok(Some((x / y).into())),
"%" => return Ok(Some((x % y).into())),
"~" => return pow_i_i_u(x, y).map(Into::<Dynamic>::into).map(Some),
">>" => return shr_u(x, y).map(Into::<Dynamic>::into).map(Some),
"<<" => return shl_u(x, y).map(Into::<Dynamic>::into).map(Some),
_ => (),
}
match op {
"==" => return Ok(Some((x == y).into())),
"!=" => return Ok(Some((x != y).into())),
">" => return Ok(Some((x > y).into())),
">=" => return Ok(Some((x >= y).into())),
"<" => return Ok(Some((x < y).into())),
"<=" => return Ok(Some((x <= y).into())),
"&" => return Ok(Some((x & y).into())),
"|" => return Ok(Some((x | y).into())),
"^" => return Ok(Some((x ^ y).into())),
_ => (),
}
} else if x.type_id() == TypeId::of::<bool>() {
let x = x.downcast_ref::<bool>().unwrap().clone();
let y = y.downcast_ref::<bool>().unwrap().clone();
match op {
"&" => return Ok(Some((x && y).into())),
"|" => return Ok(Some((x || y).into())),
"==" => return Ok(Some((x == y).into())),
"!=" => return Ok(Some((x != y).into())),
_ => (),
}
} else if x.type_id() == TypeId::of::<String>() {
let x = x.downcast_ref::<String>().unwrap();
let y = y.downcast_ref::<String>().unwrap();
match op {
"==" => return Ok(Some((x == y).into())),
"!=" => return Ok(Some((x != y).into())),
">" => return Ok(Some((x > y).into())),
">=" => return Ok(Some((x >= y).into())),
"<" => return Ok(Some((x < y).into())),
"<=" => return Ok(Some((x <= y).into())),
_ => (),
}
} else if x.type_id() == TypeId::of::<char>() {
let x = x.downcast_ref::<char>().unwrap().clone();
let y = y.downcast_ref::<char>().unwrap().clone();
match op {
"==" => return Ok(Some((x == y).into())),
"!=" => return Ok(Some((x != y).into())),
">" => return Ok(Some((x > y).into())),
">=" => return Ok(Some((x >= y).into())),
"<" => return Ok(Some((x < y).into())),
"<=" => return Ok(Some((x <= y).into())),
_ => (),
}
} else if x.type_id() == TypeId::of::<()>() {
match op {
"==" => return Ok(Some(true.into())),
"!=" | ">" | ">=" | "<" | "<=" => return Ok(Some(false.into())),
_ => (),
}
}
#[cfg(not(feature = "no_float"))]
{
if x.type_id() == TypeId::of::<FLOAT>() {
let x = x.downcast_ref::<FLOAT>().unwrap().clone();
let y = y.downcast_ref::<FLOAT>().unwrap().clone();
match op {
"+" => return Ok(Some((x + y).into())),
"-" => return Ok(Some((x - y).into())),
"*" => return Ok(Some((x * y).into())),
"/" => return Ok(Some((x / y).into())),
"%" => return Ok(Some((x % y).into())),
"~" => return pow_f_f(x, y).map(Into::<Dynamic>::into).map(Some),
"==" => return Ok(Some((x == y).into())),
"!=" => return Ok(Some((x != y).into())),
">" => return Ok(Some((x > y).into())),
">=" => return Ok(Some((x >= y).into())),
"<" => return Ok(Some((x < y).into())),
"<=" => return Ok(Some((x <= y).into())),
_ => (),
}
}
}
Ok(None)
}

View File

@ -1,11 +1,10 @@
use crate::any::Dynamic;
use crate::calc_fn_hash;
use crate::engine::{
Engine, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF,
Engine, FunctionsLib, State as EngineState, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT,
KEYWORD_TYPE_OF,
};
use crate::fn_native::FnCallArgs;
use crate::module::Module;
use crate::packages::PackagesCollection;
use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST};
use crate::result::EvalAltResult;
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
@ -112,8 +111,7 @@ impl<'a> State<'a> {
/// Call a registered function
fn call_fn(
packages: &PackagesCollection,
global_module: &Module,
state: &State,
fn_name: &str,
args: &mut FnCallArgs,
pos: Position,
@ -126,12 +124,21 @@ fn call_fn(
args.iter().map(|a| a.type_id()),
);
global_module
.get_fn(hash_fn)
.or_else(|| packages.get_fn(hash_fn))
.map(|func| func.get_native_fn()(args))
.transpose()
.map_err(|err| err.new_position(pos))
state
.engine
.call_fn_raw(
None,
&mut EngineState::new(&Default::default()),
fn_name,
(hash_fn, 0),
args,
true,
None,
pos,
0,
)
.map(|(v, _)| Some(v))
.or_else(|_| Ok(None))
}
/// Optimize a statement.
@ -546,10 +553,10 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
&& state.optimization_level == OptimizationLevel::Full // full optimizations
&& x.3.iter().all(|expr| expr.is_constant()) // all arguments are constants
=> {
let ((name, pos), _, _, args, def_value) = x.as_mut();
let ((name, native_only, pos), _, _, args, def_value) = x.as_mut();
// First search in script-defined functions (can override built-in)
if state.fn_lib.iter().find(|(id, len)| *id == name && *len == args.len()).is_some() {
if !*native_only && state.fn_lib.iter().find(|(id, len)| *id == name && *len == args.len()).is_some() {
// A script-defined function overrides the built-in function - do not make the call
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
return Expr::FnCall(x);
@ -566,7 +573,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
""
};
call_fn(&state.engine.packages, &state.engine.global_module, name, call_args.as_mut(), *pos).ok()
call_fn(&state, name, call_args.as_mut(), *pos).ok()
.and_then(|result|
result.or_else(|| {
if !arg_for_type_of.is_empty() {

View File

@ -20,7 +20,7 @@ use crate::stdlib::{
};
// Checked add
fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> {
pub(crate) fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> {
x.checked_add(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Addition overflow: {} + {}", x, y),
@ -29,7 +29,7 @@ fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> {
})
}
// Checked subtract
fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> {
pub(crate) fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> {
x.checked_sub(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Subtraction underflow: {} - {}", x, y),
@ -38,7 +38,7 @@ fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> {
})
}
// Checked multiply
fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> {
pub(crate) fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> {
x.checked_mul(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Multiplication overflow: {} * {}", x, y),
@ -47,7 +47,7 @@ fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> {
})
}
// Checked divide
fn div<T>(x: T, y: T) -> FuncReturn<T>
pub(crate) fn div<T>(x: T, y: T) -> FuncReturn<T>
where
T: Display + CheckedDiv + PartialEq + Zero,
{
@ -67,7 +67,7 @@ where
})
}
// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX
fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> {
pub(crate) fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> {
x.checked_neg().ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Negation overflow: -{}", x),
@ -76,7 +76,7 @@ fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> {
})
}
// Checked absolute
fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> FuncReturn<T> {
pub(crate) fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> FuncReturn<T> {
// FIX - We don't use Signed::abs() here because, contrary to documentation, it panics
// when the number is ::MIN instead of returning ::MIN itself.
if x >= <T as Zero>::zero() {
@ -133,7 +133,7 @@ fn binary_xor<T: BitXor>(x: T, y: T) -> FuncReturn<<T as BitXor>::Output> {
Ok(x ^ y)
}
// Checked left-shift
fn shl<T: Display + CheckedShl>(x: T, y: INT) -> FuncReturn<T> {
pub(crate) fn shl<T: Display + CheckedShl>(x: T, y: INT) -> FuncReturn<T> {
// Cannot shift by a negative number of bits
if y < 0 {
return Err(Box::new(EvalAltResult::ErrorArithmetic(
@ -150,7 +150,7 @@ fn shl<T: Display + CheckedShl>(x: T, y: INT) -> FuncReturn<T> {
})
}
// Checked right-shift
fn shr<T: Display + CheckedShr>(x: T, y: INT) -> FuncReturn<T> {
pub(crate) fn shr<T: Display + CheckedShr>(x: T, y: INT) -> FuncReturn<T> {
// Cannot shift by a negative number of bits
if y < 0 {
return Err(Box::new(EvalAltResult::ErrorArithmetic(
@ -167,15 +167,15 @@ fn shr<T: Display + CheckedShr>(x: T, y: INT) -> FuncReturn<T> {
})
}
// Unchecked left-shift - may panic if shifting by a negative number of bits
fn shl_u<T: Shl<T>>(x: T, y: T) -> FuncReturn<<T as Shl<T>>::Output> {
pub(crate) fn shl_u<T: Shl<T>>(x: T, y: T) -> FuncReturn<<T as Shl<T>>::Output> {
Ok(x.shl(y))
}
// Unchecked right-shift - may panic if shifting by a negative number of bits
fn shr_u<T: Shr<T>>(x: T, y: T) -> FuncReturn<<T as Shr<T>>::Output> {
pub(crate) fn shr_u<T: Shr<T>>(x: T, y: T) -> FuncReturn<<T as Shr<T>>::Output> {
Ok(x.shr(y))
}
// Checked modulo
fn modulo<T: Display + CheckedRem>(x: T, y: T) -> FuncReturn<T> {
pub(crate) fn modulo<T: Display + CheckedRem>(x: T, y: T) -> FuncReturn<T> {
x.checked_rem(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Modulo division by zero or overflow: {} % {}", x, y),
@ -188,7 +188,7 @@ fn modulo_u<T: Rem>(x: T, y: T) -> FuncReturn<<T as Rem>::Output> {
Ok(x % y)
}
// Checked power
fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
pub(crate) fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
#[cfg(not(feature = "only_i32"))]
{
if y > (u32::MAX as INT) {
@ -229,17 +229,17 @@ fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
}
}
// Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX)
fn pow_i_i_u(x: INT, y: INT) -> FuncReturn<INT> {
pub(crate) fn pow_i_i_u(x: INT, y: INT) -> FuncReturn<INT> {
Ok(x.pow(y as u32))
}
// Floating-point power - always well-defined
#[cfg(not(feature = "no_float"))]
fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn<FLOAT> {
pub(crate) fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn<FLOAT> {
Ok(x.powf(y))
}
// Checked power
#[cfg(not(feature = "no_float"))]
fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
pub(crate) fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
// Raise to power that is larger than an i32
if y > (i32::MAX as INT) {
return Err(Box::new(EvalAltResult::ErrorArithmetic(
@ -253,7 +253,7 @@ fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
// Unchecked power - may be incorrect if the power index is too high (> i32::MAX)
#[cfg(feature = "unchecked")]
#[cfg(not(feature = "no_float"))]
fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
pub(crate) fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
Ok(x.powi(y as i32))
}
@ -272,97 +272,118 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
// Checked basic arithmetic
#[cfg(not(feature = "unchecked"))]
{
reg_op!(lib, "+", add, INT);
reg_op!(lib, "-", sub, INT);
reg_op!(lib, "*", mul, INT);
reg_op!(lib, "/", div, INT);
// reg_op!(lib, "+", add, INT);
// reg_op!(lib, "-", sub, INT);
// reg_op!(lib, "*", mul, INT);
// reg_op!(lib, "/", div, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_op!(lib, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "/", div, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "/", div, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "+", add, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "*", mul, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "/", div, i8, u8, i16, u16, i32, u32, u64, i128, u128);
}
}
// Unchecked basic arithmetic
#[cfg(feature = "unchecked")]
{
reg_op!(lib, "+", add_u, INT);
reg_op!(lib, "-", sub_u, INT);
reg_op!(lib, "*", mul_u, INT);
reg_op!(lib, "/", div_u, INT);
// reg_op!(lib, "+", add_u, INT);
// reg_op!(lib, "-", sub_u, INT);
// reg_op!(lib, "*", mul_u, INT);
// reg_op!(lib, "/", div_u, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "*", mul_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
}
}
// Basic arithmetic for floating-point - no need to check
#[cfg(not(feature = "no_float"))]
{
reg_op!(lib, "+", add_u, f32, f64);
reg_op!(lib, "-", sub_u, f32, f64);
reg_op!(lib, "*", mul_u, f32, f64);
reg_op!(lib, "/", div_u, f32, f64);
// reg_op!(lib, "+", add_u, f32, f64);
// reg_op!(lib, "-", sub_u, f32, f64);
// reg_op!(lib, "*", mul_u, f32, f64);
// reg_op!(lib, "/", div_u, f32, f64);
reg_op!(lib, "+", add_u, f32);
reg_op!(lib, "-", sub_u, f32);
reg_op!(lib, "*", mul_u, f32);
reg_op!(lib, "/", div_u, f32);
}
// Bit operations
reg_op!(lib, "|", binary_or, INT);
reg_op!(lib, "&", binary_and, INT);
reg_op!(lib, "^", binary_xor, INT);
// reg_op!(lib, "|", binary_or, INT);
// reg_op!(lib, "&", binary_and, INT);
// reg_op!(lib, "^", binary_xor, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, u32, u64, i128, u128);
}
// Checked bit shifts
#[cfg(not(feature = "unchecked"))]
{
reg_op!(lib, "<<", shl, INT);
reg_op!(lib, ">>", shr, INT);
reg_op!(lib, "%", modulo, INT);
// reg_op!(lib, "<<", shl, INT);
// reg_op!(lib, ">>", shr, INT);
// reg_op!(lib, "%", modulo, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_op!(lib, "<<", shl, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "<<", shl, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "<<", shl, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, u32, u64, i128, u128);
}
}
// Unchecked bit shifts
#[cfg(feature = "unchecked")]
{
reg_op!(lib, "<<", shl_u, INT, INT);
reg_op!(lib, ">>", shr_u, INT, INT);
reg_op!(lib, "%", modulo_u, INT);
// reg_op!(lib, "<<", shl_u, INT, INT);
// reg_op!(lib, ">>", shr_u, INT, INT);
// reg_op!(lib, "%", modulo_u, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_op!(lib, "<<", shl_u, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "<<", shl_u, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "<<", shl_u, i64, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
}
}
// Checked power
#[cfg(not(feature = "unchecked"))]
{
lib.set_fn_2("~", pow_i_i);
// lib.set_fn_2("~", pow_i_i);
#[cfg(not(feature = "no_float"))]
lib.set_fn_2("~", pow_f_i);
@ -371,7 +392,7 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
// Unchecked power
#[cfg(feature = "unchecked")]
{
lib.set_fn_2("~", pow_i_i_u);
// lib.set_fn_2("~", pow_i_i_u);
#[cfg(not(feature = "no_float"))]
lib.set_fn_2("~", pow_f_i_u);
@ -380,8 +401,9 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
// Floating-point modulo and power
#[cfg(not(feature = "no_float"))]
{
reg_op!(lib, "%", modulo_u, f32, f64);
lib.set_fn_2("~", pow_f_f);
// reg_op!(lib, "%", modulo_u, f32, f64);
reg_op!(lib, "%", modulo_u, f32);
// lib.set_fn_2("~", pow_f_f);
}
// Checked unary

View File

@ -42,40 +42,52 @@ macro_rules! reg_op {
}
def_package!(crate:LogicPackage:"Logical operators.", lib, {
reg_op!(lib, "<", lt, INT, char);
reg_op!(lib, "<=", lte, INT, char);
reg_op!(lib, ">", gt, INT, char);
reg_op!(lib, ">=", gte, INT, char);
reg_op!(lib, "==", eq, INT, char, bool, ());
reg_op!(lib, "!=", ne, INT, char, bool, ());
// reg_op!(lib, "<", lt, INT, char);
// reg_op!(lib, "<=", lte, INT, char);
// reg_op!(lib, ">", gt, INT, char);
// reg_op!(lib, ">=", gte, INT, char);
// reg_op!(lib, "==", eq, INT, char, bool, ());
// reg_op!(lib, "!=", ne, INT, char, bool, ());
// Special versions for strings - at least avoid copying the first string
lib.set_fn_2_mut("<", |x: &mut String, y: String| Ok(*x < y));
lib.set_fn_2_mut("<=", |x: &mut String, y: String| Ok(*x <= y));
lib.set_fn_2_mut(">", |x: &mut String, y: String| Ok(*x > y));
lib.set_fn_2_mut(">=", |x: &mut String, y: String| Ok(*x >= y));
lib.set_fn_2_mut("==", |x: &mut String, y: String| Ok(*x == y));
lib.set_fn_2_mut("!=", |x: &mut String, y: String| Ok(*x != y));
// lib.set_fn_2_mut("<", |x: &mut String, y: String| Ok(*x < y));
// lib.set_fn_2_mut("<=", |x: &mut String, y: String| Ok(*x <= y));
// lib.set_fn_2_mut(">", |x: &mut String, y: String| Ok(*x > y));
// lib.set_fn_2_mut(">=", |x: &mut String, y: String| Ok(*x >= y));
// lib.set_fn_2_mut("==", |x: &mut String, y: String| Ok(*x == y));
// lib.set_fn_2_mut("!=", |x: &mut String, y: String| Ok(*x != y));
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, ">=", gte, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, u32, u64, i128, u128);
}
#[cfg(not(feature = "no_float"))]
{
reg_op!(lib, "<", lt, f32, f64);
reg_op!(lib, "<=", lte, f32, f64);
reg_op!(lib, ">", gt, f32, f64);
reg_op!(lib, ">=", gte, f32, f64);
reg_op!(lib, "==", eq, f32, f64);
reg_op!(lib, "!=", ne, f32, f64);
// reg_op!(lib, "<", lt, f32, f64);
// reg_op!(lib, "<=", lte, f32, f64);
// reg_op!(lib, ">", gt, f32, f64);
// reg_op!(lib, ">=", gte, f32, f64);
// reg_op!(lib, "==", eq, f32, f64);
// reg_op!(lib, "!=", ne, f32, f64);
reg_op!(lib, "<", lt, f32);
reg_op!(lib, "<=", lte, f32);
reg_op!(lib, ">", gt, f32);
reg_op!(lib, ">=", gte, f32);
reg_op!(lib, "==", eq, f32);
reg_op!(lib, "!=", ne, f32);
}
// `&&` and `||` are treated specially as they short-circuit.
@ -83,7 +95,7 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, {
//reg_op!(lib, "||", or, bool);
//reg_op!(lib, "&&", and, bool);
lib.set_fn_2("|", or);
lib.set_fn_2("&", and);
// lib.set_fn_2("|", or);
// lib.set_fn_2("&", and);
lib.set_fn_1("!", not);
});

View File

@ -6,7 +6,7 @@ use crate::utils::StaticVec;
use crate::stdlib::{any::TypeId, boxed::Box, collections::HashMap, rc::Rc, sync::Arc, vec::Vec};
mod arithmetic;
pub(crate) mod arithmetic;
mod array_basic;
mod eval;
mod iter_basic;

View File

@ -389,12 +389,12 @@ pub enum Expr {
Property(Box<((String, String, String), Position)>),
/// { stmt }
Stmt(Box<(Stmt, Position)>),
/// func(expr, ... ) - ((function name, position), optional modules, hash, arguments, optional default value)
/// func(expr, ... ) - ((function name, native_only, position), optional modules, hash, arguments, optional default value)
/// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls
/// and the function names are predictable, so no need to allocate a new `String`.
FnCall(
Box<(
(Cow<'static, str>, Position),
(Cow<'static, str>, bool, Position),
Option<Box<ModuleRef>>,
u64,
StaticVec<Expr>,
@ -503,7 +503,7 @@ impl Expr {
Self::Property(x) => x.1,
Self::Stmt(x) => x.1,
Self::Variable(x) => (x.0).1,
Self::FnCall(x) => (x.0).1,
Self::FnCall(x) => (x.0).2,
Self::And(x) | Self::Or(x) | Self::In(x) => x.2,
@ -527,7 +527,7 @@ impl Expr {
Self::Variable(x) => (x.0).1 = new_pos,
Self::Property(x) => x.1 = new_pos,
Self::Stmt(x) => x.1 = new_pos,
Self::FnCall(x) => (x.0).1 = new_pos,
Self::FnCall(x) => (x.0).2 = new_pos,
Self::And(x) => x.2 = new_pos,
Self::Or(x) => x.2 = new_pos,
Self::In(x) => x.2 = new_pos,
@ -761,7 +761,7 @@ fn parse_call_expr<'a>(
let hash_fn_def = calc_fn_hash(empty(), &id, empty());
return Ok(Expr::FnCall(Box::new((
(id.into(), begin),
(id.into(), false, begin),
modules,
hash_fn_def,
args,
@ -802,7 +802,7 @@ fn parse_call_expr<'a>(
let hash_fn_def = calc_fn_hash(empty(), &id, args_iter);
return Ok(Expr::FnCall(Box::new((
(id.into(), begin),
(id.into(), false, begin),
modules,
hash_fn_def,
args,
@ -1347,7 +1347,7 @@ fn parse_unary<'a>(
args.push(expr);
Ok(Expr::FnCall(Box::new((
(op.into(), pos),
(op.into(), true, pos),
None,
hash,
args,
@ -1371,7 +1371,7 @@ fn parse_unary<'a>(
let hash = calc_fn_hash(empty(), op, 2, empty());
Ok(Expr::FnCall(Box::new((
(op.into(), pos),
(op.into(), true, pos),
None,
hash,
args,
@ -1473,7 +1473,7 @@ fn parse_op_assignment_stmt<'a>(
args.push(rhs);
let hash = calc_fn_hash(empty(), &op, args.len(), empty());
let rhs_expr = Expr::FnCall(Box::new(((op, pos), None, hash, args, None)));
let rhs_expr = Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None)));
make_assignment_stmt(state, lhs, rhs_expr, pos)
}
@ -1767,26 +1767,30 @@ fn parse_binary_op<'a>(
args.push(rhs);
root = match op_token {
Token::Plus => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))),
Token::Minus => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))),
Token::Multiply => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))),
Token::Divide => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))),
Token::Plus => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
Token::Minus => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
Token::Multiply => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
Token::Divide => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
Token::LeftShift => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))),
Token::RightShift => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))),
Token::Modulo => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))),
Token::PowerOf => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))),
Token::LeftShift => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
Token::RightShift => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
Token::Modulo => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
Token::PowerOf => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
// Comparison operators default to false when passed invalid operands
Token::EqualsTo => Expr::FnCall(Box::new(((op, pos), None, hash, args, cmp_def))),
Token::NotEqualsTo => Expr::FnCall(Box::new(((op, pos), None, hash, args, cmp_def))),
Token::LessThan => Expr::FnCall(Box::new(((op, pos), None, hash, args, cmp_def))),
Token::LessThanEqualsTo => {
Expr::FnCall(Box::new(((op, pos), None, hash, args, cmp_def)))
Token::EqualsTo => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, cmp_def))),
Token::NotEqualsTo => {
Expr::FnCall(Box::new(((op, true, pos), None, hash, args, cmp_def)))
}
Token::LessThan => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, cmp_def))),
Token::LessThanEqualsTo => {
Expr::FnCall(Box::new(((op, true, pos), None, hash, args, cmp_def)))
}
Token::GreaterThan => {
Expr::FnCall(Box::new(((op, true, pos), None, hash, args, cmp_def)))
}
Token::GreaterThan => Expr::FnCall(Box::new(((op, pos), None, hash, args, cmp_def))),
Token::GreaterThanEqualsTo => {
Expr::FnCall(Box::new(((op, pos), None, hash, args, cmp_def)))
Expr::FnCall(Box::new(((op, true, pos), None, hash, args, cmp_def)))
}
Token::Or => {
@ -1799,9 +1803,9 @@ fn parse_binary_op<'a>(
let current_lhs = args.pop();
Expr::And(Box::new((current_lhs, rhs, pos)))
}
Token::Ampersand => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))),
Token::Pipe => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))),
Token::XOr => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))),
Token::Ampersand => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
Token::Pipe => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
Token::XOr => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
Token::In => {
let rhs = args.pop();
@ -1817,7 +1821,7 @@ fn parse_binary_op<'a>(
match &mut rhs {
// current_lhs.rhs(...) - method call
Expr::FnCall(x) => {
let ((id, _), _, hash, args, _) = x.as_mut();
let ((id, _, _), _, hash, args, _) = x.as_mut();
// Recalculate function call hash because there is an additional argument
*hash = calc_fn_hash(empty(), id, args.len() + 1, empty());
}