Integers with different bases

This commit is contained in:
Laurenz 2023-04-04 15:22:48 +02:00
parent f738d89ff2
commit 570c528b3e
5 changed files with 70 additions and 12 deletions

View File

@ -43,11 +43,18 @@ The number can be negative, zero, or positive. As Typst uses 64 bits to store
integers, integers cannot be smaller than `{-9223372036854775808}` or larger than
`{9223372036854775807}`.
The number can also be specified as hexadecimal, octal, or binary by starting it
with a zero followed by either `x`, `o`, or `b`.
## Example
```example
#(1 + 2) \
#(2 - 5) \
#(3 + 4 < 8)
#0xff \
#0o10 \
#0b1001
```
# Float

View File

@ -912,7 +912,17 @@ node! {
impl Int {
/// Get the integer value.
pub fn get(&self) -> i64 {
self.0.text().parse().unwrap_or_default()
let text = self.0.text();
if let Some(rest) = text.strip_prefix("0x") {
i64::from_str_radix(rest, 16)
} else if let Some(rest) = text.strip_prefix("0o") {
i64::from_str_radix(rest, 8)
} else if let Some(rest) = text.strip_prefix("0b") {
i64::from_str_radix(rest, 2)
} else {
text.parse()
}
.unwrap_or_default()
}
}

View File

@ -524,9 +524,28 @@ impl Lexer<'_> {
SyntaxKind::Ident
}
fn number(&mut self, start: usize, c: char) -> SyntaxKind {
fn number(&mut self, mut start: usize, c: char) -> SyntaxKind {
// Handle alternative integer bases.
let mut base = 10;
if c == '0' {
if self.s.eat_if('b') {
base = 2;
} else if self.s.eat_if('o') {
base = 8;
} else if self.s.eat_if('x') {
base = 16;
}
if base != 10 {
start = self.s.cursor();
}
}
// Read the first part (integer or fractional depending on `first`).
self.s.eat_while(char::is_ascii_digit);
self.s.eat_while(if base == 16 {
char::is_ascii_alphanumeric
} else {
char::is_ascii_digit
});
// Read the fractional part if not already done.
// Make sure not to confuse a range for the decimal separator.
@ -534,12 +553,13 @@ impl Lexer<'_> {
&& !self.s.at("..")
&& !self.s.scout(1).map_or(false, is_id_start)
&& self.s.eat_if('.')
&& base == 10
{
self.s.eat_while(char::is_ascii_digit);
}
// Read the exponent.
if !self.s.at("em") && self.s.eat_if(['e', 'E']) {
if !self.s.at("em") && self.s.eat_if(['e', 'E']) && base == 10 {
self.s.eat_if(['+', '-']);
self.s.eat_while(char::is_ascii_digit);
}
@ -553,14 +573,21 @@ impl Lexer<'_> {
let number = self.s.get(start..suffix_start);
let suffix = self.s.from(suffix_start);
let kind = if i64::from_str_radix(number, base).is_ok() {
SyntaxKind::Int
} else if base == 10 && number.parse::<f64>().is_ok() {
SyntaxKind::Float
} else {
return self.error(match base {
2 => "invalid binary number",
8 => "invalid octal number",
16 => "invalid hexadecimal number",
_ => "invalid number",
});
};
if suffix.is_empty() {
return if number.parse::<i64>().is_ok() {
SyntaxKind::Int
} else if number.parse::<f64>().is_ok() {
SyntaxKind::Float
} else {
self.error("invalid number")
};
return kind;
}
if !matches!(

View File

@ -109,6 +109,20 @@
}
}
---
// Test numbers with alternative bases.
#test(0x10, 16)
#test(0b1101, 13)
#test(0xA + 0xa, 0x14)
---
// Error: 2-7 invalid binary number
#0b123
---
// Error: 2-8 invalid hexadecimal number
#0x123z
---
// Test boolean operators.

View File

@ -344,7 +344,7 @@
},
{
"name": "constant.numeric.integer.typst",
"match": "\\b\\d+\\b"
"match": "\\b(0x[0-9a-zA-Z]+|(0b|0o)?\\d+)\\b"
},
{
"name": "constant.numeric.float.typst",