Skip to content

Commit 185c405

Browse files
Fix number parsing to support underscores before x or b (seaofvoices#103)
1 parent bd3d9c7 commit 185c405

10 files changed

+120
-94
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44

5+
* fix number parsing to support underscores before `x` in hexadecimal number and before `b` in binary numbers ([#103](https://github.com/seaofvoices/darklua/pull/103))
56
* add bundling with path require mode ([#97](https://github.com/seaofvoices/darklua/pull/97))
67
* upgrade full-moon parser to 0.18.1 ([#100](https://github.com/seaofvoices/darklua/pull/100))
78

src/nodes/expressions/number.rs

+93-89
Original file line numberDiff line numberDiff line change
@@ -397,99 +397,89 @@ impl FromStr for NumberExpression {
397397
type Err = NumberParsingError;
398398

399399
fn from_str(value: &str) -> Result<Self, Self::Err> {
400-
let number = if value.starts_with("0x") || value.starts_with("0X") {
401-
let is_x_uppercase = value
402-
.chars()
403-
.nth(1)
404-
.map(char::is_uppercase)
405-
.unwrap_or(false);
406-
407-
if let Some(index) = value.find('p') {
408-
let exponent = value
409-
.get(index + 1..)
410-
.and_then(|string| string.parse().ok())
411-
.ok_or(Self::Err::InvalidHexadecimalExponent)?;
412-
let number = u64::from_str_radix(value.get(2..index).unwrap(), 16)
413-
.map_err(|_| Self::Err::InvalidHexadecimalNumber)?;
414-
415-
HexNumber::new(number, is_x_uppercase).with_exponent(exponent, false)
416-
} else if let Some(index) = value.find('P') {
417-
let exponent = value
418-
.get(index + 1..)
419-
.map(filter_underscore)
420-
.and_then(|string| string.parse().ok())
421-
.ok_or(Self::Err::InvalidHexadecimalExponent)?;
422-
let filtered = filter_underscore(value.get(2..index).unwrap());
423-
let number = u64::from_str_radix(&filtered, 16)
424-
.map_err(|_| Self::Err::InvalidHexadecimalNumber)?;
425-
426-
HexNumber::new(number, is_x_uppercase).with_exponent(exponent, true)
427-
} else {
428-
let filtered = filter_underscore(value.get(2..).unwrap());
429-
let number = u64::from_str_radix(&filtered, 16)
430-
.map_err(|_| Self::Err::InvalidHexadecimalNumber)?;
431-
432-
HexNumber::new(number, is_x_uppercase)
433-
}
434-
.into()
435-
} else if value.starts_with("0b") || value.starts_with("0B") {
436-
let is_b_uppercase = value
437-
.chars()
438-
.nth(1)
439-
.map(char::is_uppercase)
440-
.unwrap_or(false);
441-
442-
let filtered = filter_underscore(value);
443-
let number = u64::from_str_radix(filtered.get(2..).unwrap(), 2)
444-
.map_err(|_| Self::Err::InvalidBinaryNumber)?;
445-
446-
BinaryNumber::new(number, is_b_uppercase).into()
447-
} else {
448-
// in Luau, underscores are valid everywhere in a number except
449-
// after a `.`
450-
if value.contains("._") {
451-
return Err(Self::Err::InvalidDecimalNumber);
400+
let notation_prefix = value
401+
.char_indices()
402+
.filter(|(_, c)| *c != '_')
403+
.take(2)
404+
.nth(1);
405+
let starts_with_zero = value.starts_with('0');
406+
407+
let number = match (starts_with_zero, notation_prefix) {
408+
(true, Some((position, notation))) if matches!(notation, 'x' | 'X' | 'b' | 'B') => {
409+
let is_uppercase = notation.is_uppercase();
410+
411+
if notation == 'x' || notation == 'X' {
412+
if let Some((exponent_is_uppercase, index)) = value
413+
.find('p')
414+
.map(|index| (false, index))
415+
.or_else(|| value.find('P').map(|index| (true, index)))
416+
{
417+
let exponent = value
418+
.get(index + 1..)
419+
.and_then(|string| string.parse().ok())
420+
.ok_or(Self::Err::InvalidHexadecimalExponent)?;
421+
let before_exponent = value.get(position + 1..index).unwrap();
422+
let number = u64::from_str_radix(before_exponent, 16)
423+
.map_err(|_| Self::Err::InvalidHexadecimalNumber)?;
424+
425+
HexNumber::new(number, is_uppercase)
426+
.with_exponent(exponent, exponent_is_uppercase)
427+
} else {
428+
let filtered = filter_underscore(value.get(position + 1..).unwrap());
429+
let number = u64::from_str_radix(&filtered, 16)
430+
.map_err(|_| Self::Err::InvalidHexadecimalNumber)?;
431+
432+
HexNumber::new(number, is_uppercase)
433+
}
434+
.into()
435+
} else if notation == 'b' || notation == 'B' {
436+
let filtered = filter_underscore(value.get(position + 1..).unwrap());
437+
let number = u64::from_str_radix(&filtered, 2)
438+
.map_err(|_| Self::Err::InvalidBinaryNumber)?;
439+
440+
BinaryNumber::new(number, is_uppercase).into()
441+
} else {
442+
unreachable!()
443+
}
452444
}
453-
454-
if let Some(index) = value.find('e') {
455-
// in Luau, underscores are not valid before the exponent sign
456-
if value.contains("_-") || value.contains("_+") {
457-
return Err(Self::Err::InvalidDecimalExponent);
445+
_ => {
446+
// in Luau, underscores are valid everywhere in a number except
447+
// after a `.`
448+
if value.contains("._") {
449+
return Err(Self::Err::InvalidDecimalNumber);
458450
}
459451

460-
let exponent = value
461-
.get(index + 1..)
462-
.map(filter_underscore)
463-
.and_then(|string| string.parse().ok())
464-
.ok_or(Self::Err::InvalidDecimalExponent)?;
465-
let number = value
466-
.get(0..index)
467-
.map(filter_underscore)
468-
.and_then(|string| string.parse().ok())
469-
.ok_or(Self::Err::InvalidDecimalNumber)?;
470-
471-
DecimalNumber::new(number).with_exponent(exponent, false)
472-
} else if let Some(index) = value.find('E') {
473-
let exponent: i64 = value
474-
.get(index + 1..)
475-
.map(filter_underscore)
476-
.and_then(|string| string.parse().ok())
477-
.ok_or(Self::Err::InvalidDecimalExponent)?;
478-
let number = value
479-
.get(0..index)
480-
.map(filter_underscore)
481-
.and_then(|string| string.parse().ok())
482-
.ok_or(Self::Err::InvalidDecimalNumber)?;
483-
484-
DecimalNumber::new(number).with_exponent(exponent, true)
485-
} else {
486-
let number = filter_underscore(value)
487-
.parse::<f64>()
488-
.map_err(|_| Self::Err::InvalidDecimalNumber)?;
489-
490-
DecimalNumber::new(number)
452+
if let Some((exponent_is_uppercase, index)) = value
453+
.find('e')
454+
.map(|index| (false, index))
455+
.or_else(|| value.find('E').map(|index| (true, index)))
456+
{
457+
// in Luau, underscores are not valid before the exponent sign
458+
if value.contains("_-") || value.contains("_+") {
459+
return Err(Self::Err::InvalidDecimalExponent);
460+
}
461+
462+
let exponent = value
463+
.get(index + 1..)
464+
.map(filter_underscore)
465+
.and_then(|string| string.parse().ok())
466+
.ok_or(Self::Err::InvalidDecimalExponent)?;
467+
let number = value
468+
.get(0..index)
469+
.map(filter_underscore)
470+
.and_then(|string| string.parse().ok())
471+
.ok_or(Self::Err::InvalidDecimalNumber)?;
472+
473+
DecimalNumber::new(number).with_exponent(exponent, exponent_is_uppercase)
474+
} else {
475+
let number = filter_underscore(value)
476+
.parse::<f64>()
477+
.map_err(|_| Self::Err::InvalidDecimalNumber)?;
478+
479+
DecimalNumber::new(number)
480+
}
481+
.into()
491482
}
492-
.into()
493483
};
494484

495485
Ok(number)
@@ -606,28 +596,42 @@ mod test {
606596
parse_float_with_exponent_and_underscores("10_0.12_e_8") => DecimalNumber::new(100.12_f64).with_exponent(8, false),
607597
parse_trailing_dot_with_exponent("10.e8") => DecimalNumber::new(10_f64).with_exponent(8, false),
608598
parse_hex_number("0x12") => HexNumber::new(18, false),
599+
parse_hex_number_with_underscore_before_x("0_x12") => HexNumber::new(18, false),
600+
parse_hex_number_with_underscores_around_x("0_x_12") => HexNumber::new(18, false),
609601
parse_hex_number_with_underscore("0x12_13") => HexNumber::new(0x1213, false),
610602
parse_uppercase_hex_number("0X12") => HexNumber::new(18, true),
603+
parse_uppercase_hex_number_with_underscore_before_x("0_X13") => HexNumber::new(19, true),
611604
parse_hex_number_with_lowercase("0x12a") => HexNumber::new(298, false),
612605
parse_hex_number_with_uppercase("0x12A") => HexNumber::new(298, false),
613606
parse_hex_number_with_mixed_case("0x1bF2A") => HexNumber::new(114_474, false),
614607
parse_hex_with_exponent("0x12p4") => HexNumber::new(18, false).with_exponent(4, false),
615608
parse_hex_with_exponent_uppercase("0xABP3") => HexNumber::new(171, false).with_exponent(3, true),
616609
parse_binary_zero("0b0") => BinaryNumber::new(0, false),
610+
parse_binary_zero_with_underscore_before_b("0_b1") => BinaryNumber::new(1, false),
617611
parse_binary_zero_with_underscore("0b1010_1100") => BinaryNumber::new(0b1010_1100, false),
618612
parse_binary_zero_uppercase("0B0") => BinaryNumber::new(0, true),
613+
parse_binary_zero_uppercase_with_underscore_before_b("0_B1") => BinaryNumber::new(1, true),
619614
);
620615

621616
test_parse_errors!(
622617
parse_empty_string("") => NumberParsingError::InvalidDecimalNumber,
623618
missing_exponent_value("1e") => NumberParsingError::InvalidDecimalExponent,
619+
missing_exponent_value_uppercase("1E") => NumberParsingError::InvalidDecimalExponent,
624620
invalid_underscore_position("1._1") => NumberParsingError::InvalidDecimalNumber,
625621
missing_negative_exponent_value("1e-") => NumberParsingError::InvalidDecimalExponent,
622+
missing_negative_exponent_value_uppercase("1E-") => NumberParsingError::InvalidDecimalExponent,
626623
invalid_underscore_before_negative_exponent("1e_-1") => NumberParsingError::InvalidDecimalExponent,
627624
invalid_underscore_before_positive_exponent("1e_+1") => NumberParsingError::InvalidDecimalExponent,
625+
invalid_underscore_before_negative_exponent_uppercase("1E_-1") => NumberParsingError::InvalidDecimalExponent,
626+
invalid_underscore_before_positive_exponent_uppercase("1E_+1") => NumberParsingError::InvalidDecimalExponent,
628627
missing_hex_exponent_value("0x1p") => NumberParsingError::InvalidHexadecimalExponent,
629628
negative_hex_exponent_value("0x1p-3") => NumberParsingError::InvalidHexadecimalExponent,
629+
missing_hex_exponent_value_uppercase("0x1P") => NumberParsingError::InvalidHexadecimalExponent,
630+
invalid_hex_exponent_value("0x1p1Z") => NumberParsingError::InvalidHexadecimalExponent,
631+
invalid_hex_exponent_value_uppercase("0x1P1Z") => NumberParsingError::InvalidHexadecimalExponent,
632+
negative_hex_exponent_value_uppercase("0x1P-3") => NumberParsingError::InvalidHexadecimalExponent,
630633
invalid_digit_in_binary("0b190") => NumberParsingError::InvalidBinaryNumber,
634+
invalid_digit_in_binary_uppercase("0B190") => NumberParsingError::InvalidBinaryNumber,
631635
);
632636
}
633637

src/rules/snapshots/darklua_core__rules__remove_comments__test__remove_comments_in_code.snap

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ function foo
1515
end
1616
function foo:var
1717
(param1 , p2 , ... )
18+
return 0x01 - 0b1
1819
end
1920

2021
for key , value in pairs( variable ) do continue end

src/rules/snapshots/darklua_core__rules__remove_spaces__test__remove_spaces_in_code.snap

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ function foo-- foo
1515
end
1616
function foo:var-- var method
1717
(param1--[[string]],p2,...--[[rest]])-- end of parameters
18+
return 0x01--[[first]]-0b1-- number
1819
end
1920

2021
for key--[[comment]],value in pairs(variable)do continue end

tests/bundle.rs

+1
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,7 @@ data:
476476
#[test]
477477
fn require_small_bundle_case() {
478478
let resources = memory_resources!(
479+
"src/initialize.lua" => include_str!("./test_cases/small_bundle/initialize.lua"),
479480
"src/value.lua" => include_str!("./test_cases/small_bundle/value.lua"),
480481
"src/format.lua" => include_str!("./test_cases/small_bundle/format.lua"),
481482
"src/main.lua" => include_str!("./test_cases/small_bundle/main.lua"),

tests/snapshots/bundle__without_rules__bundle_without_rules_require_small_bundle_case.snap

+14-5
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@
22
source: tests/bundle.rs
33
expression: main
44
---
5-
local __DARKLUA_BUNDLE_MODULES={}do local function generateNumber()
5+
local __DARKLUA_BUNDLE_MODULES={}do local function initialize()
6+
end
7+
8+
__DARKLUA_BUNDLE_MODULES.a=initialize
9+
end do
10+
local function generateNumber()
611
return math.random(1, 9999)
712
end
813

9-
__DARKLUA_BUNDLE_MODULES.a={
14+
__DARKLUA_BUNDLE_MODULES.b={
1015
zero = 0,
1116
one = 1,
17+
hex = 0x10,
18+
binary = 0b1010,
1219
number1 = generateNumber(),
1320
number2 = generateNumber(),
1421
number3 = generateNumber(),
@@ -18,11 +25,13 @@ local function format(value)
1825
return '[' .. tostring(value) .. ']'
1926
end
2027

21-
__DARKLUA_BUNDLE_MODULES.b=format -- comment after returning format function
28+
__DARKLUA_BUNDLE_MODULES.c=format -- comment after returning format function
2229
end
23-
local value = __DARKLUA_BUNDLE_MODULES.a -- import value module
30+
local initialize = __DARKLUA_BUNDLE_MODULES.a -- import initialize module
31+
32+
local value = __DARKLUA_BUNDLE_MODULES.b -- import value module
2433

25-
local format = __DARKLUA_BUNDLE_MODULES.b --[[ import format module ]]
34+
local format = __DARKLUA_BUNDLE_MODULES.c --[[ import format module ]]
2635

2736
print(format(value.number1 + value.number2))
2837

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
local function initialize()
2+
end
3+
4+
return initialize

tests/test_cases/small_bundle/main.lua

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
local initialize = require("./initialize") -- import initialize module
2+
13
local value = require("./value") -- import value module
24

35
local format = require("./format") --[[ import format module ]]

tests/test_cases/small_bundle/value.lua

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ end
55
return {
66
zero = 0,
77
one = 1,
8+
hex = 0x10,
9+
binary = 0b1010,
810
number1 = generateNumber(),
911
number2 = generateNumber(),
1012
number3 = generateNumber(),

tests/test_cases/spaces_and_comments.lua

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ function foo -- foo
1111
end
1212
function foo:var -- var method
1313
(param1 --[[string]], p2 , ... --[[rest]] ) -- end of parameters
14+
return 0x01 --[[first]] - 0b1 -- number
1415
end
1516

1617
for key --[[comment]], value in pairs( variable ) do continue end

0 commit comments

Comments
 (0)