From 570c528b3e4e41af2bb8ec0cf091e52dd50db13a Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 4 Apr 2023 15:22:48 +0200 Subject: [PATCH] Integers with different bases --- docs/src/reference/types.md | 7 +++++ src/syntax/ast.rs | 12 +++++++- src/syntax/lexer.rs | 47 +++++++++++++++++++++++------ tests/typ/compiler/ops.typ | 14 +++++++++ tools/support/typst.tmLanguage.json | 2 +- 5 files changed, 70 insertions(+), 12 deletions(-) diff --git a/docs/src/reference/types.md b/docs/src/reference/types.md index c323ab41d..312ba7c0d 100644 --- a/docs/src/reference/types.md +++ b/docs/src/reference/types.md @@ -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 diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 4abf51d9c..d718ccf02 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -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() } } diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs index 748cb0763..b67a009c2 100644 --- a/src/syntax/lexer.rs +++ b/src/syntax/lexer.rs @@ -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::().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::().is_ok() { - SyntaxKind::Int - } else if number.parse::().is_ok() { - SyntaxKind::Float - } else { - self.error("invalid number") - }; + return kind; } if !matches!( diff --git a/tests/typ/compiler/ops.typ b/tests/typ/compiler/ops.typ index a29003edb..1a9a2169b 100644 --- a/tests/typ/compiler/ops.typ +++ b/tests/typ/compiler/ops.typ @@ -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. diff --git a/tools/support/typst.tmLanguage.json b/tools/support/typst.tmLanguage.json index 1e7e94e03..7e01c41a2 100644 --- a/tools/support/typst.tmLanguage.json +++ b/tools/support/typst.tmLanguage.json @@ -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",