[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.
|
* 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
|
@Override
|
||||||
public String toString() {
|
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 */
|
/** The string representation of the token */
|
||||||
public String lexeme;
|
public String lexeme;
|
||||||
|
|
||||||
public int line = 1, character = -1;
|
|
||||||
public char ch;
|
public char ch;
|
||||||
|
|
||||||
private final String source;
|
public final String source;
|
||||||
private int start, current;
|
public int start, current;
|
||||||
|
|
||||||
public Lexer(String source) {
|
public Lexer(String source) {
|
||||||
this.source = source;
|
this.source = source;
|
||||||
@ -35,8 +34,8 @@ public class Lexer {
|
|||||||
|
|
||||||
char c = advance();
|
char c = advance();
|
||||||
|
|
||||||
if (isDigit(c) || (c == '-' && isDigit(peek()))) number();
|
if (isDigit(c)) number();
|
||||||
else if (isAlpha(c)) identifier();
|
else if (isIdentifier(c)) identifier();
|
||||||
else {
|
else {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '\'', '"' -> string(c);
|
case '\'', '"' -> string(c);
|
||||||
@ -72,7 +71,6 @@ public class Lexer {
|
|||||||
|
|
||||||
private void string(char stringChar) {
|
private void string(char stringChar) {
|
||||||
while (!isAtEnd() && peek() != stringChar) {
|
while (!isAtEnd() && peek() != stringChar) {
|
||||||
if (peek() == '\n') line++;
|
|
||||||
advance();
|
advance();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +98,7 @@ public class Lexer {
|
|||||||
private void identifier() {
|
private void identifier() {
|
||||||
while (!isAtEnd()) {
|
while (!isAtEnd()) {
|
||||||
char c = peek();
|
char c = peek();
|
||||||
if (isAlpha(c) || isDigit(c)) {
|
if (isIdentifier(c) || isDigit(c)) {
|
||||||
advance();
|
advance();
|
||||||
} else if (c == ':' && peekNext() == ':') {
|
} else if (c == ':' && peekNext() == ':') {
|
||||||
advance();
|
advance();
|
||||||
@ -123,11 +121,7 @@ public class Lexer {
|
|||||||
char c = peek();
|
char c = peek();
|
||||||
|
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case ' ', '\r', '\t' -> advance();
|
case ' ', '\r', '\t', '\n' -> advance();
|
||||||
case '\n' -> {
|
|
||||||
line++;
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
default -> {
|
default -> {
|
||||||
start = current;
|
start = current;
|
||||||
return;
|
return;
|
||||||
@ -139,7 +133,7 @@ public class Lexer {
|
|||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
private void unexpected() {
|
private void unexpected() {
|
||||||
createToken(Token.Error, "Unexpected character.");
|
createToken(Token.Error, "Unexpected character");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createToken(Token token, String lexeme) {
|
private void createToken(Token token, String lexeme) {
|
||||||
@ -160,7 +154,6 @@ public class Lexer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private char advance() {
|
private char advance() {
|
||||||
character++;
|
|
||||||
return ch = source.charAt(current++);
|
return ch = source.charAt(current++);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,7 +175,7 @@ public class Lexer {
|
|||||||
return c >= '0' && c <= '9';
|
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 == '$';
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '$';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,7 +230,7 @@ public class Parser {
|
|||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
private ParseException error(String message) {
|
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) {
|
private TokenData consume(Token token, String message) {
|
||||||
@ -258,7 +258,7 @@ public class Parser {
|
|||||||
previous.set(current);
|
previous.set(current);
|
||||||
|
|
||||||
lexer.next();
|
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) {
|
if (current.token == Token.Error) {
|
||||||
throw error(current.lexeme);
|
throw error(current.lexeme);
|
||||||
@ -275,19 +275,19 @@ public class Parser {
|
|||||||
private static class TokenData {
|
private static class TokenData {
|
||||||
public Token token;
|
public Token token;
|
||||||
public String lexeme;
|
public String lexeme;
|
||||||
public int line, character;
|
public int start, current;
|
||||||
public char ch;
|
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.token = token;
|
||||||
this.lexeme = lexeme;
|
this.lexeme = lexeme;
|
||||||
this.line = line;
|
this.start = start;
|
||||||
this.character = character;
|
this.current = current;
|
||||||
this.ch = ch;
|
this.ch = ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set(TokenData data) {
|
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
|
@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;
|
package io.gitlab.jfronny.muscript.test;
|
||||||
|
|
||||||
import io.gitlab.jfronny.muscript.compiler.*;
|
|
||||||
import org.junit.jupiter.api.*;
|
import org.junit.jupiter.api.*;
|
||||||
|
|
||||||
import static io.gitlab.jfronny.muscript.test.MuTestUtil.*;
|
import static io.gitlab.jfronny.muscript.test.MuTestUtil.*;
|
||||||
@ -15,11 +14,4 @@ public class StringTest {
|
|||||||
assertTrue(bool("string == 'Value'"));
|
assertTrue(bool("string == 'Value'"));
|
||||||
assertFalse(bool("string == 'Something else'"));
|
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