Merge pull request #654 from HigherOrderCO/648-hexadecimal-and-binary-floating-point-numbers

#648 Hexadecimal and binary floating-point numbers
This commit is contained in:
imaqtkatt 2024-08-05 16:05:50 +00:00 committed by GitHub
commit 5c6eb841e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 126 additions and 41 deletions

View File

@ -28,6 +28,7 @@ and this project does not currently adhere to a particular versioning scheme.
- Change tuple syntax to not require parentheses in some cases. ([#554][gh-554])
- Improve error messages in branching statements. ([#464][gh-464])
- Change branches to support ending with ask statements. ([#629][gh-629])
- Improve hexadecimal and binary floating numbers. ([#648][gh-648])
## [0.2.36] - 2024-07-04
@ -414,4 +415,5 @@ and this project does not currently adhere to a particular versioning scheme.
[gh-629]: https://github.com/HigherOrderCO/Bend/issues/629
[gh-642]: https://github.com/HigherOrderCO/Bend/issues/642
[gh-643]: https://github.com/HigherOrderCO/Bend/issues/643
[gh-648]: https://github.com/HigherOrderCO/Bend/issues/648
[Unreleased]: https://github.com/HigherOrderCO/Bend/compare/0.2.36...HEAD

View File

@ -580,6 +580,18 @@ Currently, the 3 number types cannot be mixed.
| Bitwise Or | x \| y | int, uint |
| Bitwise Xor | x ^ y | int, uint |
Hexadecimal and binary floating-point literals are also supported.
In these representations, each digit after the point is divided according to the bases power of the digit's position.
Specifically, for hexadecimal floating-point numbers, each place after the dot represents a fraction of 16 to the power of the digit's depth.
Similarly, for binary floating-point numbers, each place after the dot represents a fraction of 2 to the power of the digit's depth.
```python
0xA.A == 10.625
0b111.111 == 7.875
```
### Constructor Literals
Constructors are just functions.
@ -1141,6 +1153,18 @@ Currently, the 3 number types cannot be mixed.
| Bitwise Or | (\| x y) | int, uint |
| Bitwise Xor | (^ x y) | int, uint |
Hexadecimal and binary floating-point literals are also supported.
In these representations, each digit after the point is divided according to the bases power of the digit's position.
Specifically, for hexadecimal floating-point numbers, each place after the dot represents a fraction of 16 to the negative power of the digit's depth.
Similarly, for binary floating-point numbers, each place after the dot represents a fraction of 2 to the negative power of the digit's depth.
```python
(== 0xA.A 10.625)
(== 0b111.111 7.875)
```
### Character Literal
```rust
@ -1240,14 +1264,17 @@ changed or optimized by the compiler.
# Import Syntax
### Import Relative to the File
Paths starting with `./` or `../` are imported relative to the file.
### Import Relative to the Main Folder
Paths that do not start with `./` or `../` are relative to the folder of the main file.
## Syntax
### Import Specific Names from a File, or Files from a Folder
```py
from path import name
from path import (name1, name2)
@ -1255,11 +1282,13 @@ import (path/name1, path/name2)
```
### Import All Names from a File, or All Files from a Folder
```py
from path import *
```
### Aliasing Imports
```py
from path import name as alias
from path import (name1 as Alias1, name2 as Alias2)

View File

@ -1518,80 +1518,85 @@ pub trait ParserCommons<'a>: Parser<'a> {
let radix = match self.peek_many(2) {
Some("0x") => {
self.advance_many(2);
16
Radix::Hex
}
Some("0b") => {
self.advance_many(2);
2
Radix::Bin
}
_ => 10,
_ => Radix::Dec,
};
let num_str = self.take_while(move |c| c.is_digit(radix) || c == '_');
let num_str = self.take_while(move |c| c.is_digit(radix as u32) || c == '_');
let num_str = num_str.chars().filter(|c| *c != '_').collect::<String>();
let next_is_hex = self.peek_one().map_or(false, |c| "0123456789abcdefABCDEF".contains(c));
if next_is_hex || num_str.is_empty() {
let base = match radix {
16 => "hexadecimal",
10 => "decimal",
2 => "binary",
_ => unreachable!(),
};
self.expected(format!("valid {base} digit").as_str())
self.expected(format!("valid {radix} digit").as_str())
} else {
u32::from_str_radix(&num_str, radix).map_err(|e| e.to_string())
u32::from_str_radix(&num_str, radix as u32).map_err(|e| e.to_string())
}
}
fn u32_with_radix(&mut self, radix: Radix) -> ParseResult<u32> {
let num_str = self.take_while(move |c| c.is_digit(radix as u32) || c == '_');
let num_str = num_str.chars().filter(|c| *c != '_').collect::<String>();
let next_is_hex = self.peek_one().map_or(false, |c| "0123456789abcdefABCDEF".contains(c));
if next_is_hex || num_str.is_empty() {
self.expected(format!("valid {radix} digit").as_str())
} else {
u32::from_str_radix(&num_str, radix as u32).map_err(|e| e.to_string())
}
}
fn parse_number(&mut self) -> ParseResult<Num> {
let ini_idx = *self.index();
// Parses sign
let sgn = if self.try_consume_exactly("+") {
let sign = if self.try_consume_exactly("+") {
Some(1)
} else if self.try_consume_exactly("-") {
Some(-1)
} else {
None
};
// Parses main value
let num = self.parse_u32()?;
// Parses frac value (Float type)
// TODO: Will lead to some rounding errors
// TODO: Doesn't cover very large/small numbers
let fra = if let Some('.') = self.peek_one() {
let radix = match self.peek_many(2) {
Some("0x") => {
self.advance_many(2);
Radix::Hex
}
Some("0b") => {
self.advance_many(2);
Radix::Bin
}
_ => Radix::Dec,
};
let num = self.u32_with_radix(radix)?;
let frac = if let Some('.') = self.peek_one() {
self.advance_one();
let ini_idx = *self.index();
let fra = self.parse_u32()? as f32;
let end_idx = *self.index();
let fra = fra / 10f32.powi((end_idx - ini_idx) as i32);
let fra_str = self.take_while(|c| c.is_digit(radix as u32) || c == '_');
let fra_str = fra_str.chars().filter(|c| *c != '_').collect::<String>();
let fra = u32::from_str_radix(&fra_str, radix as u32).map_err(|e| e.to_string())?;
let fra = fra as f32 / (radix.to_f32()).powi(fra_str.len() as i32);
Some(fra)
} else {
None
};
// F24
if let Some(fra) = fra {
let sgn = sgn.unwrap_or(1);
return Ok(Num::F24(sgn as f32 * (num as f32 + fra)));
if let Some(frac) = frac {
let sign = sign.unwrap_or(1);
return Ok(Num::F24(sign as f32 * (num as f32 + frac)));
}
// I24
if let Some(sgn) = sgn {
let num = sgn * num as i32;
if let Some(sign) = sign {
let num = sign * num as i32;
if !(-0x00800000..=0x007fffff).contains(&num) {
return self.num_range_err(ini_idx, "I24");
}
return Ok(Num::I24(num));
Ok(Num::I24(num))
} else {
if num >= 1 << 24 {
return self.num_range_err(ini_idx, "U24");
}
Ok(Num::U24(num))
}
// U24
if num >= 1 << 24 {
return self.num_range_err(ini_idx, "U24");
}
Ok(Num::U24(num))
}
fn num_range_err<T>(&mut self, ini_idx: usize, typ: &str) -> ParseResult<T> {
@ -1659,3 +1664,30 @@ pub trait ParserCommons<'a>: Parser<'a> {
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Radix {
Bin = 2,
Dec = 10,
Hex = 16,
}
impl Radix {
fn to_f32(self) -> f32 {
match self {
Radix::Bin => 2.,
Radix::Dec => 10.,
Radix::Hex => 16.,
}
}
}
impl std::fmt::Display for Radix {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Radix::Bin => write!(f, "binary"),
Radix::Dec => write!(f, "decimal"),
Radix::Hex => write!(f, "hexadecimal"),
}
}
}

View File

@ -0,0 +1,2 @@
def main:
return 0xA.0xA

View File

@ -0,0 +1,2 @@
def main:
return [0x12.129, 0x0.2, 0b101.101, 0xAAAAAAAA.AAAAAAAA, 0xA.__A__]

View File

@ -0,0 +1,9 @@
---
source: tests/golden_tests.rs
input_file: tests/golden_tests/parse_file/bad_floating.bend
---
Errors:
In tests/golden_tests/parse_file/bad_floating.bend :
- expected: newline
- detected:
 2 | return 0xA.0xA

View File

@ -0,0 +1,9 @@
---
source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/floating_numbers.bend
---
NumScott:
[18.072, 0.125, 5.625, 2863333376.000, 10.625]
Scott:
[18.072, 0.125, 5.625, 2863333376.000, 10.625]