[muscript] Slightly better debugging experience
This commit is contained in:
parent
626580af63
commit
ed680f57a9
@ -3,9 +3,29 @@ package io.gitlab.jfronny.muscript.compiler;
|
||||
/**
|
||||
* Class for storing errors produced while parsing.
|
||||
*/
|
||||
public record Error(int line, int character, char ch, String message) {
|
||||
public record Error(String source, int character, String message) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[line %d, character %d] at '%s': %s", line, character, ch, message);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (character >= source.length()) {
|
||||
builder.append("Error at character ").append(character);
|
||||
}
|
||||
else {
|
||||
builder.append("Error at '").append(source.charAt(character)).append("' (character ").append(character).append(")");
|
||||
}
|
||||
builder.append(": ").append(message);
|
||||
|
||||
int lineStart = source.lastIndexOf('\n', character);
|
||||
int lineEnd = source.indexOf('\n', character);
|
||||
if (lineEnd == -1) lineEnd = source.length();
|
||||
|
||||
String line = source.substring(lineStart + 1, lineEnd);
|
||||
int lineNumber = lineStart > 0 ? (int) source.substring(0, lineStart).chars().filter(c -> c == '\n').count() : 1;
|
||||
|
||||
String linePrefix = String.format("%1$6d", lineNumber) + " | ";
|
||||
|
||||
builder.append('\n').append(linePrefix).append(line);
|
||||
builder.append('\n').append(" ".repeat(linePrefix.length() + (character - lineStart - 1))).append("^-- Here");
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,10 @@ public class Lexer {
|
||||
/** The string representation of the token */
|
||||
public String lexeme;
|
||||
|
||||
public int line = 1, character = -1;
|
||||
public char ch;
|
||||
|
||||
private final String source;
|
||||
private int start, current;
|
||||
public final String source;
|
||||
public int start, current;
|
||||
|
||||
public Lexer(String source) {
|
||||
this.source = source;
|
||||
@ -35,8 +34,8 @@ public class Lexer {
|
||||
|
||||
char c = advance();
|
||||
|
||||
if (isDigit(c) || (c == '-' && isDigit(peek()))) number();
|
||||
else if (isAlpha(c)) identifier();
|
||||
if (isDigit(c)) number();
|
||||
else if (isIdentifier(c)) identifier();
|
||||
else {
|
||||
switch (c) {
|
||||
case '\'', '"' -> string(c);
|
||||
@ -72,7 +71,6 @@ public class Lexer {
|
||||
|
||||
private void string(char stringChar) {
|
||||
while (!isAtEnd() && peek() != stringChar) {
|
||||
if (peek() == '\n') line++;
|
||||
advance();
|
||||
}
|
||||
|
||||
@ -100,7 +98,7 @@ public class Lexer {
|
||||
private void identifier() {
|
||||
while (!isAtEnd()) {
|
||||
char c = peek();
|
||||
if (isAlpha(c) || isDigit(c)) {
|
||||
if (isIdentifier(c) || isDigit(c)) {
|
||||
advance();
|
||||
} else if (c == ':' && peekNext() == ':') {
|
||||
advance();
|
||||
@ -123,11 +121,7 @@ public class Lexer {
|
||||
char c = peek();
|
||||
|
||||
switch (c) {
|
||||
case ' ', '\r', '\t' -> advance();
|
||||
case '\n' -> {
|
||||
line++;
|
||||
advance();
|
||||
}
|
||||
case ' ', '\r', '\t', '\n' -> advance();
|
||||
default -> {
|
||||
start = current;
|
||||
return;
|
||||
@ -139,7 +133,7 @@ public class Lexer {
|
||||
// Helpers
|
||||
|
||||
private void unexpected() {
|
||||
createToken(Token.Error, "Unexpected character.");
|
||||
createToken(Token.Error, "Unexpected character");
|
||||
}
|
||||
|
||||
private void createToken(Token token, String lexeme) {
|
||||
@ -160,7 +154,6 @@ public class Lexer {
|
||||
}
|
||||
|
||||
private char advance() {
|
||||
character++;
|
||||
return ch = source.charAt(current++);
|
||||
}
|
||||
|
||||
@ -182,7 +175,7 @@ public class Lexer {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
private boolean isAlpha(char c) {
|
||||
private boolean isIdentifier(char c) {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '$';
|
||||
}
|
||||
}
|
||||
|
@ -230,7 +230,7 @@ public class Parser {
|
||||
|
||||
// Helpers
|
||||
private ParseException error(String message) {
|
||||
return new ParseException(new Error(current.line, current.character, current.ch, message));
|
||||
return new ParseException(new Error(lexer.source, current.current - 1, message));
|
||||
}
|
||||
|
||||
private TokenData consume(Token token, String message) {
|
||||
@ -258,7 +258,7 @@ public class Parser {
|
||||
previous.set(current);
|
||||
|
||||
lexer.next();
|
||||
current.set(lexer.token, lexer.lexeme, lexer.line, lexer.character, lexer.ch);
|
||||
current.set(lexer.token, lexer.lexeme, lexer.start, lexer.current, lexer.ch);
|
||||
|
||||
if (current.token == Token.Error) {
|
||||
throw error(current.lexeme);
|
||||
@ -275,19 +275,19 @@ public class Parser {
|
||||
private static class TokenData {
|
||||
public Token token;
|
||||
public String lexeme;
|
||||
public int line, character;
|
||||
public int start, current;
|
||||
public char ch;
|
||||
|
||||
public void set(Token token, String lexeme, int line, int character, char ch) {
|
||||
public void set(Token token, String lexeme, int start, int current, char ch) {
|
||||
this.token = token;
|
||||
this.lexeme = lexeme;
|
||||
this.line = line;
|
||||
this.character = character;
|
||||
this.start = start;
|
||||
this.current = current;
|
||||
this.ch = ch;
|
||||
}
|
||||
|
||||
public void set(TokenData data) {
|
||||
set(data.token, data.lexeme, data.line, data.character, data.ch);
|
||||
set(data.token, data.lexeme, data.start, data.current, data.ch);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,27 @@
|
||||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import io.gitlab.jfronny.muscript.compiler.*;
|
||||
import org.junit.jupiter.api.*;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class ErrorTest {
|
||||
@Test
|
||||
void invalidCode() {
|
||||
assertEquals("""
|
||||
Error at 'e' (character 8): Expected number but is Boolean
|
||||
1 | 15 + true
|
||||
^-- Here""",
|
||||
assertThrows(Parser.ParseException.class, () -> Parser.parse("15 + true")).error.toString());
|
||||
assertEquals("""
|
||||
Error at ''' (character 9): Expected number but is String
|
||||
1 | 15 + 'yes'
|
||||
^-- Here""",
|
||||
assertThrows(Parser.ParseException.class, () -> Parser.parse("15 + 'yes'")).error.toString());
|
||||
assertEquals("""
|
||||
Error at '=' (character 7): Unexpected character
|
||||
1 | string = 'Value'
|
||||
^-- Here""",
|
||||
assertThrows(Parser.ParseException.class, () -> Parser.parse("string = 'Value'")).error.toString());
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import io.gitlab.jfronny.muscript.compiler.*;
|
||||
import org.junit.jupiter.api.*;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.test.MuTestUtil.*;
|
||||
@ -15,11 +14,4 @@ public class StringTest {
|
||||
assertTrue(bool("string == 'Value'"));
|
||||
assertFalse(bool("string == 'Something else'"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidCode() {
|
||||
assertThrows(Parser.ParseException.class, () -> Parser.parse("15 + true"));
|
||||
assertThrows(Parser.ParseException.class, () -> Parser.parse("15 + true"));
|
||||
assertThrows(Parser.ParseException.class, () -> Parser.parse("string = 'Value'"));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user