diff --git a/Cargo.lock b/Cargo.lock index 5f14fc7..b0c2885 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,7 +65,7 @@ dependencies = [ [[package]] name = "num-parse" -version = "0.1.2" +version = "0.2.0" dependencies = [ "num", ] diff --git a/Cargo.toml b/Cargo.toml index f69b241..1012b53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "num-parse" -version = "0.1.2" +version = "0.2.0" edition = "2021" -description = "Generic, JavaScript-like parseInt() functions for Rust." +description = "parseInt() and parseFloat() as known from JavaScript, but generic, and in Rust" authors = [ "Jan Max Meyer " ] @@ -13,6 +13,9 @@ categories = [ ] keywords = [ "parseint", + "parsefloat", + "javascript", + "ecmascript", "numbers" ] diff --git a/LICENSE b/LICENSE index 23ce87c..a0a39ed 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright © 2022 by Jan Max Meyer, Phorward Software Technologies. +Copyright © 2024 by Jan Max Meyer, Phorward Software Technologies. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index ba41b72..95d4f26 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,9 @@ [![crates.io](https://img.shields.io/crates/v/num-parse)](https://crates.io/crates/num-parse) [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) -Generic, JavaScript-like parseInt() functions for Rust. +Generic, JavaScript-style parseInt() and parseFloat() functions for Rust. -This crate is intended to provide a fast and generic `parseInt()`-like implementation for Rust, which mostly follows the specification described in the [MDN parseInt() documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt). - -Likewise in JavaScript, a `parseFloat()`-like implementation for float-types is planned as well, therefore the crate has been named `num-parse` already, althought it currently provides `parse_int()` and variative functions only. +This crate is intended to provide a fast and generic `parseInt()`- and `parseFloat()`-like implementation for Rust, which mostly follows the specification described in the MDN documentation for [parseInt()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt) and [parseFloat()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat). ## parse_int(), parse_uint() @@ -33,3 +31,11 @@ assert_eq!( Some(3405691582usize) ); ``` + +## parse_float() + +TODO + +## PeekableIterator + +This crate is required by and implemented together with the [Tokay programming language](https://tokay.dev) to parse and calculate numerical values from a `PeekableIterator`-trait, which is also defined here. A JavaScript-like numerical parsing was thought to be useful for other projects as well. diff --git a/src/float.rs b/src/float.rs new file mode 100644 index 0000000..d12000e --- /dev/null +++ b/src/float.rs @@ -0,0 +1,184 @@ +/*! Generic, JavaScript-like parseFloat() function for parsing floating point numbers +from any character-emitting resource. */ + +use super::*; +use num; + +/** Parse float values from a PeekableIterator. + +Trailing `whitespace` is accepted, when set to `true`. +*/ +pub fn parse_float_from_iter( + chars: &mut dyn PeekableIterator, + whitespace: bool, +) -> Option { + let mut neg = false; + let mut int: Option = None; + let mut dec: Option = None; + + // Skip over whitespace + if whitespace { + while let Some(ch) = chars.peek() { + if !ch.is_whitespace() { + break; + } + + chars.next(); + } + } + + // Match sign + match chars.peek() { + Some(ch) if *ch == '-' || *ch == '+' => { + neg = chars.next().unwrap() == '-'; + } + _ => {} + } + + // Integer part (optional) + while let Some(dig) = chars.peek() { + int = match dig.to_digit(10) { + Some(digit) => { + let mut int = int.unwrap_or(0); + + int = int * 10 + digit; + + chars.next(); + Some(int) + } + None => break, + } + } + + // Decimal point (this *is* mandatory!) + match chars.peek() { + Some(ch) if *ch == '.' => { + chars.next(); + } + _ => return None, + } + + // Decimal part (optional) + while let Some(dig) = chars.peek() { + dec = match dig.to_digit(10) { + Some(digit) => { + let mut dec = dec.unwrap_or(0); + + dec = dec * 10 + digit; + + chars.next(); + Some(dec) + } + None => break, + } + } + + // Either integer or decimal part must be given, otherwise reject + if int.is_none() && dec.is_none() { + return None; + } + + // Turn integer and decimal part into floating point number + let int = T::from_u32(int.unwrap_or(0)).unwrap(); + let dec = T::from_u32(dec.unwrap_or(0)).unwrap(); + let ten = T::from_u32(10).unwrap(); + + let mut precision = + std::iter::successors(Some(dec), |&n| (n >= ten).then(|| n / ten)).count() as u32; + let mut ret = int + dec / ten.powi(precision as i32); + + // Parse optionally provided exponential notation + match chars.peek() { + Some('e') | Some('E') => { + chars.next(); + + let mut neg = false; + + match chars.peek() { + Some(ch) if *ch == '-' || *ch == '+' => { + neg = chars.next().unwrap() == '-'; + } + _ => {} + } + + let mut exp: u32 = 0; + + while let Some(dig) = chars.peek() { + match dig.to_digit(10) { + Some(digit) => { + exp = exp * 10; + exp = exp + digit; + + chars.next(); + } + None => break, + } + } + + if neg { + precision += exp; + } else if precision < exp { + precision = 0; + } + + for _ in 0..exp { + if neg { + ret = ret / ten; + } else { + ret = ret * ten; + } + } + } + _ => {} + } + + let factor = ten.powf(T::from_u32(precision).unwrap()); + + ret = (ret * factor).round() / factor; + + // Negate when necessary + if neg { + Some(-ret) + } else { + Some(ret) + } +} + +/// Parse float values from a &str, ignoring trailing whitespace. +pub fn parse_float(s: &str) -> Option { + parse_float_from_iter::(&mut s.chars().peekable(), true) +} + +#[test] +fn test_parse_float_f32() { + assert_eq!(parse_float::(" -123.hello "), Some(-123f32)); + assert_eq!(parse_float::(" -13.37.hello "), Some(-13.37f32)); + assert_eq!(parse_float::(" -13.37e2.hello "), Some(-1337f32)); + assert_eq!(parse_float::(" -13.37e-2.hello "), Some(-0.1337f32)); + assert_eq!( + parse_float::(" -13.37e-16 "), + Some(-0.000000000000001337f32) + ); + assert_eq!(parse_float::(" -1337.0e-30f32 "), Some(-1337.0e-30f32)); +} + +#[test] +fn test_parse_float_f64() { + assert_eq!(parse_float::(" -123.hello "), Some(-123f64)); + assert_eq!(parse_float::(" -13.37.hello "), Some(-13.37f64)); + assert_eq!(parse_float::(" -13.37e2.hello "), Some(-1337f64)); + assert_eq!(parse_float::(" -13.37e-2.hello "), Some(-0.1337f64)); + assert_eq!( + parse_float::(" -13.37e-16 "), + Some(-0.000000000000001337f64) + ); + assert_eq!(parse_float::(" -1337.0e-30f64 "), Some(-1337.0e-30f64)); + assert_eq!( + parse_float::(" -1337.0e-296f64 "), + Some(-1337.0e-296f64) + ); // OK + assert_ne!( + parse_float::(" -1337.0e-297f64 "), + Some(-1337.0e-297f64) + ); // fails due precision error +} diff --git a/src/parseint.rs b/src/int.rs similarity index 97% rename from src/parseint.rs rename to src/int.rs index 6c957aa..ce82ffa 100644 --- a/src/parseint.rs +++ b/src/int.rs @@ -1,4 +1,5 @@ -//! JavaScript-style parseInt-like parsing of numbers from strings in Rust. +/** Generic, JavaScript-like parseInt() function for parsing integer numbers +with custom bases from any character-emitting resource. */ use super::*; use num; diff --git a/src/lib.rs b/src/lib.rs index 1cc0ced..1213492 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,16 @@ // num-parse -// Copyright © 2022 by Jan Max Meyer, Phorward Software Technologies. +// Copyright © 2023 by Jan Max Meyer, Phorward Software Technologies. // Licensed under the MIT license. See LICENSE for more information. /*! num-parse - Generic, JavaScript-like parseInt() functions for Rust. + parseInt() and parseFloat() as known from JavaScript, but generic, and in Rust! */ -mod parseint; -pub use parseint::*; +mod float; +mod int; +pub use float::*; +pub use int::*; /// Trait defining an iterator that implements a peek method on its own. pub trait PeekableIterator: std::iter::Iterator {