diff --git a/RELEASES.md b/RELEASES.md index 4a999e76..18f3ffd1 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -20,6 +20,11 @@ Breaking changes * `Dynamic::into_shared` is no longer available under `no_closure`. It used to panic. * `Token::is_operator` is renamed to `Token::is_symbol`. +New features +------------ + +* Scientific notation is supported for floating-point number literals. + Enhancements ------------ diff --git a/src/ast.rs b/src/ast.rs index 78d179de..0e4e4ee5 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1488,10 +1488,15 @@ impl Expr { 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::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.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)), _ => (), } diff --git a/src/fn_call.rs b/src/fn_call.rs index 3e8a1070..bb928bb0 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -577,7 +577,7 @@ impl Engine { // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. 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 { // type_of @@ -587,7 +587,7 @@ impl Engine { Some(mods), Some(state), lib, - hash_fn, + Some(hash_fn), hash_script, pub_only, ) => @@ -606,7 +606,7 @@ impl Engine { Some(mods), Some(state), lib, - hash_fn, + Some(hash_fn), hash_script, pub_only, ) => @@ -622,14 +622,11 @@ impl Engine { .into() } - // Script-like function found #[cfg(not(feature = "no_function"))] - _ if hash_script.is_some() - && self.has_override(Some(mods), Some(state), lib, None, hash_script, pub_only) => - { + _ if hash_script.is_some() => { 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 .fn_resolution_cache_mut() .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())))) }) .as_ref() - .map(|(f, s)| (f.clone(), s.clone())) - .unwrap(); + .map(|(f, s)| (Some(f.clone()), s.clone())) + .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 - #[cfg(not(feature = "no_closure"))] - if let Some(captured) = _capture_scope { - if !func.externals.is_empty() { - captured - .into_iter() - .filter(|(name, _, _)| func.externals.iter().any(|ex| ex == name)) - .for_each(|(name, value, _)| { - // Consume the scope values. - scope.push_dynamic(name, value); - }); + // Move captured variables into scope + #[cfg(not(feature = "no_closure"))] + if let Some(captured) = _capture_scope { + if !func.externals.is_empty() { + captured + .into_iter() + .filter(|(name, _, _)| func.externals.iter().any(|ex| ex == name)) + .for_each(|(name, value, _)| { + // Consume the scope values. + scope.push_dynamic(name, value); + }); + } } - } - let result = if _is_method { - // Method call of script function - map first argument to `this` - let (first, rest) = args.split_first_mut().unwrap(); + let result = if _is_method { + // Method call of script function - map first argument to `this` + let (first, rest) = args.split_first_mut().unwrap(); - let orig_source = mem::take(&mut state.source); - state.source = source; + let orig_source = mem::take(&mut state.source); + state.source = source; - let level = _level + 1; + let level = _level + 1; - let result = self.call_script_fn( - scope, - mods, - state, - lib, - &mut Some(*first), - func, - rest, - pos, - level, - ); + let result = self.call_script_fn( + scope, + mods, + state, + lib, + &mut Some(*first), + func, + rest, + pos, + level, + ); - // Restore the original source - state.source = orig_source; + // Restore the original 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 { - // 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)) + // Native function call + self.call_native_fn( + mods, state, lib, fn_name, hash_fn, args, is_ref, pub_only, pos, def_val, + ) + } } - // Normal native function call + // Native function call _ => self.call_native_fn( - mods, - state, - lib, - fn_name, - hash_fn.unwrap(), - args, - is_ref, - pub_only, - pos, - def_val, + mods, state, lib, fn_name, hash_fn, args, is_ref, pub_only, pos, def_val, ), } } diff --git a/src/token.rs b/src/token.rs index 64ed7ab2..b473e374 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1000,7 +1000,7 @@ pub fn get_next_token( /// Test if the given character is a hex character. #[inline(always)] -fn is_hex_char(c: char) -> bool { +fn is_hex_digit(c: char) -> bool { match c { '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)] -fn is_octal_char(c: char) -> bool { +fn is_numeric_digit(c: char) -> bool { match c { - '0'..='7' => 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, + '0'..='9' => true, _ => false, } } @@ -1073,11 +1064,12 @@ fn get_next_token_inner( ('0'..='9', _) => { let mut result: StaticVec = Default::default(); let mut radix_base: Option = None; + let mut valid: fn(char) -> bool = is_numeric_digit; result.push(c); while let Some(next_char) = stream.peek_next() { match next_char { - '0'..='9' | '_' => { + ch if valid(ch) || ch == '_' => { result.push(next_char); eat_next(stream, pos); } @@ -1090,7 +1082,7 @@ fn get_next_token_inner( // digits after period - accept the period '0'..='9' => { result.push(next_char); - pos.advance() + pos.advance(); } // _ - cannot follow a decimal point '_' => { @@ -1114,28 +1106,43 @@ fn get_next_token_inner( break; } } + } + #[cfg(not(feature = "no_float"))] + 'e' => { + stream.get_next().unwrap(); - while let Some(next_char_in_float) = stream.peek_next() { - match next_char_in_float { - '0'..='9' | '_' => { - result.push(next_char_in_float); - eat_next(stream, pos); - } - _ => break, + // Check if followed by digits or +/- + match stream.peek_next().unwrap_or('\0') { + // digits after e - accept the e + '0'..='9' => { + result.push(next_char); + pos.advance(); + } + // +/- 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' - if c == '0' => + if c == '0' && result.len() <= 1 => { result.push(next_char); eat_next(stream, pos); - let valid = match ch { - 'x' | 'X' => is_hex_char, - 'o' | 'O' => is_octal_char, - 'b' | 'B' => is_binary_char, + valid = match ch { + 'x' | 'X' => is_hex_digit, + 'o' | 'O' => is_numeric_digit, + 'b' | 'B' => is_numeric_digit, _ => unreachable!("expecting 'x', 'o' or 'b', but gets {}", ch), }; @@ -1145,15 +1152,6 @@ fn get_next_token_inner( 'b' | 'B' => 2, _ => 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, diff --git a/tests/float.rs b/tests/float.rs index 7c5a4c2f..5cb23e5f 100644 --- a/tests/float.rs +++ b/tests/float.rs @@ -7,20 +7,25 @@ const EPSILON: FLOAT = 0.000_000_000_1; fn test_float() -> Result<(), Box> { let engine = Engine::new(); - assert_eq!( - engine.eval::("let x = 0.0; let y = 1.0; x < y")?, - true - ); - assert_eq!( - engine.eval::("let x = 0.0; let y = 1.0; x > y")?, - false - ); + assert!(engine.eval::("let x = 0.0; let y = 1.0; x < y")?,); + assert!(!engine.eval::("let x = 0.0; let y = 1.0; x > y")?,); assert_eq!(engine.eval::("let x = 0.; let y = 1.; x > y")?, false); assert!((engine.eval::("let x = 9.9999; x")? - 9.9999 as FLOAT).abs() < EPSILON); Ok(()) } +#[test] +fn test_float_scientific() -> Result<(), Box> { + let engine = Engine::new(); + + assert!(engine.eval::("123.456 == 1.23456e2")?); + assert!(engine.eval::("123.456 == 1.23456e+2")?); + assert!(engine.eval::("123.456 == 123456e-3")?); + + Ok(()) +} + #[test] fn test_float_parse() -> Result<(), Box> { let engine = Engine::new();