Support switch range cases for floating-point values.
This commit is contained in:
parent
d0a47d7f66
commit
10089c5cb0
@ -17,6 +17,7 @@ Enhancements
|
||||
------------
|
||||
|
||||
* The functions `min` and `max` are added for numbers.
|
||||
* Range cases in `switch` statements now also match floating-point and decimal values. In order to support this, however, small numeric ranges cases are no longer unrolled.
|
||||
|
||||
|
||||
Version 1.12.0
|
||||
|
@ -4,8 +4,9 @@ use super::{ASTFlags, ASTNode, BinaryExpr, Expr, FnCallExpr, Ident};
|
||||
use crate::engine::{KEYWORD_EVAL, OP_EQUALS};
|
||||
use crate::func::StraightHashMap;
|
||||
use crate::tokenizer::Token;
|
||||
use crate::types::dynamic::Union;
|
||||
use crate::types::Span;
|
||||
use crate::{calc_fn_hash, Position, StaticVec, INT};
|
||||
use crate::{calc_fn_hash, Dynamic, Position, StaticVec, INT};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{
|
||||
@ -257,7 +258,7 @@ impl IntoIterator for RangeCase {
|
||||
type Item = INT;
|
||||
type IntoIter = Box<dyn Iterator<Item = Self::Item>>;
|
||||
|
||||
#[inline(always)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
match self {
|
||||
@ -269,7 +270,7 @@ impl IntoIterator for RangeCase {
|
||||
|
||||
impl RangeCase {
|
||||
/// Returns `true` if the range contains no items.
|
||||
#[inline(always)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
@ -278,7 +279,7 @@ impl RangeCase {
|
||||
}
|
||||
}
|
||||
/// Size of the range.
|
||||
#[inline(always)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn len(&self) -> INT {
|
||||
match self {
|
||||
@ -288,15 +289,56 @@ impl RangeCase {
|
||||
Self::InclusiveInt(r, ..) => *r.end() - *r.start() + 1,
|
||||
}
|
||||
}
|
||||
/// Is the specified number within this range?
|
||||
#[inline(always)]
|
||||
/// Is the specified value within this range?
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn contains(&self, n: INT) -> bool {
|
||||
pub fn contains(&self, value: &Dynamic) -> bool {
|
||||
match value {
|
||||
Dynamic(Union::Int(v, ..)) => self.contains_int(*v),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Dynamic(Union::Float(v, ..)) => self.contains_float(**v),
|
||||
#[cfg(feature = "decimal")]
|
||||
Dynamic(Union::Decimal(v, ..)) => self.contains_decimal(**v),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
/// Is the specified number within this range?
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn contains_int(&self, n: INT) -> bool {
|
||||
match self {
|
||||
Self::ExclusiveInt(r, ..) => r.contains(&n),
|
||||
Self::InclusiveInt(r, ..) => r.contains(&n),
|
||||
}
|
||||
}
|
||||
/// Is the specified floating-point number within this range?
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn contains_float(&self, n: crate::FLOAT) -> bool {
|
||||
use crate::FLOAT;
|
||||
|
||||
match self {
|
||||
Self::ExclusiveInt(r, ..) => ((r.start as FLOAT)..(r.end as FLOAT)).contains(&n),
|
||||
Self::InclusiveInt(r, ..) => ((*r.start() as FLOAT)..=(*r.end() as FLOAT)).contains(&n),
|
||||
}
|
||||
}
|
||||
/// Is the specified decimal number within this range?
|
||||
#[cfg(feature = "decimal")]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn contains_decimal(&self, n: rust_decimal::Decimal) -> bool {
|
||||
use rust_decimal::Decimal;
|
||||
|
||||
match self {
|
||||
Self::ExclusiveInt(r, ..) => {
|
||||
(Into::<Decimal>::into(r.start)..Into::<Decimal>::into(r.end)).contains(&n)
|
||||
}
|
||||
Self::InclusiveInt(r, ..) => {
|
||||
(Into::<Decimal>::into(*r.start())..=Into::<Decimal>::into(*r.end())).contains(&n)
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Is the specified range inclusive?
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
|
@ -3,7 +3,8 @@
|
||||
use super::{Caches, EvalContext, GlobalRuntimeState, Target};
|
||||
use crate::api::events::VarDefInfo;
|
||||
use crate::ast::{
|
||||
ASTFlags, BinaryExpr, Expr, FlowControl, OpAssignment, Stmt, SwitchCasesCollection,
|
||||
ASTFlags, BinaryExpr, ConditionalExpr, Expr, FlowControl, OpAssignment, Stmt,
|
||||
SwitchCasesCollection,
|
||||
};
|
||||
use crate::func::{get_builtin_op_assignment_fn, get_hasher};
|
||||
use crate::types::dynamic::{AccessMode, Union};
|
||||
@ -359,15 +360,13 @@ impl Engine {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if value.is_int() && !ranges.is_empty() {
|
||||
} else if !ranges.is_empty() {
|
||||
// Then check integer ranges
|
||||
let value = value.as_int().expect("`INT`");
|
||||
for r in ranges.iter().filter(|r| r.contains(&value)) {
|
||||
let ConditionalExpr { condition, expr } = &expressions[r.index()];
|
||||
|
||||
for r in ranges.iter().filter(|r| r.contains(value)) {
|
||||
let block = &expressions[r.index()];
|
||||
|
||||
let cond_result = match block.condition {
|
||||
Expr::BoolConstant(b, ..) => b,
|
||||
let cond_result = match condition {
|
||||
Expr::BoolConstant(b, ..) => *b,
|
||||
ref c => self
|
||||
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), c)?
|
||||
.as_bool()
|
||||
@ -377,7 +376,7 @@ impl Engine {
|
||||
};
|
||||
|
||||
if cond_result {
|
||||
result = Some(&block.expr);
|
||||
result = Some(expr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ impl Hasher for StraightHasher {
|
||||
self.0
|
||||
}
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn write(&mut self, _bytes: &[u8]) {
|
||||
panic!("StraightHasher can only hash u64 values");
|
||||
}
|
||||
|
@ -563,16 +563,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
}
|
||||
|
||||
// Then check ranges
|
||||
if value.is_int() && !ranges.is_empty() {
|
||||
let value = value.as_int().unwrap();
|
||||
|
||||
if !ranges.is_empty() {
|
||||
// Only one range or all ranges without conditions
|
||||
if ranges.len() == 1
|
||||
|| ranges
|
||||
.iter()
|
||||
.all(|r| expressions[r.index()].is_always_true())
|
||||
{
|
||||
if let Some(r) = ranges.iter().find(|r| r.contains(value)) {
|
||||
if let Some(r) = ranges.iter().find(|r| r.contains(&value)) {
|
||||
let range_block = &mut expressions[r.index()];
|
||||
|
||||
if range_block.is_always_true() {
|
||||
@ -619,7 +617,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
||||
|
||||
let old_ranges_len = ranges.len();
|
||||
|
||||
ranges.retain(|r| r.contains(value));
|
||||
ranges.retain(|r| r.contains(&value));
|
||||
|
||||
if ranges.len() != old_ranges_len {
|
||||
state.set_dirty();
|
||||
|
@ -19,7 +19,7 @@ use crate::types::StringsInterner;
|
||||
use crate::{
|
||||
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ExclusiveRange, FnArgsVec,
|
||||
Identifier, ImmutableString, InclusiveRange, LexError, OptimizationLevel, ParseError, Position,
|
||||
Scope, Shared, SmartString, StaticVec, AST, INT, PERR,
|
||||
Scope, Shared, SmartString, StaticVec, AST, PERR,
|
||||
};
|
||||
use bitflags::bitflags;
|
||||
#[cfg(feature = "no_std")]
|
||||
@ -42,9 +42,6 @@ const SCOPE_SEARCH_BARRIER_MARKER: &str = "$ BARRIER $";
|
||||
/// The message: `TokenStream` never ends
|
||||
const NEVER_ENDS: &str = "`Token`";
|
||||
|
||||
/// Unroll `switch` ranges no larger than this.
|
||||
const SMALL_SWITCH_RANGE: INT = 16;
|
||||
|
||||
/// _(internals)_ A type that encapsulates the current state of the parser.
|
||||
/// Exported under the `internals` feature only.
|
||||
pub struct ParseState<'e, 's> {
|
||||
@ -1216,7 +1213,6 @@ impl Engine {
|
||||
let stmt_block: StmtBlock = stmt.into();
|
||||
(Expr::Stmt(stmt_block.into()), need_comma)
|
||||
};
|
||||
let has_condition = !matches!(condition, Expr::BoolConstant(true, ..));
|
||||
|
||||
expressions.push((condition, action_expr).into());
|
||||
let index = expressions.len() - 1;
|
||||
@ -1240,24 +1236,10 @@ impl Engine {
|
||||
|
||||
if let Some(mut r) = range_value {
|
||||
if !r.is_empty() {
|
||||
// Do not unroll ranges if there are previous non-unrolled ranges
|
||||
if !has_condition && ranges.is_empty() && r.len() <= SMALL_SWITCH_RANGE
|
||||
{
|
||||
// Unroll small range
|
||||
r.into_iter().for_each(|n| {
|
||||
let hasher = &mut get_hasher();
|
||||
Dynamic::from_int(n).hash(hasher);
|
||||
cases
|
||||
.entry(hasher.finish())
|
||||
.and_modify(|cases| cases.push(index))
|
||||
.or_insert_with(|| [index].into());
|
||||
});
|
||||
} else {
|
||||
// Other range
|
||||
r.set_index(index);
|
||||
ranges.push(r);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -50,8 +50,8 @@ fn test_expressions() -> Result<(), Box<EvalAltResult>> {
|
||||
"
|
||||
switch x {
|
||||
0 => 1,
|
||||
1..10 => 123,
|
||||
10 => 42,
|
||||
1..10 => 123,
|
||||
}
|
||||
"
|
||||
)?,
|
||||
@ -63,11 +63,11 @@ fn test_expressions() -> Result<(), Box<EvalAltResult>> {
|
||||
"
|
||||
switch x {
|
||||
0 => 1,
|
||||
10 => 42,
|
||||
1..10 => {
|
||||
let y = 123;
|
||||
y
|
||||
}
|
||||
10 => 42,
|
||||
}
|
||||
"
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user