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.
|
* 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
|
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::engine::{KEYWORD_EVAL, OP_EQUALS};
|
||||||
use crate::func::StraightHashMap;
|
use crate::func::StraightHashMap;
|
||||||
use crate::tokenizer::Token;
|
use crate::tokenizer::Token;
|
||||||
|
use crate::types::dynamic::Union;
|
||||||
use crate::types::Span;
|
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")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
use std::{
|
use std::{
|
||||||
@ -257,7 +258,7 @@ impl IntoIterator for RangeCase {
|
|||||||
type Item = INT;
|
type Item = INT;
|
||||||
type IntoIter = Box<dyn Iterator<Item = Self::Item>>;
|
type IntoIter = Box<dyn Iterator<Item = Self::Item>>;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
match self {
|
match self {
|
||||||
@ -269,7 +270,7 @@ impl IntoIterator for RangeCase {
|
|||||||
|
|
||||||
impl RangeCase {
|
impl RangeCase {
|
||||||
/// Returns `true` if the range contains no items.
|
/// Returns `true` if the range contains no items.
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
@ -278,7 +279,7 @@ impl RangeCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Size of the range.
|
/// Size of the range.
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn len(&self) -> INT {
|
pub fn len(&self) -> INT {
|
||||||
match self {
|
match self {
|
||||||
@ -288,15 +289,56 @@ impl RangeCase {
|
|||||||
Self::InclusiveInt(r, ..) => *r.end() - *r.start() + 1,
|
Self::InclusiveInt(r, ..) => *r.end() - *r.start() + 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Is the specified number within this range?
|
/// Is the specified value within this range?
|
||||||
#[inline(always)]
|
#[inline]
|
||||||
#[must_use]
|
#[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 {
|
match self {
|
||||||
Self::ExclusiveInt(r, ..) => r.contains(&n),
|
Self::ExclusiveInt(r, ..) => r.contains(&n),
|
||||||
Self::InclusiveInt(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?
|
/// Is the specified range inclusive?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
use super::{Caches, EvalContext, GlobalRuntimeState, Target};
|
use super::{Caches, EvalContext, GlobalRuntimeState, Target};
|
||||||
use crate::api::events::VarDefInfo;
|
use crate::api::events::VarDefInfo;
|
||||||
use crate::ast::{
|
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::func::{get_builtin_op_assignment_fn, get_hasher};
|
||||||
use crate::types::dynamic::{AccessMode, Union};
|
use crate::types::dynamic::{AccessMode, Union};
|
||||||
@ -359,15 +360,13 @@ impl Engine {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if value.is_int() && !ranges.is_empty() {
|
} else if !ranges.is_empty() {
|
||||||
// Then check integer ranges
|
// 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 cond_result = match condition {
|
||||||
let block = &expressions[r.index()];
|
Expr::BoolConstant(b, ..) => *b,
|
||||||
|
|
||||||
let cond_result = match block.condition {
|
|
||||||
Expr::BoolConstant(b, ..) => b,
|
|
||||||
ref c => self
|
ref c => self
|
||||||
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), c)?
|
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), c)?
|
||||||
.as_bool()
|
.as_bool()
|
||||||
@ -377,7 +376,7 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if cond_result {
|
if cond_result {
|
||||||
result = Some(&block.expr);
|
result = Some(expr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ impl Hasher for StraightHasher {
|
|||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
#[cold]
|
#[cold]
|
||||||
|
#[inline(never)]
|
||||||
fn write(&mut self, _bytes: &[u8]) {
|
fn write(&mut self, _bytes: &[u8]) {
|
||||||
panic!("StraightHasher can only hash u64 values");
|
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
|
// Then check ranges
|
||||||
if value.is_int() && !ranges.is_empty() {
|
if !ranges.is_empty() {
|
||||||
let value = value.as_int().unwrap();
|
|
||||||
|
|
||||||
// Only one range or all ranges without conditions
|
// Only one range or all ranges without conditions
|
||||||
if ranges.len() == 1
|
if ranges.len() == 1
|
||||||
|| ranges
|
|| ranges
|
||||||
.iter()
|
.iter()
|
||||||
.all(|r| expressions[r.index()].is_always_true())
|
.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()];
|
let range_block = &mut expressions[r.index()];
|
||||||
|
|
||||||
if range_block.is_always_true() {
|
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();
|
let old_ranges_len = ranges.len();
|
||||||
|
|
||||||
ranges.retain(|r| r.contains(value));
|
ranges.retain(|r| r.contains(&value));
|
||||||
|
|
||||||
if ranges.len() != old_ranges_len {
|
if ranges.len() != old_ranges_len {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
|
@ -19,7 +19,7 @@ use crate::types::StringsInterner;
|
|||||||
use crate::{
|
use crate::{
|
||||||
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ExclusiveRange, FnArgsVec,
|
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ExclusiveRange, FnArgsVec,
|
||||||
Identifier, ImmutableString, InclusiveRange, LexError, OptimizationLevel, ParseError, Position,
|
Identifier, ImmutableString, InclusiveRange, LexError, OptimizationLevel, ParseError, Position,
|
||||||
Scope, Shared, SmartString, StaticVec, AST, INT, PERR,
|
Scope, Shared, SmartString, StaticVec, AST, PERR,
|
||||||
};
|
};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
@ -42,9 +42,6 @@ const SCOPE_SEARCH_BARRIER_MARKER: &str = "$ BARRIER $";
|
|||||||
/// The message: `TokenStream` never ends
|
/// The message: `TokenStream` never ends
|
||||||
const NEVER_ENDS: &str = "`Token`";
|
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.
|
/// _(internals)_ A type that encapsulates the current state of the parser.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
pub struct ParseState<'e, 's> {
|
pub struct ParseState<'e, 's> {
|
||||||
@ -1216,7 +1213,6 @@ impl Engine {
|
|||||||
let stmt_block: StmtBlock = stmt.into();
|
let stmt_block: StmtBlock = stmt.into();
|
||||||
(Expr::Stmt(stmt_block.into()), need_comma)
|
(Expr::Stmt(stmt_block.into()), need_comma)
|
||||||
};
|
};
|
||||||
let has_condition = !matches!(condition, Expr::BoolConstant(true, ..));
|
|
||||||
|
|
||||||
expressions.push((condition, action_expr).into());
|
expressions.push((condition, action_expr).into());
|
||||||
let index = expressions.len() - 1;
|
let index = expressions.len() - 1;
|
||||||
@ -1240,24 +1236,10 @@ impl Engine {
|
|||||||
|
|
||||||
if let Some(mut r) = range_value {
|
if let Some(mut r) = range_value {
|
||||||
if !r.is_empty() {
|
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
|
// Other range
|
||||||
r.set_index(index);
|
r.set_index(index);
|
||||||
ranges.push(r);
|
ranges.push(r);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,8 +50,8 @@ fn test_expressions() -> Result<(), Box<EvalAltResult>> {
|
|||||||
"
|
"
|
||||||
switch x {
|
switch x {
|
||||||
0 => 1,
|
0 => 1,
|
||||||
1..10 => 123,
|
|
||||||
10 => 42,
|
10 => 42,
|
||||||
|
1..10 => 123,
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
)?,
|
)?,
|
||||||
@ -63,11 +63,11 @@ fn test_expressions() -> Result<(), Box<EvalAltResult>> {
|
|||||||
"
|
"
|
||||||
switch x {
|
switch x {
|
||||||
0 => 1,
|
0 => 1,
|
||||||
|
10 => 42,
|
||||||
1..10 => {
|
1..10 => {
|
||||||
let y = 123;
|
let y = 123;
|
||||||
y
|
y
|
||||||
}
|
}
|
||||||
10 => 42,
|
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user