Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>"
]
Expand All @@ -13,6 +13,9 @@ categories = [
]
keywords = [
"parseint",
"parsefloat",
"javascript",
"ecmascript",
"numbers"
]

Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -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
Expand Down
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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.
184 changes: 184 additions & 0 deletions src/float.rs
Original file line number Diff line number Diff line change
@@ -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<T: num::Float + num::FromPrimitive + std::fmt::Display>(
chars: &mut dyn PeekableIterator<Item = char>,
whitespace: bool,
) -> Option<T> {
let mut neg = false;
let mut int: Option<u32> = None;
let mut dec: Option<u32> = 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<T: num::Float + num::FromPrimitive + std::fmt::Display>(s: &str) -> Option<T> {
parse_float_from_iter::<T>(&mut s.chars().peekable(), true)
}

#[test]
fn test_parse_float_f32() {
assert_eq!(parse_float::<f32>(" -123.hello "), Some(-123f32));
assert_eq!(parse_float::<f32>(" -13.37.hello "), Some(-13.37f32));
assert_eq!(parse_float::<f32>(" -13.37e2.hello "), Some(-1337f32));
assert_eq!(parse_float::<f32>(" -13.37e-2.hello "), Some(-0.1337f32));
assert_eq!(
parse_float::<f32>(" -13.37e-16 "),
Some(-0.000000000000001337f32)
);
assert_eq!(parse_float::<f32>(" -1337.0e-30f32 "), Some(-1337.0e-30f32));
}

#[test]
fn test_parse_float_f64() {
assert_eq!(parse_float::<f64>(" -123.hello "), Some(-123f64));
assert_eq!(parse_float::<f64>(" -13.37.hello "), Some(-13.37f64));
assert_eq!(parse_float::<f64>(" -13.37e2.hello "), Some(-1337f64));
assert_eq!(parse_float::<f64>(" -13.37e-2.hello "), Some(-0.1337f64));
assert_eq!(
parse_float::<f64>(" -13.37e-16 "),
Some(-0.000000000000001337f64)
);
assert_eq!(parse_float::<f64>(" -1337.0e-30f64 "), Some(-1337.0e-30f64));
assert_eq!(
parse_float::<f64>(" -1337.0e-296f64 "),
Some(-1337.0e-296f64)
); // OK
assert_ne!(
parse_float::<f64>(" -1337.0e-297f64 "),
Some(-1337.0e-297f64)
); // fails due precision error
}
3 changes: 2 additions & 1 deletion src/parseint.rs → src/int.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
10 changes: 6 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down