Merge pull request #350 from schungx/master

Add scientific notation to floating-point numbers.
This commit is contained in:
Stephen Chung 2021-02-11 19:44:44 +08:00 committed by GitHub
commit a2e7e3fc4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 135 additions and 125 deletions

View File

@ -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
------------ ------------

View File

@ -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)),
_ => (), _ => (),
} }

View File

@ -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,
), ),
} }
} }

View File

@ -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,

View File

@ -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();