Merge pull request #350 from schungx/master
Add scientific notation to floating-point numbers.
This commit is contained in:
commit
a2e7e3fc4e
@ -20,6 +20,11 @@ Breaking changes
|
|||||||
* `Dynamic::into_shared` is no longer available under `no_closure`. It used to panic.
|
* `Dynamic::into_shared` is no longer available under `no_closure`. It used to panic.
|
||||||
* `Token::is_operator` is renamed to `Token::is_symbol`.
|
* `Token::is_operator` is renamed to `Token::is_symbol`.
|
||||||
|
|
||||||
|
New features
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Scientific notation is supported for floating-point number literals.
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
@ -1488,10 +1488,15 @@ impl Expr {
|
|||||||
Self::Stmt(x, _) => x.iter().for_each(|s| s.walk(path, on_node)),
|
Self::Stmt(x, _) => x.iter().for_each(|s| s.walk(path, on_node)),
|
||||||
Self::Array(x, _) => x.iter().for_each(|e| e.walk(path, on_node)),
|
Self::Array(x, _) => x.iter().for_each(|e| e.walk(path, on_node)),
|
||||||
Self::Map(x, _) => x.iter().for_each(|(_, e)| e.walk(path, on_node)),
|
Self::Map(x, _) => x.iter().for_each(|(_, e)| e.walk(path, on_node)),
|
||||||
Self::Index(x, _) | Expr::In(x, _) | Expr::And(x, _) | Expr::Or(x, _) => {
|
Self::Index(x, _)
|
||||||
|
| Self::Dot(x, _)
|
||||||
|
| Expr::In(x, _)
|
||||||
|
| Expr::And(x, _)
|
||||||
|
| Expr::Or(x, _) => {
|
||||||
x.lhs.walk(path, on_node);
|
x.lhs.walk(path, on_node);
|
||||||
x.rhs.walk(path, on_node);
|
x.rhs.walk(path, on_node);
|
||||||
}
|
}
|
||||||
|
Self::FnCall(x, _) => x.args.iter().for_each(|e| e.walk(path, on_node)),
|
||||||
Self::Custom(x, _) => x.keywords.iter().for_each(|e| e.walk(path, on_node)),
|
Self::Custom(x, _) => x.keywords.iter().for_each(|e| e.walk(path, on_node)),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
155
src/fn_call.rs
155
src/fn_call.rs
@ -577,7 +577,7 @@ impl Engine {
|
|||||||
|
|
||||||
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
|
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
|
||||||
let arg_types = args.iter().map(|a| a.type_id());
|
let arg_types = args.iter().map(|a| a.type_id());
|
||||||
let hash_fn = calc_native_fn_hash(empty(), fn_name, arg_types);
|
let hash_fn = calc_native_fn_hash(empty(), fn_name, arg_types).unwrap();
|
||||||
|
|
||||||
match fn_name {
|
match fn_name {
|
||||||
// type_of
|
// type_of
|
||||||
@ -587,7 +587,7 @@ impl Engine {
|
|||||||
Some(mods),
|
Some(mods),
|
||||||
Some(state),
|
Some(state),
|
||||||
lib,
|
lib,
|
||||||
hash_fn,
|
Some(hash_fn),
|
||||||
hash_script,
|
hash_script,
|
||||||
pub_only,
|
pub_only,
|
||||||
) =>
|
) =>
|
||||||
@ -606,7 +606,7 @@ impl Engine {
|
|||||||
Some(mods),
|
Some(mods),
|
||||||
Some(state),
|
Some(state),
|
||||||
lib,
|
lib,
|
||||||
hash_fn,
|
Some(hash_fn),
|
||||||
hash_script,
|
hash_script,
|
||||||
pub_only,
|
pub_only,
|
||||||
) =>
|
) =>
|
||||||
@ -622,14 +622,11 @@ impl Engine {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Script-like function found
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
_ if hash_script.is_some()
|
_ if hash_script.is_some() => {
|
||||||
&& self.has_override(Some(mods), Some(state), lib, None, hash_script, pub_only) =>
|
|
||||||
{
|
|
||||||
let hash_script = hash_script.unwrap();
|
let hash_script = hash_script.unwrap();
|
||||||
|
|
||||||
// Check if function access already in the cache
|
// Check if script function access already in the cache
|
||||||
let (func, source) = state
|
let (func, source) = state
|
||||||
.fn_resolution_cache_mut()
|
.fn_resolution_cache_mut()
|
||||||
.entry(hash_script)
|
.entry(hash_script)
|
||||||
@ -649,92 +646,92 @@ impl Engine {
|
|||||||
//.or_else(|| mods.iter().find_map(|(_, m)| m.get_qualified_fn(hash_script).map(|f| (f, m.id_raw().clone()))))
|
//.or_else(|| mods.iter().find_map(|(_, m)| m.get_qualified_fn(hash_script).map(|f| (f, m.id_raw().clone()))))
|
||||||
})
|
})
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|(f, s)| (f.clone(), s.clone()))
|
.map(|(f, s)| (Some(f.clone()), s.clone()))
|
||||||
.unwrap();
|
.unwrap_or((None, None));
|
||||||
|
|
||||||
assert!(func.is_script());
|
if let Some(func) = func {
|
||||||
|
// Script function call
|
||||||
|
assert!(func.is_script());
|
||||||
|
|
||||||
let func = func.get_fn_def();
|
let func = func.get_fn_def();
|
||||||
|
|
||||||
let scope: &mut Scope = &mut Default::default();
|
let scope: &mut Scope = &mut Default::default();
|
||||||
|
|
||||||
// Move captured variables into scope
|
// Move captured variables into scope
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
if let Some(captured) = _capture_scope {
|
if let Some(captured) = _capture_scope {
|
||||||
if !func.externals.is_empty() {
|
if !func.externals.is_empty() {
|
||||||
captured
|
captured
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|(name, _, _)| func.externals.iter().any(|ex| ex == name))
|
.filter(|(name, _, _)| func.externals.iter().any(|ex| ex == name))
|
||||||
.for_each(|(name, value, _)| {
|
.for_each(|(name, value, _)| {
|
||||||
// Consume the scope values.
|
// Consume the scope values.
|
||||||
scope.push_dynamic(name, value);
|
scope.push_dynamic(name, value);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let result = if _is_method {
|
let result = if _is_method {
|
||||||
// Method call of script function - map first argument to `this`
|
// Method call of script function - map first argument to `this`
|
||||||
let (first, rest) = args.split_first_mut().unwrap();
|
let (first, rest) = args.split_first_mut().unwrap();
|
||||||
|
|
||||||
let orig_source = mem::take(&mut state.source);
|
let orig_source = mem::take(&mut state.source);
|
||||||
state.source = source;
|
state.source = source;
|
||||||
|
|
||||||
let level = _level + 1;
|
let level = _level + 1;
|
||||||
|
|
||||||
let result = self.call_script_fn(
|
let result = self.call_script_fn(
|
||||||
scope,
|
scope,
|
||||||
mods,
|
mods,
|
||||||
state,
|
state,
|
||||||
lib,
|
lib,
|
||||||
&mut Some(*first),
|
&mut Some(*first),
|
||||||
func,
|
func,
|
||||||
rest,
|
rest,
|
||||||
pos,
|
pos,
|
||||||
level,
|
level,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Restore the original source
|
// Restore the original source
|
||||||
state.source = orig_source;
|
state.source = orig_source;
|
||||||
|
|
||||||
result?
|
result?
|
||||||
|
} else {
|
||||||
|
// Normal call of script function
|
||||||
|
// The first argument is a reference?
|
||||||
|
let mut backup: ArgBackup = Default::default();
|
||||||
|
backup.change_first_arg_to_copy(is_ref, args);
|
||||||
|
|
||||||
|
let orig_source = mem::take(&mut state.source);
|
||||||
|
state.source = source;
|
||||||
|
|
||||||
|
let level = _level + 1;
|
||||||
|
|
||||||
|
let result = self.call_script_fn(
|
||||||
|
scope, mods, state, lib, &mut None, func, args, pos, level,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Restore the original source
|
||||||
|
state.source = orig_source;
|
||||||
|
|
||||||
|
// Restore the original reference
|
||||||
|
backup.restore_first_arg(args);
|
||||||
|
|
||||||
|
result?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((result, false))
|
||||||
} else {
|
} else {
|
||||||
// Normal call of script function
|
// Native function call
|
||||||
// The first argument is a reference?
|
self.call_native_fn(
|
||||||
let mut backup: ArgBackup = Default::default();
|
mods, state, lib, fn_name, hash_fn, args, is_ref, pub_only, pos, def_val,
|
||||||
backup.change_first_arg_to_copy(is_ref, args);
|
)
|
||||||
|
}
|
||||||
let orig_source = mem::take(&mut state.source);
|
|
||||||
state.source = source;
|
|
||||||
|
|
||||||
let level = _level + 1;
|
|
||||||
|
|
||||||
let result = self
|
|
||||||
.call_script_fn(scope, mods, state, lib, &mut None, func, args, pos, level);
|
|
||||||
|
|
||||||
// Restore the original source
|
|
||||||
state.source = orig_source;
|
|
||||||
|
|
||||||
// Restore the original reference
|
|
||||||
backup.restore_first_arg(args);
|
|
||||||
|
|
||||||
result?
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((result, false))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal native function call
|
// Native function call
|
||||||
_ => self.call_native_fn(
|
_ => self.call_native_fn(
|
||||||
mods,
|
mods, state, lib, fn_name, hash_fn, args, is_ref, pub_only, pos, def_val,
|
||||||
state,
|
|
||||||
lib,
|
|
||||||
fn_name,
|
|
||||||
hash_fn.unwrap(),
|
|
||||||
args,
|
|
||||||
is_ref,
|
|
||||||
pub_only,
|
|
||||||
pos,
|
|
||||||
def_val,
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
72
src/token.rs
72
src/token.rs
@ -1000,7 +1000,7 @@ pub fn get_next_token(
|
|||||||
|
|
||||||
/// Test if the given character is a hex character.
|
/// Test if the given character is a hex character.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn is_hex_char(c: char) -> bool {
|
fn is_hex_digit(c: char) -> bool {
|
||||||
match c {
|
match c {
|
||||||
'a'..='f' => true,
|
'a'..='f' => true,
|
||||||
'A'..='F' => true,
|
'A'..='F' => true,
|
||||||
@ -1009,20 +1009,11 @@ fn is_hex_char(c: char) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test if the given character is an octal character.
|
/// Test if the given character is a numeric digit.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn is_octal_char(c: char) -> bool {
|
fn is_numeric_digit(c: char) -> bool {
|
||||||
match c {
|
match c {
|
||||||
'0'..='7' => true,
|
'0'..='9' => true,
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test if the given character is a binary character.
|
|
||||||
#[inline(always)]
|
|
||||||
fn is_binary_char(c: char) -> bool {
|
|
||||||
match c {
|
|
||||||
'0' | '1' => true,
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1073,11 +1064,12 @@ fn get_next_token_inner(
|
|||||||
('0'..='9', _) => {
|
('0'..='9', _) => {
|
||||||
let mut result: StaticVec<char> = Default::default();
|
let mut result: StaticVec<char> = Default::default();
|
||||||
let mut radix_base: Option<u32> = None;
|
let mut radix_base: Option<u32> = None;
|
||||||
|
let mut valid: fn(char) -> bool = is_numeric_digit;
|
||||||
result.push(c);
|
result.push(c);
|
||||||
|
|
||||||
while let Some(next_char) = stream.peek_next() {
|
while let Some(next_char) = stream.peek_next() {
|
||||||
match next_char {
|
match next_char {
|
||||||
'0'..='9' | '_' => {
|
ch if valid(ch) || ch == '_' => {
|
||||||
result.push(next_char);
|
result.push(next_char);
|
||||||
eat_next(stream, pos);
|
eat_next(stream, pos);
|
||||||
}
|
}
|
||||||
@ -1090,7 +1082,7 @@ fn get_next_token_inner(
|
|||||||
// digits after period - accept the period
|
// digits after period - accept the period
|
||||||
'0'..='9' => {
|
'0'..='9' => {
|
||||||
result.push(next_char);
|
result.push(next_char);
|
||||||
pos.advance()
|
pos.advance();
|
||||||
}
|
}
|
||||||
// _ - cannot follow a decimal point
|
// _ - cannot follow a decimal point
|
||||||
'_' => {
|
'_' => {
|
||||||
@ -1114,28 +1106,43 @@ fn get_next_token_inner(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
'e' => {
|
||||||
|
stream.get_next().unwrap();
|
||||||
|
|
||||||
while let Some(next_char_in_float) = stream.peek_next() {
|
// Check if followed by digits or +/-
|
||||||
match next_char_in_float {
|
match stream.peek_next().unwrap_or('\0') {
|
||||||
'0'..='9' | '_' => {
|
// digits after e - accept the e
|
||||||
result.push(next_char_in_float);
|
'0'..='9' => {
|
||||||
eat_next(stream, pos);
|
result.push(next_char);
|
||||||
}
|
pos.advance();
|
||||||
_ => break,
|
}
|
||||||
|
// +/- after e - accept the e and the sign
|
||||||
|
'+' | '-' => {
|
||||||
|
result.push(next_char);
|
||||||
|
pos.advance();
|
||||||
|
result.push(stream.get_next().unwrap());
|
||||||
|
pos.advance();
|
||||||
|
}
|
||||||
|
// Not a floating-point number
|
||||||
|
_ => {
|
||||||
|
stream.unget(next_char);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 0x????, 0o????, 0b????
|
// 0x????, 0o????, 0b???? at beginning
|
||||||
ch @ 'x' | ch @ 'o' | ch @ 'b' | ch @ 'X' | ch @ 'O' | ch @ 'B'
|
ch @ 'x' | ch @ 'o' | ch @ 'b' | ch @ 'X' | ch @ 'O' | ch @ 'B'
|
||||||
if c == '0' =>
|
if c == '0' && result.len() <= 1 =>
|
||||||
{
|
{
|
||||||
result.push(next_char);
|
result.push(next_char);
|
||||||
eat_next(stream, pos);
|
eat_next(stream, pos);
|
||||||
|
|
||||||
let valid = match ch {
|
valid = match ch {
|
||||||
'x' | 'X' => is_hex_char,
|
'x' | 'X' => is_hex_digit,
|
||||||
'o' | 'O' => is_octal_char,
|
'o' | 'O' => is_numeric_digit,
|
||||||
'b' | 'B' => is_binary_char,
|
'b' | 'B' => is_numeric_digit,
|
||||||
_ => unreachable!("expecting 'x', 'o' or 'b', but gets {}", ch),
|
_ => unreachable!("expecting 'x', 'o' or 'b', but gets {}", ch),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1145,15 +1152,6 @@ fn get_next_token_inner(
|
|||||||
'b' | 'B' => 2,
|
'b' | 'B' => 2,
|
||||||
_ => unreachable!("expecting 'x', 'o' or 'b', but gets {}", ch),
|
_ => unreachable!("expecting 'x', 'o' or 'b', but gets {}", ch),
|
||||||
});
|
});
|
||||||
|
|
||||||
while let Some(next_char_in_escape_seq) = stream.peek_next() {
|
|
||||||
if !valid(next_char_in_escape_seq) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.push(next_char_in_escape_seq);
|
|
||||||
eat_next(stream, pos);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => break,
|
_ => break,
|
||||||
|
@ -7,20 +7,25 @@ const EPSILON: FLOAT = 0.000_000_000_1;
|
|||||||
fn test_float() -> Result<(), Box<EvalAltResult>> {
|
fn test_float() -> Result<(), Box<EvalAltResult>> {
|
||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(
|
assert!(engine.eval::<bool>("let x = 0.0; let y = 1.0; x < y")?,);
|
||||||
engine.eval::<bool>("let x = 0.0; let y = 1.0; x < y")?,
|
assert!(!engine.eval::<bool>("let x = 0.0; let y = 1.0; x > y")?,);
|
||||||
true
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
engine.eval::<bool>("let x = 0.0; let y = 1.0; x > y")?,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
assert_eq!(engine.eval::<bool>("let x = 0.; let y = 1.; x > y")?, false);
|
assert_eq!(engine.eval::<bool>("let x = 0.; let y = 1.; x > y")?, false);
|
||||||
assert!((engine.eval::<FLOAT>("let x = 9.9999; x")? - 9.9999 as FLOAT).abs() < EPSILON);
|
assert!((engine.eval::<FLOAT>("let x = 9.9999; x")? - 9.9999 as FLOAT).abs() < EPSILON);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_float_scientific() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let engine = Engine::new();
|
||||||
|
|
||||||
|
assert!(engine.eval::<bool>("123.456 == 1.23456e2")?);
|
||||||
|
assert!(engine.eval::<bool>("123.456 == 1.23456e+2")?);
|
||||||
|
assert!(engine.eval::<bool>("123.456 == 123456e-3")?);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_float_parse() -> Result<(), Box<EvalAltResult>> {
|
fn test_float_parse() -> Result<(), Box<EvalAltResult>> {
|
||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user