General cleanup.

This commit is contained in:
Stephen Chung 2020-03-11 11:03:18 +08:00
parent 708c285a0a
commit 880bce1114
9 changed files with 124 additions and 116 deletions

View File

@ -18,6 +18,7 @@ include = [
num-traits = "*" num-traits = "*"
[features] [features]
#default = ["no_index", "no_float", "only_i32", "no_stdlib", "unchecked"]
default = [] default = []
debug_msgs = [] debug_msgs = []
unchecked = [] unchecked = []

View File

@ -2,45 +2,23 @@
//! _standard library_ of utility functions. //! _standard library_ of utility functions.
use crate::any::Any; use crate::any::Any;
use crate::engine::Engine;
use crate::fn_register::RegisterFn;
use crate::parser::INT;
#[cfg(not(feature = "unchecked"))]
use crate::{parser::Position, result::EvalAltResult, RegisterResultFn};
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::engine::Array; use crate::engine::Array;
use crate::engine::Engine;
#[cfg(not(feature = "no_float"))] use crate::fn_register::{RegisterFn, RegisterResultFn};
use crate::parser::{Position, INT};
use crate::result::EvalAltResult;
use crate::FLOAT; use crate::FLOAT;
use num_traits::{
identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl,
CheckedShr, CheckedSub,
};
use std::{ use std::{
fmt::{Debug, Display}, fmt::{Debug, Display},
ops::{BitAnd, BitOr, BitXor, Range}, ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub},
}; {i32, i64, u32},
#[cfg(feature = "unchecked")]
use std::ops::{Shl, Shr};
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_float"))]
use std::{i32, i64};
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "only_i32"))]
use std::u32;
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
use std::ops::{Add, Div, Mul, Neg, Rem, Sub};
#[cfg(not(feature = "unchecked"))]
use {
num_traits::{
CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr,
CheckedSub,
},
std::convert::TryFrom,
}; };
macro_rules! reg_op { macro_rules! reg_op {
@ -162,12 +140,9 @@ impl Engine<'_> {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn div<T>(x: T, y: T) -> Result<T, EvalAltResult> fn div<T>(x: T, y: T) -> Result<T, EvalAltResult>
where where
T: Display + CheckedDiv + PartialEq + TryFrom<i8>, T: Display + CheckedDiv + PartialEq + Zero,
{ {
if y == <T as TryFrom<i8>>::try_from(0) if y == T::zero() {
.map_err(|_| ())
.expect("zero should always succeed")
{
return Err(EvalAltResult::ErrorArithmetic( return Err(EvalAltResult::ErrorArithmetic(
format!("Division by zero: {} / {}", x, y), format!("Division by zero: {} / {}", x, y),
Position::none(), Position::none(),
@ -191,8 +166,10 @@ impl Engine<'_> {
}) })
} }
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn abs<T: Display + CheckedNeg + PartialOrd + From<i8>>(x: T) -> Result<T, EvalAltResult> { fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> Result<T, EvalAltResult> {
if x >= 0.into() { // 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() {
Ok(x) Ok(x)
} else { } else {
x.checked_neg().ok_or_else(|| { x.checked_neg().ok_or_else(|| {
@ -224,14 +201,15 @@ impl Engine<'_> {
-x -x
} }
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn abs_u<T: Neg + PartialOrd + From<i8>>(x: T) -> T fn abs_u<T>(x: T) -> <T as Neg>::Output
where where
<T as Neg>::Output: Into<T>, T: Neg + PartialOrd + Default + Into<<T as Neg>::Output>,
{ {
if x < 0.into() { // Numbers should default to zero
(-x).into() if x < Default::default() {
-x
} else { } else {
x x.into()
} }
} }
fn lt<T: PartialOrd>(x: T, y: T) -> bool { fn lt<T: PartialOrd>(x: T, y: T) -> bool {

View File

@ -4,8 +4,6 @@ use crate::any::{Any, AnyExt, Dynamic, Variant};
use crate::parser::{Expr, FnDef, Position, Stmt}; use crate::parser::{Expr, FnDef, Position, Stmt};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::Scope; use crate::scope::Scope;
#[cfg(not(feature = "no_index"))]
use crate::INT; use crate::INT;
use std::{ use std::{
@ -788,6 +786,9 @@ impl Engine<'_> {
.eval_index_expr(scope, lhs, idx_expr, *idx_pos) .eval_index_expr(scope, lhs, idx_expr, *idx_pos)
.map(|(_, _, _, x)| x), .map(|(_, _, _, x)| x),
#[cfg(feature = "no_index")]
Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"),
// Statement block // Statement block
Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt), Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt),
@ -855,6 +856,8 @@ impl Engine<'_> {
Ok(Box::new(arr)) Ok(Box::new(arr))
} }
#[cfg(feature = "no_index")]
Expr::Array(_, _) => panic!("encountered an array during no_index!"),
Expr::FunctionCall(fn_name, args, def_val, pos) => { Expr::FunctionCall(fn_name, args, def_val, pos) => {
let mut args = args let mut args = args

View File

@ -120,20 +120,20 @@ macro_rules! def_register {
const NUM_ARGS: usize = count_args!($($par)*); const NUM_ARGS: usize = count_args!($($par)*);
if args.len() != NUM_ARGS { if args.len() != NUM_ARGS {
Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)) return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos));
} else {
#[allow(unused_variables, unused_mut)]
let mut drain = args.drain(..);
$(
// Downcast every element, return in case of a type mismatch
let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap();
)*
// Call the user-supplied function using ($clone) to
// potentially clone the value, otherwise pass the reference.
let r = f($(($clone)($par)),*);
Ok(Box::new(r) as Dynamic)
} }
#[allow(unused_variables, unused_mut)]
let mut drain = args.drain(..);
$(
// Downcast every element, return in case of a type mismatch
let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap();
)*
// Call the user-supplied function using ($clone) to
// potentially clone the value, otherwise pass the reference.
let r = f($(($clone)($par)),*);
Ok(Box::new(r) as Dynamic)
}; };
self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
} }
@ -152,19 +152,19 @@ macro_rules! def_register {
const NUM_ARGS: usize = count_args!($($par)*); const NUM_ARGS: usize = count_args!($($par)*);
if args.len() != NUM_ARGS { if args.len() != NUM_ARGS {
Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)) return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos));
} else {
#[allow(unused_variables, unused_mut)]
let mut drain = args.drain(..);
$(
// Downcast every element, return in case of a type mismatch
let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap();
)*
// Call the user-supplied function using ($clone) to
// potentially clone the value, otherwise pass the reference.
Ok(f($(($clone)($par)),*))
} }
#[allow(unused_variables, unused_mut)]
let mut drain = args.drain(..);
$(
// Downcast every element, return in case of a type mismatch
let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap();
)*
// Call the user-supplied function using ($clone) to
// potentially clone the value, otherwise pass the reference.
Ok(f($(($clone)($par)),*))
}; };
self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
} }
@ -184,23 +184,23 @@ macro_rules! def_register {
const NUM_ARGS: usize = count_args!($($par)*); const NUM_ARGS: usize = count_args!($($par)*);
if args.len() != NUM_ARGS { if args.len() != NUM_ARGS {
Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)) return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos));
} else { }
#[allow(unused_variables, unused_mut)]
let mut drain = args.drain(..);
$(
// Downcast every element, return in case of a type mismatch
let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap();
)*
// Call the user-supplied function using ($clone) to #[allow(unused_variables, unused_mut)]
// potentially clone the value, otherwise pass the reference. let mut drain = args.drain(..);
match f($(($clone)($par)),*) { $(
Ok(r) => Ok(Box::new(r) as Dynamic), // Downcast every element, return in case of a type mismatch
Err(mut err) => { let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap();
err.set_position(pos); )*
Err(err)
} // Call the user-supplied function using ($clone) to
// potentially clone the value, otherwise pass the reference.
match f($(($clone)($par)),*) {
Ok(r) => Ok(Box::new(r) as Dynamic),
Err(mut err) => {
err.set_position(pos);
Err(err)
} }
} }
}; };

View File

@ -141,6 +141,7 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
Box::new(optimize_expr(*rhs, changed)), Box::new(optimize_expr(*rhs, changed)),
pos, pos,
), ),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) { Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) {
(Expr::Array(mut items, _), Expr::IntegerConstant(i, _)) (Expr::Array(mut items, _), Expr::IntegerConstant(i, _))
@ -158,6 +159,9 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
pos, pos,
), ),
}, },
#[cfg(feature = "no_index")]
Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Array(items, pos) => { Expr::Array(items, pos) => {
let original_len = items.len(); let original_len = items.len();
@ -172,6 +176,9 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
Expr::Array(items, pos) Expr::Array(items, pos)
} }
#[cfg(feature = "no_index")]
Expr::Array(_, _) => panic!("encountered an array during no_index!"),
Expr::And(lhs, rhs) => match (*lhs, *rhs) { Expr::And(lhs, rhs) => match (*lhs, *rhs) {
(Expr::True(_), rhs) => { (Expr::True(_), rhs) => {
*changed = true; *changed = true;
@ -208,7 +215,6 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
Box::new(optimize_expr(rhs, changed)), Box::new(optimize_expr(rhs, changed)),
), ),
}, },
Expr::FunctionCall(id, args, def_value, pos) => { Expr::FunctionCall(id, args, def_value, pos) => {
let original_len = args.len(); let original_len = args.len();

View File

@ -3,13 +3,18 @@
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::error::{LexError, ParseError, ParseErrorType}; use crate::error::{LexError, ParseError, ParseErrorType};
use crate::optimize::optimize; use crate::optimize::optimize;
use std::{borrow::Cow, char, fmt, iter::Peekable, str::Chars, str::FromStr, usize}; use std::{borrow::Cow, char, fmt, iter::Peekable, str::Chars, str::FromStr, usize};
/// The system integer type /// The system integer type.
///
/// If the `only_i32` feature is enabled, this will be `i32` instead.
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
pub type INT = i64; pub type INT = i64;
/// The system integer type /// The system integer type
///
/// If the `only_i32` feature is not enabled, this will be `i64` instead.
#[cfg(feature = "only_i32")] #[cfg(feature = "only_i32")]
pub type INT = i32; pub type INT = i32;
@ -179,9 +184,7 @@ pub enum Expr {
FunctionCall(String, Vec<Expr>, Option<Dynamic>, Position), FunctionCall(String, Vec<Expr>, Option<Dynamic>, Position),
Assignment(Box<Expr>, Box<Expr>, Position), Assignment(Box<Expr>, Box<Expr>, Position),
Dot(Box<Expr>, Box<Expr>, Position), Dot(Box<Expr>, Box<Expr>, Position),
#[cfg(not(feature = "no_index"))]
Index(Box<Expr>, Box<Expr>, Position), Index(Box<Expr>, Box<Expr>, Position),
#[cfg(not(feature = "no_index"))]
Array(Vec<Expr>, Position), Array(Vec<Expr>, Position),
And(Box<Expr>, Box<Expr>), And(Box<Expr>, Box<Expr>),
Or(Box<Expr>, Box<Expr>), Or(Box<Expr>, Box<Expr>),
@ -197,24 +200,21 @@ impl Expr {
| Expr::Identifier(_, pos) | Expr::Identifier(_, pos)
| Expr::CharConstant(_, pos) | Expr::CharConstant(_, pos)
| Expr::StringConstant(_, pos) | Expr::StringConstant(_, pos)
| Expr::FunctionCall(_, _, _, pos)
| Expr::Stmt(_, pos) | Expr::Stmt(_, pos)
| Expr::FunctionCall(_, _, _, pos)
| Expr::Array(_, pos)
| Expr::True(pos) | Expr::True(pos)
| Expr::False(pos) | Expr::False(pos)
| Expr::Unit(pos) => *pos, | Expr::Unit(pos) => *pos,
Expr::Assignment(e, _, _) | Expr::Dot(e, _, _) | Expr::And(e, _) | Expr::Or(e, _) => { Expr::Assignment(e, _, _)
e.position() | Expr::Dot(e, _, _)
} | Expr::Index(e, _, _)
| Expr::And(e, _)
| Expr::Or(e, _) => e.position(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Expr::FloatConstant(_, pos) => *pos, Expr::FloatConstant(_, pos) => *pos,
#[cfg(not(feature = "no_index"))]
Expr::Index(e, _, _) => e.position(),
#[cfg(not(feature = "no_index"))]
Expr::Array(_, pos) => *pos,
} }
} }

View File

@ -3,6 +3,7 @@
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::error::ParseError; use crate::error::ParseError;
use crate::parser::{Position, INT}; use crate::parser::{Position, INT};
use std::{error::Error, fmt}; use std::{error::Error, fmt};
/// Evaluation result. /// Evaluation result.
@ -75,12 +76,12 @@ impl Error for EvalAltResult {
Self::ErrorArrayBounds(_, index, _) if *index < 0 => { Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
"Array access expects non-negative index" "Array access expects non-negative index"
} }
Self::ErrorArrayBounds(max, _, _) if *max == 0 => "Access of empty array", Self::ErrorArrayBounds(0, _, _) => "Access of empty array",
Self::ErrorArrayBounds(_, _, _) => "Array index out of bounds", Self::ErrorArrayBounds(_, _, _) => "Array index out of bounds",
Self::ErrorStringBounds(_, index, _) if *index < 0 => { Self::ErrorStringBounds(_, index, _) if *index < 0 => {
"Indexing a string expects a non-negative index" "Indexing a string expects a non-negative index"
} }
Self::ErrorStringBounds(max, _, _) if *max == 0 => "Indexing of empty string", Self::ErrorStringBounds(0, _, _) => "Indexing of empty string",
Self::ErrorStringBounds(_, _, _) => "String index out of bounds", Self::ErrorStringBounds(_, _, _) => "String index out of bounds",
Self::ErrorIfGuard(_) => "If guard expects boolean expression", Self::ErrorIfGuard(_) => "If guard expects boolean expression",
Self::ErrorFor(_) => "For loop expects array or range", Self::ErrorFor(_) => "For loop expects array or range",
@ -128,6 +129,16 @@ impl fmt::Display for EvalAltResult {
write!(f, "{} '{}': {}", desc, filename, err) write!(f, "{} '{}': {}", desc, filename, err)
} }
Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p), Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p),
Self::ErrorFunctionArgsMismatch(fun, 0, n, pos) => write!(
f,
"Function '{}' expects no argument but {} found ({})",
fun, n, pos
),
Self::ErrorFunctionArgsMismatch(fun, 1, n, pos) => write!(
f,
"Function '{}' expects one argument but {} found ({})",
fun, n, pos
),
Self::ErrorFunctionArgsMismatch(fun, need, n, pos) => write!( Self::ErrorFunctionArgsMismatch(fun, need, n, pos) => write!(
f, f,
"Function '{}' expects {} argument(s) but {} found ({})", "Function '{}' expects {} argument(s) but {} found ({})",
@ -142,26 +153,30 @@ impl fmt::Display for EvalAltResult {
Self::ErrorArrayBounds(_, index, pos) if *index < 0 => { Self::ErrorArrayBounds(_, index, pos) if *index < 0 => {
write!(f, "{}: {} < 0 ({})", desc, index, pos) write!(f, "{}: {} < 0 ({})", desc, index, pos)
} }
Self::ErrorArrayBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), Self::ErrorArrayBounds(0, _, pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorArrayBounds(1, index, pos) => write!(
f,
"Array index {} is out of bounds: only one element in the array ({})",
index, pos
),
Self::ErrorArrayBounds(max, index, pos) => write!( Self::ErrorArrayBounds(max, index, pos) => write!(
f, f,
"Array index {} is out of bounds: only {} element{} in the array ({})", "Array index {} is out of bounds: only {} elements in the array ({})",
index, index, max, pos
max,
if *max > 1 { "s" } else { "" },
pos
), ),
Self::ErrorStringBounds(_, index, pos) if *index < 0 => { Self::ErrorStringBounds(_, index, pos) if *index < 0 => {
write!(f, "{}: {} < 0 ({})", desc, index, pos) write!(f, "{}: {} < 0 ({})", desc, index, pos)
} }
Self::ErrorStringBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), Self::ErrorStringBounds(0, _, pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorStringBounds(1, index, pos) => write!(
f,
"String index {} is out of bounds: only one character in the string ({})",
index, pos
),
Self::ErrorStringBounds(max, index, pos) => write!( Self::ErrorStringBounds(max, index, pos) => write!(
f, f,
"String index {} is out of bounds: only {} character{} in the string ({})", "String index {} is out of bounds: only {} characters in the string ({})",
index, index, max, pos
max,
if *max > 1 { "s" } else { "" },
pos
), ),
} }
} }

View File

@ -1,6 +1,7 @@
//! Module that defines the `Scope` type representing a function call-stack scope. //! Module that defines the `Scope` type representing a function call-stack scope.
use crate::any::{Any, Dynamic}; use crate::any::{Any, Dynamic};
use std::borrow::Cow; use std::borrow::Cow;
/// A type containing information about current scope. /// A type containing information about current scope.

View File

@ -24,6 +24,10 @@ fn test_math() -> Result<(), EvalAltResult> {
{ {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
{ {
match engine.eval::<INT>("(-9223372036854775808).abs()") {
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
r => panic!("should return overflow error: {:?}", r),
}
match engine.eval::<INT>("9223372036854775807 + 1") { match engine.eval::<INT>("9223372036854775807 + 1") {
Err(EvalAltResult::ErrorArithmetic(_, _)) => (), Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
r => panic!("should return overflow error: {:?}", r), r => panic!("should return overflow error: {:?}", r),