Skip to content

Commit

Permalink
Merge pull request #564 from phel-lang/feat/allow-underscores-in-deci…
Browse files Browse the repository at this point in the history
…mal-numbers

Allow underscores in decimal numbers
  • Loading branch information
Chemaclass committed Feb 3, 2023
2 parents e5da3f5 + f97391c commit 5079153
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 35 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
* Rename command `phel compile` to `phel build` (#555)
* Added config parameter `ignore-when-building` (#557)
* Added config parameter `keep-generated-temp-files` (#553)
* Allow underscores in decimal numbers (#564)

## 0.8.0 (2023-01-16)

Expand Down
97 changes: 66 additions & 31 deletions src/php/Compiler/Domain/Parser/ExpressionParser/AtomParser.php
Expand Up @@ -18,7 +18,11 @@

final class AtomParser
{
private const KEYWORD_REGEX = '/:(?<second_colon>:?)((?<namespace>[^\/]+)\/)?(?<keyword>[^\/]+)/';
private const REGEX_KEYWORD = '/:(?<second_colon>:?)((?<namespace>[^\/]+)\/)?(?<keyword>[^\/]+)/';
private const REGEX_BINARY_NUMBER = '/^([+-])?0[bB][01]+(_[01]+)*$/';
private const REGEX_HEXADECIMAL_NUMBER = '/^([+-])?0[xX][0-9a-fA-F]+(_[0-9a-fA-F]+)*$/';
private const REGEX_OCTAL_NUMBER = '/^([+-])?0[0-7]+(_[0-7]+)*$/';
private const REGEX_DECIMAL_NUMBER = '/^([+-])?[0-9]+(_[0-9]+)*[\.(_[0-9]+]?|0$/';

public function __construct(private GlobalEnvironmentInterface $globalEnvironment)
{
Expand All @@ -44,50 +48,33 @@ public function parse(Token $token): AbstractAtomNode
return $this->parseKeyword($token);
}

if (preg_match('/^([+-])?0[bB][01]+(_[01]+)*$/', $word, $matches)) {
// binary numbers
$sign = (isset($matches[1]) && $matches[1] === '-') ? -1 : 1;
return new NumberNode(
$word,
$token->getStartLocation(),
$token->getEndLocation(),
$sign * bindec(str_replace('_', '', $word)),
);
if (preg_match(self::REGEX_BINARY_NUMBER, $word, $matches)) {
return $this->parseBinaryNumber($matches, $word, $token);
}

if (preg_match('/^([+-])?0[xX][0-9a-fA-F]+(_[0-9a-fA-F]+)*$/', $word, $matches)) {
// hexdecimal numbers
$sign = (isset($matches[1]) && $matches[1] === '-') ? -1 : 1;
return new NumberNode(
$word,
$token->getStartLocation(),
$token->getEndLocation(),
$sign * hexdec(str_replace('_', '', $word)),
);
if (preg_match(self::REGEX_HEXADECIMAL_NUMBER, $word, $matches)) {
return $this->parseHexadecimalNumber($matches, $word, $token);
}

if (preg_match('/^([+-])?0[0-7]+(_[0-7]+)*$/', $word, $matches)) {
// octal numbers
$sign = (isset($matches[1]) && $matches[1] === '-') ? -1 : 1;
return new NumberNode(
$word,
$token->getStartLocation(),
$token->getEndLocation(),
$sign * octdec(str_replace('_', '', $word)),
);
if (preg_match(self::REGEX_OCTAL_NUMBER, $word, $matches)) {
return $this->parseOctalNumber($matches, $word, $token);
}

if (is_numeric($word)) {
return new NumberNode($word, $token->getStartLocation(), $token->getEndLocation(), $word + 0);
}

if (preg_match(self::REGEX_DECIMAL_NUMBER, $word, $matches)) {
return $this->parseDecimalNumber($matches, $word, $token);
}

return new SymbolNode($word, $token->getStartLocation(), $token->getEndLocation(), Symbol::create($word));
}

private function parseKeyword(Token $token): KeywordNode
{
$word = $token->getCode();
$isValid = preg_match(self::KEYWORD_REGEX, $word, $matches);
$isValid = preg_match(self::REGEX_KEYWORD, $word, $matches);

if (!$isValid) {
throw new KeywordParserException('This is not a valid keyword');
Expand Down Expand Up @@ -116,8 +103,8 @@ private function parseKeyword(Token $token): KeywordNode
}

$keyword = $namespace
? Keyword::createForNamespace($namespace, $matches['keyword'])
: Keyword::create($matches['keyword']);
? Keyword::createForNamespace($namespace, $matches['keyword'])
: Keyword::create($matches['keyword']);

return new KeywordNode(
$word,
Expand All @@ -126,4 +113,52 @@ private function parseKeyword(Token $token): KeywordNode
$keyword,
);
}

private function parseBinaryNumber(array $matches, string $word, Token $token): NumberNode
{
$sign = (isset($matches[1]) && $matches[1] === '-') ? -1 : 1;

return new NumberNode(
$word,
$token->getStartLocation(),
$token->getEndLocation(),
$sign * bindec(str_replace('_', '', $word)),
);
}

private function parseHexadecimalNumber(array $matches, string $word, Token $token): NumberNode
{
$sign = (isset($matches[1]) && $matches[1] === '-') ? -1 : 1;

return new NumberNode(
$word,
$token->getStartLocation(),
$token->getEndLocation(),
$sign * hexdec(str_replace('_', '', $word)),
);
}

private function parseOctalNumber(array $matches, string $word, Token $token): NumberNode
{
$sign = (isset($matches[1]) && $matches[1] === '-') ? -1 : 1;

return new NumberNode(
$word,
$token->getStartLocation(),
$token->getEndLocation(),
$sign * octdec(str_replace('_', '', $word)),
);
}

private function parseDecimalNumber(array $matches, string $word, Token $token): NumberNode
{
$sign = (isset($matches[1]) && $matches[1] === '-') ? -1 : 1;

return new NumberNode(
$word,
$token->getStartLocation(),
$token->getEndLocation(),
$sign * (int)str_replace('_', '', $word),
);
}
}
8 changes: 6 additions & 2 deletions tests/phel/test/core/math-operation.phel
Expand Up @@ -103,6 +103,10 @@
(is (= 2 (mean [1 2 3])) "(mean [1 2 3])"))

(deftest test-numbers-with-underscore
(is (= 1337 0b10100111001) "binary number")
(is (= 1337 0b101_0011_1001) "binary number with underscores")
(is (= -1337 -0x5_39) "hexadecimal number with underscores")
(is (= 1337 024_71) "octal number with underscores"))
(is (= 1337 0x539) "hexadecimal number")
(is (= 1337 0x5_39) "hexadecimal number with underscores")
(is (= 1337 02471) "octal number")
(is (= 1337 024_71) "octal number with underscores")
(is (= 1337 1_337) "decimal number with underscores"))
Expand Up @@ -182,7 +182,7 @@ public function test_parse_binary_number(): void
);
}

public function test_parse_hexdecimal_number(): void
public function test_parse_hexadecimal_number(): void
{
$parser = new AtomParser(new GlobalEnvironment());
$start = new SourceLocation('string', 0, 0);
Expand Down Expand Up @@ -218,7 +218,7 @@ public function test_parse_numeric_number(): void
);
}

public function test_parse_otcal_number(): void
public function test_parse_octal_number(): void
{
$parser = new AtomParser(new GlobalEnvironment());
$start = new SourceLocation('string', 0, 0);
Expand Down

0 comments on commit 5079153

Please sign in to comment.