mirror of
https://github.com/aNNiMON/Own-Programming-Language-Tutorial.git
synced 2024-09-20 00:34:20 +03:00
Support for long number declaration
This commit is contained in:
parent
31945471dd
commit
7acad44211
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
- Introducing Constants. Constant can be imported only when using a module.
|
- Introducing Constants. Constant can be imported only when using a module.
|
||||||
|
- Support for long number declaration: `700L`, `0xABL`
|
||||||
- Fixed variables scope in shadowing.
|
- Fixed variables scope in shadowing.
|
||||||
- Better error visualizing. Parse errors shows exact line in which an error occurs. Same for Linter and Runtime errors.
|
- Better error visualizing. Parse errors shows exact line in which an error occurs. Same for Linter and Runtime errors.
|
||||||
- Semantic linter as a required stage.
|
- Semantic linter as a required stage.
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
### Изменения
|
### Изменения
|
||||||
- Добавлены константы. Константа может быть импортирована только при подключении модуля.
|
- Добавлены константы. Константа может быть импортирована только при подключении модуля.
|
||||||
|
- Возможность задать числа типа long: `700L`, `0xABL`
|
||||||
- Исправлена область видимости переменных при шедоуинге.
|
- Исправлена область видимости переменных при шедоуинге.
|
||||||
- Улучшена визуализация ошибок. Ошибки парсинга показывают конкретное место, где возникла ошибка. То же самое с линтером и ошибками времени исполнения.
|
- Улучшена визуализация ошибок. Ошибки парсинга показывают конкретное место, где возникла ошибка. То же самое с линтером и ошибками времени исполнения.
|
||||||
- Семантический линтер как обязательный этап работы интерпретатора.
|
- Семантический линтер как обязательный этап работы интерпретатора.
|
||||||
|
@ -167,7 +167,8 @@ public final class Lexer {
|
|||||||
while (true) {
|
while (true) {
|
||||||
if (current == '.') {
|
if (current == '.') {
|
||||||
decimal = true;
|
decimal = true;
|
||||||
if (hasDot) throw error("Invalid float number " + buffer, startPos);
|
if (hasDot)
|
||||||
|
throw error("Invalid float number " + buffer, startPos);
|
||||||
hasDot = true;
|
hasDot = true;
|
||||||
} else if (current == 'e' || current == 'E') {
|
} else if (current == 'e' || current == 'E') {
|
||||||
decimal = true;
|
decimal = true;
|
||||||
@ -182,6 +183,9 @@ public final class Lexer {
|
|||||||
}
|
}
|
||||||
if (decimal) {
|
if (decimal) {
|
||||||
addToken(TokenType.DECIMAL_NUMBER, buffer.toString(), startPos);
|
addToken(TokenType.DECIMAL_NUMBER, buffer.toString(), startPos);
|
||||||
|
} else if (current == 'L') {
|
||||||
|
next();
|
||||||
|
addToken(TokenType.LONG_NUMBER, buffer.toString(), startPos);
|
||||||
} else {
|
} else {
|
||||||
addToken(TokenType.NUMBER, buffer.toString(), startPos);
|
addToken(TokenType.NUMBER, buffer.toString(), startPos);
|
||||||
}
|
}
|
||||||
@ -232,7 +236,12 @@ public final class Lexer {
|
|||||||
|
|
||||||
if (buffer.isEmpty()) throw error("Empty HEX value", startPos);
|
if (buffer.isEmpty()) throw error("Empty HEX value", startPos);
|
||||||
if (peek(-1) == '_') throw error("HEX value cannot end with _", startPos, markEndPos());
|
if (peek(-1) == '_') throw error("HEX value cannot end with _", startPos, markEndPos());
|
||||||
addToken(TokenType.HEX_NUMBER, buffer.toString(), startPos);
|
if (current == 'L') {
|
||||||
|
next();
|
||||||
|
addToken(TokenType.HEX_LONG_NUMBER, buffer.toString(), startPos);
|
||||||
|
} else {
|
||||||
|
addToken(TokenType.HEX_NUMBER, buffer.toString(), startPos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isNumber(char current) {
|
private static boolean isNumber(char current) {
|
||||||
|
@ -50,6 +50,14 @@ public final class Parser {
|
|||||||
ASSIGN_OPERATORS.put(TokenType.ATEQ, BinaryExpression.Operator.AT);
|
ASSIGN_OPERATORS.put(TokenType.ATEQ, BinaryExpression.Operator.AT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final EnumSet<TokenType> NUMBER_TOKEN_TYPES = EnumSet.of(
|
||||||
|
TokenType.NUMBER,
|
||||||
|
TokenType.LONG_NUMBER,
|
||||||
|
TokenType.DECIMAL_NUMBER,
|
||||||
|
TokenType.HEX_NUMBER,
|
||||||
|
TokenType.HEX_LONG_NUMBER
|
||||||
|
);
|
||||||
|
|
||||||
private final List<Token> tokens;
|
private final List<Token> tokens;
|
||||||
private final int size;
|
private final int size;
|
||||||
private final ParseErrors parseErrors;
|
private final ParseErrors parseErrors;
|
||||||
@ -347,7 +355,7 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
if (lookMatch(0, TokenType.DOT)) {
|
if (lookMatch(0, TokenType.DOT)) {
|
||||||
final List<Node> indices = variableSuffix();
|
final List<Node> indices = variableSuffix();
|
||||||
if (indices == null || indices.isEmpty()) {
|
if (indices.isEmpty()) {
|
||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,20 +419,10 @@ public final class Parser {
|
|||||||
consume(TokenType.CASE);
|
consume(TokenType.CASE);
|
||||||
MatchExpression.Pattern pattern = null;
|
MatchExpression.Pattern pattern = null;
|
||||||
final Token current = get(0);
|
final Token current = get(0);
|
||||||
if (match(TokenType.NUMBER)) {
|
if (isNumberToken(current.type())) {
|
||||||
// case 20:
|
// case 20: / case 0.5: / case #FF:
|
||||||
pattern = new MatchExpression.ConstantPattern(
|
pattern = new MatchExpression.ConstantPattern(
|
||||||
NumberValue.of(createNumber(current.text(), 10))
|
NumberValue.of(getAsNumber(current))
|
||||||
);
|
|
||||||
} else if (match(TokenType.DECIMAL_NUMBER)) {
|
|
||||||
// case 0.5:
|
|
||||||
pattern = new MatchExpression.ConstantPattern(
|
|
||||||
NumberValue.of(createDecimalNumber(current.text()))
|
|
||||||
);
|
|
||||||
} else if (match(TokenType.HEX_NUMBER)) {
|
|
||||||
// case #FF:
|
|
||||||
pattern = new MatchExpression.ConstantPattern(
|
|
||||||
NumberValue.of(createNumber(current.text(), 16))
|
|
||||||
);
|
);
|
||||||
} else if (match(TokenType.TEXT)) {
|
} else if (match(TokenType.TEXT)) {
|
||||||
// case "text":
|
// case "text":
|
||||||
@ -859,7 +857,7 @@ public final class Parser {
|
|||||||
final List<Node> indices = variableSuffix();
|
final List<Node> indices = variableSuffix();
|
||||||
final var variable = new VariableExpression(current.text());
|
final var variable = new VariableExpression(current.text());
|
||||||
variable.setRange(getRange(startTokenIndex, index - 1));
|
variable.setRange(getRange(startTokenIndex, index - 1));
|
||||||
if (indices == null || indices.isEmpty()) {
|
if (indices.isEmpty()) {
|
||||||
return variable;
|
return variable;
|
||||||
} else {
|
} else {
|
||||||
return new ContainerAccessExpression(variable, indices, variable.getRange());
|
return new ContainerAccessExpression(variable, indices, variable.getRange());
|
||||||
@ -869,7 +867,7 @@ public final class Parser {
|
|||||||
private List<Node> variableSuffix() {
|
private List<Node> variableSuffix() {
|
||||||
// .key1.arr1[expr1][expr2].key2
|
// .key1.arr1[expr1][expr2].key2
|
||||||
if (!lookMatch(0, TokenType.DOT) && !lookMatch(0, TokenType.LBRACKET)) {
|
if (!lookMatch(0, TokenType.DOT) && !lookMatch(0, TokenType.LBRACKET)) {
|
||||||
return null;
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
final List<Node> indices = new ArrayList<>();
|
final List<Node> indices = new ArrayList<>();
|
||||||
while (lookMatch(0, TokenType.DOT) || lookMatch(0, TokenType.LBRACKET)) {
|
while (lookMatch(0, TokenType.DOT) || lookMatch(0, TokenType.LBRACKET)) {
|
||||||
@ -888,47 +886,72 @@ public final class Parser {
|
|||||||
|
|
||||||
private Node value() {
|
private Node value() {
|
||||||
final Token current = get(0);
|
final Token current = get(0);
|
||||||
if (match(TokenType.NUMBER)) {
|
if (isNumberToken(current.type())) {
|
||||||
return new ValueExpression(createNumber(current.text(), 10));
|
return new ValueExpression(getAsNumber(current));
|
||||||
}
|
|
||||||
if (match(TokenType.DECIMAL_NUMBER)) {
|
|
||||||
return new ValueExpression(createDecimalNumber(current.text()));
|
|
||||||
}
|
|
||||||
if (match(TokenType.HEX_NUMBER)) {
|
|
||||||
return new ValueExpression(createNumber(current.text(), 16));
|
|
||||||
}
|
}
|
||||||
if (match(TokenType.TEXT)) {
|
if (match(TokenType.TEXT)) {
|
||||||
final ValueExpression strExpr = new ValueExpression(current.text());
|
final ValueExpression strExpr = new ValueExpression(current.text());
|
||||||
// "text".property || "text".func()
|
// "text".property || "text".func()
|
||||||
if (lookMatch(0, TokenType.DOT)) {
|
if (lookMatch(0, TokenType.DOT)) {
|
||||||
if (lookMatch(1, TokenType.WORD) && lookMatch(2, TokenType.LPAREN)) {
|
return stringProperty(strExpr);
|
||||||
match(TokenType.DOT);
|
|
||||||
return functionChain(new ContainerAccessExpression(
|
|
||||||
strExpr,
|
|
||||||
Collections.singletonList(new ValueExpression(consume(TokenType.WORD).text())),
|
|
||||||
getRange()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
final List<Node> indices = variableSuffix();
|
|
||||||
if (indices == null || indices.isEmpty()) {
|
|
||||||
return strExpr;
|
|
||||||
}
|
|
||||||
return new ContainerAccessExpression(strExpr, indices, getRange());
|
|
||||||
}
|
}
|
||||||
return strExpr;
|
return strExpr;
|
||||||
}
|
}
|
||||||
throw error("Unknown expression: " + current);
|
throw error("Unknown expression: " + current);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Node stringProperty(ValueExpression strExpr) {
|
||||||
|
if (lookMatch(1, TokenType.WORD) && lookMatch(2, TokenType.LPAREN)) {
|
||||||
|
match(TokenType.DOT);
|
||||||
|
return functionChain(new ContainerAccessExpression(
|
||||||
|
strExpr,
|
||||||
|
Collections.singletonList(new ValueExpression(consume(TokenType.WORD).text())),
|
||||||
|
getRange()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
final List<Node> indices = variableSuffix();
|
||||||
|
if (indices.isEmpty()) {
|
||||||
|
return strExpr;
|
||||||
|
}
|
||||||
|
return new ContainerAccessExpression(strExpr, indices, getRange());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNumberToken(TokenType type) {
|
||||||
|
return NUMBER_TOKEN_TYPES.contains(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Number getAsNumber(Token current) {
|
||||||
|
if (match(TokenType.NUMBER)) {
|
||||||
|
return createNumber(current.text(), 10);
|
||||||
|
}
|
||||||
|
if (match(TokenType.LONG_NUMBER)) {
|
||||||
|
return createLongNumber(current.text(), 10);
|
||||||
|
}
|
||||||
|
if (match(TokenType.DECIMAL_NUMBER)) {
|
||||||
|
return createDecimalNumber(current.text());
|
||||||
|
}
|
||||||
|
if (match(TokenType.HEX_NUMBER)) {
|
||||||
|
return createNumber(current.text(), 16);
|
||||||
|
}
|
||||||
|
if (match(TokenType.HEX_LONG_NUMBER)) {
|
||||||
|
return createLongNumber(current.text(), 16);
|
||||||
|
}
|
||||||
|
throw error("Unknown number expression: " + current);
|
||||||
|
}
|
||||||
|
|
||||||
private Number createNumber(String text, int radix) {
|
private Number createNumber(String text, int radix) {
|
||||||
// Integer
|
// Integer
|
||||||
try {
|
try {
|
||||||
return Integer.parseInt(text, radix);
|
return Integer.parseInt(text, radix);
|
||||||
} catch (NumberFormatException nfe) {
|
} catch (NumberFormatException nfe) {
|
||||||
return Long.parseLong(text, radix);
|
return createLongNumber(text, radix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Number createLongNumber(String text, int radix) {
|
||||||
|
return Long.parseLong(text, radix);
|
||||||
|
}
|
||||||
|
|
||||||
private Number createDecimalNumber(String text) {
|
private Number createDecimalNumber(String text) {
|
||||||
// Double
|
// Double
|
||||||
return Double.parseDouble(text);
|
return Double.parseDouble(text);
|
||||||
|
@ -7,8 +7,10 @@ package com.annimon.ownlang.parser;
|
|||||||
public enum TokenType {
|
public enum TokenType {
|
||||||
|
|
||||||
NUMBER,
|
NUMBER,
|
||||||
|
LONG_NUMBER,
|
||||||
DECIMAL_NUMBER,
|
DECIMAL_NUMBER,
|
||||||
HEX_NUMBER,
|
HEX_NUMBER,
|
||||||
|
HEX_LONG_NUMBER,
|
||||||
WORD,
|
WORD,
|
||||||
TEXT,
|
TEXT,
|
||||||
|
|
||||||
|
@ -41,17 +41,17 @@ public class LexerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNumbers() {
|
void testNumbers() {
|
||||||
String input = "0 3.1415 0xCAFEBABE 0Xf7_d6_c5 #FFFF";
|
String input = "0 800L 3.1415 0xCAFEBABE 0Xf7_d6_c5 #FFFF 0x7FL";
|
||||||
List<Token> result = Lexer.tokenize(input);
|
List<Token> result = Lexer.tokenize(input);
|
||||||
assertTokens(result, NUMBER, DECIMAL_NUMBER, HEX_NUMBER, HEX_NUMBER, HEX_NUMBER);
|
assertTokens(result, NUMBER, LONG_NUMBER, DECIMAL_NUMBER, HEX_NUMBER, HEX_NUMBER, HEX_NUMBER, HEX_LONG_NUMBER);
|
||||||
assertThat(result)
|
assertThat(result)
|
||||||
.extracting(Token::text)
|
.extracting(Token::text)
|
||||||
.containsExactly("0", "3.1415", "CAFEBABE", "f7d6c5", "FFFF");
|
.containsExactly("0", "800", "3.1415", "CAFEBABE", "f7d6c5", "FFFF", "7F");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDecimalNumbersExponent() {
|
void testDecimalNumbersExponent() {
|
||||||
String input = "4e+7 0.3E-19 2e0 5e0000000000000200 5E-000000089";
|
String input = "4e+7 0.3E-19 2e0 5e0000000000000200 5E-000000089";
|
||||||
List<Token> result = Lexer.tokenize(input);
|
List<Token> result = Lexer.tokenize(input);
|
||||||
assertThat(result)
|
assertThat(result)
|
||||||
@ -61,7 +61,7 @@ public class LexerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testString() {
|
void testString() {
|
||||||
String input = "\"1\\\"2\"";
|
String input = "\"1\\\"2\"";
|
||||||
List<Token> result = Lexer.tokenize(input);
|
List<Token> result = Lexer.tokenize(input);
|
||||||
assertTokens(result, TEXT);
|
assertTokens(result, TEXT);
|
||||||
@ -69,7 +69,7 @@ public class LexerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEscapeString() {
|
void testEscapeString() {
|
||||||
String input = """
|
String input = """
|
||||||
"\\\\/\\\\"
|
"\\\\/\\\\"
|
||||||
""".stripIndent();
|
""".stripIndent();
|
||||||
@ -79,7 +79,7 @@ public class LexerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEmptyString() {
|
void testEmptyString() {
|
||||||
String input = "\"\"";
|
String input = "\"\"";
|
||||||
List<Token> result = Lexer.tokenize(input);
|
List<Token> result = Lexer.tokenize(input);
|
||||||
assertTokens(result, TEXT);
|
assertTokens(result, TEXT);
|
||||||
@ -87,7 +87,7 @@ public class LexerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testComments() {
|
void testComments() {
|
||||||
String input = "// 1234 \n /* */ 123 /* \n 12345 \n\n\n */";
|
String input = "// 1234 \n /* */ 123 /* \n 12345 \n\n\n */";
|
||||||
List<Token> result = Lexer.tokenize(input);
|
List<Token> result = Lexer.tokenize(input);
|
||||||
assertTokens(result, NUMBER);
|
assertTokens(result, NUMBER);
|
||||||
@ -96,7 +96,7 @@ public class LexerTest {
|
|||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("validData")
|
@MethodSource("validData")
|
||||||
public void testValidInput(String name, String input, List<TokenType> tokenTypes) throws IOException {
|
void testValidInput(String name, String input, List<TokenType> tokenTypes) throws IOException {
|
||||||
List<Token> result = Lexer.tokenize(input);
|
List<Token> result = Lexer.tokenize(input);
|
||||||
assertThat(result)
|
assertThat(result)
|
||||||
.hasSize(tokenTypes.size())
|
.hasSize(tokenTypes.size())
|
||||||
@ -106,7 +106,7 @@ public class LexerTest {
|
|||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("invalidData")
|
@MethodSource("invalidData")
|
||||||
public void testInvalidInput(String name, String input) throws IOException {
|
void testInvalidInput(String name, String input) throws IOException {
|
||||||
assertThatThrownBy(() -> Lexer.tokenize(input))
|
assertThatThrownBy(() -> Lexer.tokenize(input))
|
||||||
.isInstanceOf(OwnLangParserException.class);
|
.isInstanceOf(OwnLangParserException.class);
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,10 @@ public class LexerValidDataProvider {
|
|||||||
List.of(DECIMAL_NUMBER)),
|
List.of(DECIMAL_NUMBER)),
|
||||||
Arguments.of("Hex numbers",
|
Arguments.of("Hex numbers",
|
||||||
"#FF 0xCA 0x12fb 0xFF",
|
"#FF 0xCA 0x12fb 0xFF",
|
||||||
List.of(HEX_NUMBER, HEX_NUMBER, HEX_NUMBER, HEX_NUMBER))
|
List.of(HEX_NUMBER, HEX_NUMBER, HEX_NUMBER, HEX_NUMBER)),
|
||||||
|
Arguments.of("Long numbers",
|
||||||
|
"680L #80L 0x700L",
|
||||||
|
List.of(LONG_NUMBER, HEX_LONG_NUMBER, HEX_LONG_NUMBER))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ public class ProgramsTest {
|
|||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("data")
|
@MethodSource("data")
|
||||||
public void testProgram(InputSource inputSource) {
|
void testProgram(InputSource inputSource) {
|
||||||
final StagesDataMap stagesData = new StagesDataMap();
|
final StagesDataMap stagesData = new StagesDataMap();
|
||||||
try {
|
try {
|
||||||
testPipeline.perform(stagesData, inputSource);
|
testPipeline.perform(stagesData, inputSource);
|
||||||
|
@ -14,6 +14,16 @@ def testMultiplicationOnNumbers() {
|
|||||||
assertEquals(30, 5 * (-2 * -3))
|
assertEquals(30, 5 * (-2 * -3))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def testMultiplicationOverflowOnNumbers() {
|
||||||
|
assertNotEquals(1234567890000L, 1234567890 * 1000)
|
||||||
|
assertNotEquals(0xFFFFFF00, 0x100 * 0xFFFFFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
def testMultiplicationOnLongNumbers() {
|
||||||
|
assertEquals(1234567890000L, 1234567890 * 1000L)
|
||||||
|
assertEquals(0xFFFFFF00L, 0x100L * 0xFFFFFF)
|
||||||
|
}
|
||||||
|
|
||||||
def testDivisionOnNumbers() {
|
def testDivisionOnNumbers() {
|
||||||
assertEquals(3, 6 / 2)
|
assertEquals(3, 6 / 2)
|
||||||
assertEquals(30, -900 / (60 / -2))
|
assertEquals(30, -900 / (60 / -2))
|
||||||
|
Loading…
Reference in New Issue
Block a user