Skip to content

Commit 7acad44

Browse files
committed
Support for long number declaration
1 parent 3194547 commit 7acad44

File tree

9 files changed

+102
-53
lines changed

9 files changed

+102
-53
lines changed

docs/docs/en/changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
### Changes
1212
- Introducing Constants. Constant can be imported only when using a module.
13+
- Support for long number declaration: `700L`, `0xABL`
1314
- Fixed variables scope in shadowing.
1415
- Better error visualizing. Parse errors shows exact line in which an error occurs. Same for Linter and Runtime errors.
1516
- Semantic linter as a required stage.

docs/docs/ru/changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
### Изменения
1212
- Добавлены константы. Константа может быть импортирована только при подключении модуля.
13+
- Возможность задать числа типа long: `700L`, `0xABL`
1314
- Исправлена область видимости переменных при шедоуинге.
1415
- Улучшена визуализация ошибок. Ошибки парсинга показывают конкретное место, где возникла ошибка. То же самое с линтером и ошибками времени исполнения.
1516
- Семантический линтер как обязательный этап работы интерпретатора.

ownlang-parser/src/main/java/com/annimon/ownlang/parser/Lexer.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,8 @@ private void tokenizeNumber() {
167167
while (true) {
168168
if (current == '.') {
169169
decimal = true;
170-
if (hasDot) throw error("Invalid float number " + buffer, startPos);
170+
if (hasDot)
171+
throw error("Invalid float number " + buffer, startPos);
171172
hasDot = true;
172173
} else if (current == 'e' || current == 'E') {
173174
decimal = true;
@@ -182,6 +183,9 @@ private void tokenizeNumber() {
182183
}
183184
if (decimal) {
184185
addToken(TokenType.DECIMAL_NUMBER, buffer.toString(), startPos);
186+
} else if (current == 'L') {
187+
next();
188+
addToken(TokenType.LONG_NUMBER, buffer.toString(), startPos);
185189
} else {
186190
addToken(TokenType.NUMBER, buffer.toString(), startPos);
187191
}
@@ -232,7 +236,12 @@ private void tokenizeHexNumber(int skipChars) {
232236

233237
if (buffer.isEmpty()) throw error("Empty HEX value", startPos);
234238
if (peek(-1) == '_') throw error("HEX value cannot end with _", startPos, markEndPos());
235-
addToken(TokenType.HEX_NUMBER, buffer.toString(), startPos);
239+
if (current == 'L') {
240+
next();
241+
addToken(TokenType.HEX_LONG_NUMBER, buffer.toString(), startPos);
242+
} else {
243+
addToken(TokenType.HEX_NUMBER, buffer.toString(), startPos);
244+
}
236245
}
237246

238247
private static boolean isNumber(char current) {

ownlang-parser/src/main/java/com/annimon/ownlang/parser/Parser.java

Lines changed: 61 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ public static Node parse(List<Token> tokens) {
5050
ASSIGN_OPERATORS.put(TokenType.ATEQ, BinaryExpression.Operator.AT);
5151
}
5252

53+
private static final EnumSet<TokenType> NUMBER_TOKEN_TYPES = EnumSet.of(
54+
TokenType.NUMBER,
55+
TokenType.LONG_NUMBER,
56+
TokenType.DECIMAL_NUMBER,
57+
TokenType.HEX_NUMBER,
58+
TokenType.HEX_LONG_NUMBER
59+
);
60+
5361
private final List<Token> tokens;
5462
private final int size;
5563
private final ParseErrors parseErrors;
@@ -347,7 +355,7 @@ private Node functionChain(Node qualifiedNameExpr) {
347355
}
348356
if (lookMatch(0, TokenType.DOT)) {
349357
final List<Node> indices = variableSuffix();
350-
if (indices == null || indices.isEmpty()) {
358+
if (indices.isEmpty()) {
351359
return expr;
352360
}
353361

@@ -411,20 +419,10 @@ private MatchExpression match() {
411419
consume(TokenType.CASE);
412420
MatchExpression.Pattern pattern = null;
413421
final Token current = get(0);
414-
if (match(TokenType.NUMBER)) {
415-
// case 20:
416-
pattern = new MatchExpression.ConstantPattern(
417-
NumberValue.of(createNumber(current.text(), 10))
418-
);
419-
} else if (match(TokenType.DECIMAL_NUMBER)) {
420-
// case 0.5:
421-
pattern = new MatchExpression.ConstantPattern(
422-
NumberValue.of(createDecimalNumber(current.text()))
423-
);
424-
} else if (match(TokenType.HEX_NUMBER)) {
425-
// case #FF:
422+
if (isNumberToken(current.type())) {
423+
// case 20: / case 0.5: / case #FF:
426424
pattern = new MatchExpression.ConstantPattern(
427-
NumberValue.of(createNumber(current.text(), 16))
425+
NumberValue.of(getAsNumber(current))
428426
);
429427
} else if (match(TokenType.TEXT)) {
430428
// case "text":
@@ -859,7 +857,7 @@ private Node qualifiedName() {
859857
final List<Node> indices = variableSuffix();
860858
final var variable = new VariableExpression(current.text());
861859
variable.setRange(getRange(startTokenIndex, index - 1));
862-
if (indices == null || indices.isEmpty()) {
860+
if (indices.isEmpty()) {
863861
return variable;
864862
} else {
865863
return new ContainerAccessExpression(variable, indices, variable.getRange());
@@ -869,7 +867,7 @@ private Node qualifiedName() {
869867
private List<Node> variableSuffix() {
870868
// .key1.arr1[expr1][expr2].key2
871869
if (!lookMatch(0, TokenType.DOT) && !lookMatch(0, TokenType.LBRACKET)) {
872-
return null;
870+
return Collections.emptyList();
873871
}
874872
final List<Node> indices = new ArrayList<>();
875873
while (lookMatch(0, TokenType.DOT) || lookMatch(0, TokenType.LBRACKET)) {
@@ -888,47 +886,72 @@ private List<Node> variableSuffix() {
888886

889887
private Node value() {
890888
final Token current = get(0);
891-
if (match(TokenType.NUMBER)) {
892-
return new ValueExpression(createNumber(current.text(), 10));
893-
}
894-
if (match(TokenType.DECIMAL_NUMBER)) {
895-
return new ValueExpression(createDecimalNumber(current.text()));
896-
}
897-
if (match(TokenType.HEX_NUMBER)) {
898-
return new ValueExpression(createNumber(current.text(), 16));
889+
if (isNumberToken(current.type())) {
890+
return new ValueExpression(getAsNumber(current));
899891
}
900892
if (match(TokenType.TEXT)) {
901893
final ValueExpression strExpr = new ValueExpression(current.text());
902894
// "text".property || "text".func()
903895
if (lookMatch(0, TokenType.DOT)) {
904-
if (lookMatch(1, TokenType.WORD) && lookMatch(2, TokenType.LPAREN)) {
905-
match(TokenType.DOT);
906-
return functionChain(new ContainerAccessExpression(
907-
strExpr,
908-
Collections.singletonList(new ValueExpression(consume(TokenType.WORD).text())),
909-
getRange()
910-
));
911-
}
912-
final List<Node> indices = variableSuffix();
913-
if (indices == null || indices.isEmpty()) {
914-
return strExpr;
915-
}
916-
return new ContainerAccessExpression(strExpr, indices, getRange());
896+
return stringProperty(strExpr);
917897
}
918898
return strExpr;
919899
}
920900
throw error("Unknown expression: " + current);
921901
}
922902

903+
private Node stringProperty(ValueExpression strExpr) {
904+
if (lookMatch(1, TokenType.WORD) && lookMatch(2, TokenType.LPAREN)) {
905+
match(TokenType.DOT);
906+
return functionChain(new ContainerAccessExpression(
907+
strExpr,
908+
Collections.singletonList(new ValueExpression(consume(TokenType.WORD).text())),
909+
getRange()
910+
));
911+
}
912+
final List<Node> indices = variableSuffix();
913+
if (indices.isEmpty()) {
914+
return strExpr;
915+
}
916+
return new ContainerAccessExpression(strExpr, indices, getRange());
917+
}
918+
919+
private boolean isNumberToken(TokenType type) {
920+
return NUMBER_TOKEN_TYPES.contains(type);
921+
}
922+
923+
private Number getAsNumber(Token current) {
924+
if (match(TokenType.NUMBER)) {
925+
return createNumber(current.text(), 10);
926+
}
927+
if (match(TokenType.LONG_NUMBER)) {
928+
return createLongNumber(current.text(), 10);
929+
}
930+
if (match(TokenType.DECIMAL_NUMBER)) {
931+
return createDecimalNumber(current.text());
932+
}
933+
if (match(TokenType.HEX_NUMBER)) {
934+
return createNumber(current.text(), 16);
935+
}
936+
if (match(TokenType.HEX_LONG_NUMBER)) {
937+
return createLongNumber(current.text(), 16);
938+
}
939+
throw error("Unknown number expression: " + current);
940+
}
941+
923942
private Number createNumber(String text, int radix) {
924943
// Integer
925944
try {
926945
return Integer.parseInt(text, radix);
927946
} catch (NumberFormatException nfe) {
928-
return Long.parseLong(text, radix);
947+
return createLongNumber(text, radix);
929948
}
930949
}
931950

951+
private Number createLongNumber(String text, int radix) {
952+
return Long.parseLong(text, radix);
953+
}
954+
932955
private Number createDecimalNumber(String text) {
933956
// Double
934957
return Double.parseDouble(text);

ownlang-parser/src/main/java/com/annimon/ownlang/parser/TokenType.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
public enum TokenType {
88

99
NUMBER,
10+
LONG_NUMBER,
1011
DECIMAL_NUMBER,
1112
HEX_NUMBER,
13+
HEX_LONG_NUMBER,
1214
WORD,
1315
TEXT,
1416

ownlang-parser/src/test/java/com/annimon/ownlang/parser/LexerTest.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,17 @@ public static Stream<Arguments> invalidData() {
4141
}
4242

4343
@Test
44-
public void testNumbers() {
45-
String input = "0 3.1415 0xCAFEBABE 0Xf7_d6_c5 #FFFF";
44+
void testNumbers() {
45+
String input = "0 800L 3.1415 0xCAFEBABE 0Xf7_d6_c5 #FFFF 0x7FL";
4646
List<Token> result = Lexer.tokenize(input);
47-
assertTokens(result, NUMBER, DECIMAL_NUMBER, HEX_NUMBER, HEX_NUMBER, HEX_NUMBER);
47+
assertTokens(result, NUMBER, LONG_NUMBER, DECIMAL_NUMBER, HEX_NUMBER, HEX_NUMBER, HEX_NUMBER, HEX_LONG_NUMBER);
4848
assertThat(result)
4949
.extracting(Token::text)
50-
.containsExactly("0", "3.1415", "CAFEBABE", "f7d6c5", "FFFF");
50+
.containsExactly("0", "800", "3.1415", "CAFEBABE", "f7d6c5", "FFFF", "7F");
5151
}
5252

5353
@Test
54-
public void testDecimalNumbersExponent() {
54+
void testDecimalNumbersExponent() {
5555
String input = "4e+7 0.3E-19 2e0 5e0000000000000200 5E-000000089";
5656
List<Token> result = Lexer.tokenize(input);
5757
assertThat(result)
@@ -61,15 +61,15 @@ public void testDecimalNumbersExponent() {
6161
}
6262

6363
@Test
64-
public void testString() {
64+
void testString() {
6565
String input = "\"1\\\"2\"";
6666
List<Token> result = Lexer.tokenize(input);
6767
assertTokens(result, TEXT);
6868
assertEquals("1\"2", result.get(0).text());
6969
}
7070

7171
@Test
72-
public void testEscapeString() {
72+
void testEscapeString() {
7373
String input = """
7474
"\\\\/\\\\"
7575
""".stripIndent();
@@ -79,15 +79,15 @@ public void testEscapeString() {
7979
}
8080

8181
@Test
82-
public void testEmptyString() {
82+
void testEmptyString() {
8383
String input = "\"\"";
8484
List<Token> result = Lexer.tokenize(input);
8585
assertTokens(result, TEXT);
8686
assertEquals("", result.get(0).text());
8787
}
8888

8989
@Test
90-
public void testComments() {
90+
void testComments() {
9191
String input = "// 1234 \n /* */ 123 /* \n 12345 \n\n\n */";
9292
List<Token> result = Lexer.tokenize(input);
9393
assertTokens(result, NUMBER);
@@ -96,7 +96,7 @@ public void testComments() {
9696

9797
@ParameterizedTest
9898
@MethodSource("validData")
99-
public void testValidInput(String name, String input, List<TokenType> tokenTypes) throws IOException {
99+
void testValidInput(String name, String input, List<TokenType> tokenTypes) throws IOException {
100100
List<Token> result = Lexer.tokenize(input);
101101
assertThat(result)
102102
.hasSize(tokenTypes.size())
@@ -106,7 +106,7 @@ public void testValidInput(String name, String input, List<TokenType> tokenTypes
106106

107107
@ParameterizedTest
108108
@MethodSource("invalidData")
109-
public void testInvalidInput(String name, String input) throws IOException {
109+
void testInvalidInput(String name, String input) throws IOException {
110110
assertThatThrownBy(() -> Lexer.tokenize(input))
111111
.isInstanceOf(OwnLangParserException.class);
112112
}

ownlang-parser/src/test/java/com/annimon/ownlang/parser/LexerValidDataProvider.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ private static List<Arguments> numbers() {
3333
List.of(DECIMAL_NUMBER)),
3434
Arguments.of("Hex numbers",
3535
"#FF 0xCA 0x12fb 0xFF",
36-
List.of(HEX_NUMBER, HEX_NUMBER, HEX_NUMBER, HEX_NUMBER))
36+
List.of(HEX_NUMBER, HEX_NUMBER, HEX_NUMBER, HEX_NUMBER)),
37+
Arguments.of("Long numbers",
38+
"680L #80L 0x700L",
39+
List.of(LONG_NUMBER, HEX_LONG_NUMBER, HEX_LONG_NUMBER))
3740
);
3841
}
3942

ownlang-parser/src/test/java/com/annimon/ownlang/parser/ProgramsTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public static void createStage() {
5050

5151
@ParameterizedTest
5252
@MethodSource("data")
53-
public void testProgram(InputSource inputSource) {
53+
void testProgram(InputSource inputSource) {
5454
final StagesDataMap stagesData = new StagesDataMap();
5555
try {
5656
testPipeline.perform(stagesData, inputSource);

ownlang-parser/src/test/resources/expressions/binaryExpressionOnNumbers.own

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ def testMultiplicationOnNumbers() {
1414
assertEquals(30, 5 * (-2 * -3))
1515
}
1616

17+
def testMultiplicationOverflowOnNumbers() {
18+
assertNotEquals(1234567890000L, 1234567890 * 1000)
19+
assertNotEquals(0xFFFFFF00, 0x100 * 0xFFFFFF)
20+
}
21+
22+
def testMultiplicationOnLongNumbers() {
23+
assertEquals(1234567890000L, 1234567890 * 1000L)
24+
assertEquals(0xFFFFFF00L, 0x100L * 0xFFFFFF)
25+
}
26+
1727
def testDivisionOnNumbers() {
1828
assertEquals(3, 6 / 2)
1929
assertEquals(30, -900 / (60 / -2))

0 commit comments

Comments
 (0)