Use ranges in parser

This commit is contained in:
aNNiMON 2023-09-17 18:34:11 +03:00 committed by Victor Melnik
parent da950f96c2
commit 312a05576c
8 changed files with 92 additions and 46 deletions

View File

@ -12,7 +12,7 @@ public final class LexerException extends RuntimeException {
super(message); super(message);
} }
public LexerException(Pos pos, String message) { public LexerException(String message, Pos pos) {
super(pos.format() + " " + message); super(pos.format() + " " + message);
} }
} }

View File

@ -1,6 +1,7 @@
package com.annimon.ownlang.exceptions; package com.annimon.ownlang.exceptions;
import com.annimon.ownlang.parser.Pos; import com.annimon.ownlang.parser.Pos;
import com.annimon.ownlang.parser.Range;
/** /**
* *
@ -8,11 +9,10 @@ import com.annimon.ownlang.parser.Pos;
*/ */
public final class ParseException extends RuntimeException { public final class ParseException extends RuntimeException {
private final Pos start; private final Range range;
private final Pos end;
public ParseException(String message) { public ParseException(String message) {
this(message, Pos.ZERO, Pos.ZERO); this(message, Range.ZERO);
} }
public ParseException(String message, Pos pos) { public ParseException(String message, Pos pos) {
@ -20,16 +20,15 @@ public final class ParseException extends RuntimeException {
} }
public ParseException(String message, Pos start, Pos end) { public ParseException(String message, Pos start, Pos end) {
this(message, new Range(start, end));
}
public ParseException(String message, Range range) {
super(message); super(message);
this.start = start; this.range = range;
this.end = end;
} }
public Pos getStart() { public Range getRange() {
return start; return range;
}
public Pos getEnd() {
return end;
} }
} }

View File

@ -411,6 +411,6 @@ public final class Lexer {
} }
private LexerException error(String text) { private LexerException error(String text) {
return new LexerException(markPos(), text); return new LexerException(text, markPos());
} }
} }

View File

@ -1,9 +1,19 @@
package com.annimon.ownlang.parser; package com.annimon.ownlang.parser;
public record ParseError(Exception exception, Pos pos) { import java.util.Collections;
import java.util.List;
public record ParseError(String message, Range range, List<StackTraceElement> stackTraceElements) {
public ParseError(String message, Range range) {
this(message, range, Collections.emptyList());
}
public boolean hasStackTrace() {
return !stackTraceElements.isEmpty();
}
@Override @Override
public String toString() { public String toString() {
return "Error on line " + pos.row() + ": " + exception; return "Error on line " + range().start().row() + ": " + message;
} }
} }

View File

@ -17,8 +17,8 @@ public final class ParseErrors implements Iterable<ParseError> {
errors.clear(); errors.clear();
} }
public void add(Exception ex, Pos pos) { public void add(ParseError parseError) {
errors.add(new ParseError(ex, pos)); errors.add(parseError);
} }
public boolean hasErrors() { public boolean hasErrors() {

View File

@ -24,7 +24,7 @@ public final class Parser {
return program; return program;
} }
private static final Token EOF = new Token(TokenType.EOF, "", new Pos(-1, -1)); private static final Token EOF = new Token(TokenType.EOF, "", Pos.UNKNOWN);
private static final EnumMap<TokenType, BinaryExpression.Operator> ASSIGN_OPERATORS; private static final EnumMap<TokenType, BinaryExpression.Operator> ASSIGN_OPERATORS;
static { static {
@ -50,7 +50,7 @@ public final class Parser {
private final ParseErrors parseErrors; private final ParseErrors parseErrors;
private Statement parsedStatement; private Statement parsedStatement;
private int pos; private int index;
public Parser(List<Token> tokens) { public Parser(List<Token> tokens) {
this.tokens = tokens; this.tokens = tokens;
@ -72,11 +72,11 @@ public final class Parser {
while (!match(TokenType.EOF)) { while (!match(TokenType.EOF)) {
try { try {
result.add(statement()); result.add(statement());
} catch (ParseException parseException) { } catch (ParseException ex) {
parseErrors.add(parseException, parseException.getStart()); parseErrors.add(new ParseError(ex.getMessage(), ex.getRange()));
recover(); recover();
} catch (Exception ex) { } catch (Exception ex) {
parseErrors.add(ex, getPos()); parseErrors.add(new ParseError(ex.getMessage(), getRange(), List.of(ex.getStackTrace())));
recover(); recover();
} }
} }
@ -84,20 +84,14 @@ public final class Parser {
return result; return result;
} }
private Pos getPos() {
if (size == 0) return new Pos(0, 0);
if (pos >= size) return tokens.get(size - 1).pos();
return tokens.get(pos).pos();
}
private void recover() { private void recover() {
int preRecoverPosition = pos; int preRecoverIndex = index;
for (int i = preRecoverPosition; i <= size; i++) { for (int i = preRecoverIndex; i <= size; i++) {
pos = i; index = i;
try { try {
statement(); statement();
// successfully parsed, // successfully parsed,
pos = i; // restore position index = i; // restore position
return; return;
} catch (Exception ex) { } catch (Exception ex) {
// fail // fail
@ -190,7 +184,7 @@ public final class Parser {
private DestructuringAssignmentStatement destructuringAssignment() { private DestructuringAssignmentStatement destructuringAssignment() {
// extract(var1, var2, ...) = ... // extract(var1, var2, ...) = ...
final var startPos = getPos(); final var startTokenIndex = index;
consume(TokenType.LPAREN); consume(TokenType.LPAREN);
final List<String> variables = new ArrayList<>(); final List<String> variables = new ArrayList<>();
while (!match(TokenType.RPAREN)) { while (!match(TokenType.RPAREN)) {
@ -203,7 +197,7 @@ public final class Parser {
match(TokenType.COMMA); match(TokenType.COMMA);
} }
if (variables.isEmpty() || variables.stream().allMatch(Objects::isNull)) { if (variables.isEmpty() || variables.stream().allMatch(Objects::isNull)) {
throw error(errorDestructuringAssignmentEmpty(), startPos, getPos()); throw error(errorDestructuringAssignmentEmpty(), startTokenIndex, index);
} }
consume(TokenType.EQ); consume(TokenType.EQ);
return new DestructuringAssignmentStatement(variables, expression()); return new DestructuringAssignmentStatement(variables, expression());
@ -493,16 +487,16 @@ public final class Parser {
private AssignmentExpression assignmentStrict() { private AssignmentExpression assignmentStrict() {
// x[0].prop += ... // x[0].prop += ...
final int position = pos; final int position = index;
final Expression targetExpr = qualifiedName(); final Expression targetExpr = qualifiedName();
if (!(targetExpr instanceof Accessible)) { if (!(targetExpr instanceof Accessible)) {
pos = position; index = position;
return null; return null;
} }
final TokenType currentType = get(0).type(); final TokenType currentType = get(0).type();
if (!ASSIGN_OPERATORS.containsKey(currentType)) { if (!ASSIGN_OPERATORS.containsKey(currentType)) {
pos = position; index = position;
return null; return null;
} }
match(currentType); match(currentType);
@ -905,7 +899,7 @@ public final class Parser {
if (expectedType != actual.type()) { if (expectedType != actual.type()) {
throw error(errorUnexpectedToken(actual, expectedType)); throw error(errorUnexpectedToken(actual, expectedType));
} }
pos++; index++;
return actual; return actual;
} }
@ -915,7 +909,7 @@ public final class Parser {
throw error(errorUnexpectedToken(actual, expectedType) throw error(errorUnexpectedToken(actual, expectedType)
+ errorMessageFunction.apply(actual)); + errorMessageFunction.apply(actual));
} }
pos++; index++;
return actual; return actual;
} }
@ -924,7 +918,7 @@ public final class Parser {
if (type != current.type()) { if (type != current.type()) {
return false; return false;
} }
pos++; index++;
return true; return true;
} }
@ -933,17 +927,33 @@ public final class Parser {
} }
private Token get(int relativePosition) { private Token get(int relativePosition) {
final int position = pos + relativePosition; final int position = index + relativePosition;
if (position >= size) return EOF; if (position >= size) return EOF;
return tokens.get(position); return tokens.get(position);
} }
private ParseException error(String message) { private Range getRange() {
return new ParseException(message, getPos()); return getRange(index, index);
} }
private static ParseException error(String message, Pos start, Pos end) { private Range getRange(int startIndex, int endIndex) {
return new ParseException(message, start, end); if (size == 0) return Range.ZERO;
final int last = size - 1;
Pos start = tokens.get(Math.min(startIndex, last)).pos();
if (startIndex == endIndex) {
return new Range(start, start);
} else {
Pos end = tokens.get(Math.min(endIndex, last)).pos();
return new Range(start, end);
}
}
private ParseException error(String message) {
return new ParseException(message, getRange());
}
private ParseException error(String message, int startIndex, int endIndex) {
return new ParseException(message, getRange(startIndex, endIndex));
} }
private static String errorUnexpectedToken(Token actual, TokenType expectedType) { private static String errorUnexpectedToken(Token actual, TokenType expectedType) {

View File

@ -0,0 +1,27 @@
package com.annimon.ownlang.parser;
import java.util.Objects;
public record Range(Pos start, Pos end) {
public static final Range ZERO = new Range(Pos.ZERO, Pos.ZERO);
public Range normalize() {
return new Range(start.normalize(), end.normalize());
}
public boolean isEqualPosition() {
return Objects.equals(start, end);
}
public boolean isOnSameLine() {
return start.row() == end.row();
}
public String format() {
if (isOnSameLine()) {
return start.format();
} else {
return start.format() + "..." + end.format();
}
}
}

View File

@ -33,7 +33,7 @@ public final class Repl {
RESET = ":reset", RESET = ":reset",
EXIT = ":exit"; EXIT = ":exit";
private static final Token PRINTLN_TOKEN = new Token(TokenType.PRINTLN, "", new Pos(0, 0)); private static final Token PRINTLN_TOKEN = new Token(TokenType.PRINTLN, "", Pos.ZERO);
public static void main(String[] args) { public static void main(String[] args) {
System.out.println("Welcome to OwnLang " + Version.VERSION + " REPL"); System.out.println("Welcome to OwnLang " + Version.VERSION + " REPL");