implements PR-11.3

This commit is contained in:
bQUARKz 2026-03-10 09:57:04 +00:00
parent abcc099334
commit ea44d40410
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
2 changed files with 345 additions and 245 deletions

View File

@ -22,6 +22,7 @@ final class PbsExprParser {
private final PbsExprParserContext context;
private final PbsTokenCursor cursor;
private final PbsExprControlFlowParser controlFlowParser;
private final PbsExprPrimaryParser primaryParser;
PbsExprParser(
final PbsParserContext parserContext,
@ -29,6 +30,7 @@ final class PbsExprParser {
this.context = new PbsExprParserContext(parserContext, blockParserDelegate);
this.cursor = context.cursor();
this.controlFlowParser = new PbsExprControlFlowParser(context, this::parseExpression, this::parsePrecedenceExpression);
this.primaryParser = new PbsExprPrimaryParser(context, this::parseExpression, controlFlowParser::parseErrorPath);
}
/**
@ -172,260 +174,19 @@ final class PbsExprParser {
final var right = parseUnary();
return new PbsAst.UnaryExpr(operator.lexeme(), right, span(operator.start(), right.span().getEnd()));
}
return parsePostfix();
return primaryParser.parsePostfix();
}
private PbsAst.Expression parsePostfix() {
var expression = parsePrimary();
while (true) {
if (cursor.match(PbsTokenKind.LEFT_PAREN)) {
final var arguments = new ArrayList<PbsAst.Expression>();
if (!cursor.check(PbsTokenKind.RIGHT_PAREN)) {
do {
arguments.add(parseExpression());
} while (cursor.match(PbsTokenKind.COMMA));
}
final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after call arguments");
expression = new PbsAst.CallExpr(expression, ReadOnlyList.wrap(arguments),
span(expression.span().getStart(), close.end()));
continue;
}
if (cursor.match(PbsTokenKind.DOT)) {
final var member = consumeMemberName("Expected member name after '.'");
expression = new PbsAst.MemberExpr(
expression,
member.lexeme(),
span(expression.span().getStart(), member.end()));
continue;
}
if (cursor.match(PbsTokenKind.BANG)) {
final var bang = cursor.previous();
expression = new PbsAst.PropagateExpr(expression, span(expression.span().getStart(), bang.end()));
continue;
}
if (cursor.match(PbsTokenKind.QUESTION)) {
final var question = cursor.previous();
report(question, ParseErrors.E_PARSE_INVALID_PROPAGATE_OPERATOR,
"Use '!' as propagation operator; '?' is not valid PBS syntax");
continue;
}
return expression;
}
return primaryParser.parsePostfix();
}
private PbsAst.Expression parsePrimary() {
if (cursor.match(PbsTokenKind.TRUE)) {
final var token = cursor.previous();
return new PbsAst.BoolLiteralExpr(true, span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.FALSE)) {
final var token = cursor.previous();
return new PbsAst.BoolLiteralExpr(false, span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.INT_LITERAL)) {
final var token = cursor.previous();
return new PbsAst.IntLiteralExpr(parseLongOrDefault(token.lexeme()), span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.FLOAT_LITERAL)) {
final var token = cursor.previous();
return new PbsAst.FloatLiteralExpr(parseDoubleOrDefault(token.lexeme()), span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.BOUNDED_LITERAL)) {
final var token = cursor.previous();
final var raw = token.lexeme().substring(0, Math.max(token.lexeme().length() - 1, 0));
return new PbsAst.BoundedLiteralExpr(parseIntOrDefault(raw), span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.STRING_LITERAL)) {
final var token = cursor.previous();
return new PbsAst.StringLiteralExpr(unescapeString(token.lexeme()), span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.THIS)) {
final var token = cursor.previous();
return new PbsAst.ThisExpr(span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.NEW)) {
return parseNewExpression(cursor.previous());
}
if (cursor.match(PbsTokenKind.BIND)) {
return parseBindExpression(cursor.previous());
}
if (cursor.match(PbsTokenKind.SOME)) {
return parseSomeExpression(cursor.previous());
}
if (cursor.match(PbsTokenKind.NONE)) {
final var token = cursor.previous();
return new PbsAst.NoneExpr(span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.OK)) {
return parseOkExpression(cursor.previous());
}
if (cursor.match(PbsTokenKind.ERR)) {
return parseErrExpression(cursor.previous());
}
if (cursor.match(PbsTokenKind.IDENTIFIER)) {
final var token = cursor.previous();
return new PbsAst.IdentifierExpr(token.lexeme(), span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.LEFT_PAREN)) {
return parseParenthesizedPrimary(cursor.previous());
}
if (cursor.check(PbsTokenKind.LEFT_BRACE)) {
final var block = parseSurfaceBlock("Expected block expression");
return new PbsAst.BlockExpr(block, block.span());
}
final var token = cursor.peek();
report(token, ParseErrors.E_PARSE_UNEXPECTED_TOKEN, "Unexpected token in expression: " + token.kind());
cursor.advance();
return new PbsAst.IntLiteralExpr(0L, span(token.start(), token.end()));
return primaryParser.parsePrimary();
}
private PbsAst.Expression parseParenthesizedPrimary(final PbsToken open) {
if (cursor.match(PbsTokenKind.RIGHT_PAREN)) {
final var close = cursor.previous();
return new PbsAst.UnitExpr(span(open.start(), close.end()));
}
if (cursor.check(PbsTokenKind.IDENTIFIER) && cursor.checkNext(PbsTokenKind.COLON)) {
final var items = parseTupleItems(true);
final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after tuple literal");
if (items.size() < 2) {
report(close, ParseErrors.E_PARSE_INVALID_TUPLE_LITERAL,
"Single-slot tuple literal is not allowed in PBS core syntax");
}
if (items.size() > MAX_TUPLE_LITERAL_ARITY) {
report(close, ParseErrors.E_PARSE_INVALID_TUPLE_LITERAL,
"Tuple literal arity must be between 2 and 6 items");
}
return new PbsAst.TupleExpr(ReadOnlyList.wrap(items), span(open.start(), close.end()));
}
final var first = parseExpression();
if (!cursor.match(PbsTokenKind.COMMA)) {
final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after grouped expression");
return new PbsAst.GroupExpr(first, span(open.start(), close.end()));
}
final var items = new ArrayList<PbsAst.TupleItem>();
items.add(new PbsAst.TupleItem(null, first, first.span()));
var hasLabels = false;
while (!cursor.check(PbsTokenKind.RIGHT_PAREN) && !cursor.isAtEnd()) {
final var item = parseTupleItem();
if (item.label() != null) {
hasLabels = true;
}
items.add(item);
if (!cursor.match(PbsTokenKind.COMMA)) {
break;
}
}
if (items.size() < 2) {
report(cursor.peek(), ParseErrors.E_PARSE_INVALID_TUPLE_LITERAL,
"Tuple literals require at least two items");
}
final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after tuple literal");
if (items.size() > MAX_TUPLE_LITERAL_ARITY) {
report(close, ParseErrors.E_PARSE_INVALID_TUPLE_LITERAL,
"Tuple literal arity must be between 2 and 6 items");
}
if (hasLabels) {
for (final var item : items) {
if (item.label() == null) {
report(close, ParseErrors.E_PARSE_INVALID_TUPLE_LITERAL,
"Mixed labeled/unlabeled tuple items are not allowed");
break;
}
}
}
return new PbsAst.TupleExpr(ReadOnlyList.wrap(items), span(open.start(), close.end()));
}
private ArrayList<PbsAst.TupleItem> parseTupleItems(final boolean labeledOnly) {
final var items = new ArrayList<PbsAst.TupleItem>();
do {
final var item = parseTupleItem();
items.add(item);
if (labeledOnly && item.label() == null) {
report(cursor.previous(), ParseErrors.E_PARSE_INVALID_TUPLE_LITERAL,
"Named tuple literal items must use labels");
}
} while (cursor.match(PbsTokenKind.COMMA) && !cursor.check(PbsTokenKind.RIGHT_PAREN));
return items;
}
private PbsAst.TupleItem parseTupleItem() {
if (cursor.check(PbsTokenKind.IDENTIFIER) && cursor.checkNext(PbsTokenKind.COLON)) {
final var label = cursor.advance();
consume(PbsTokenKind.COLON, "Expected ':' after tuple label");
final var value = parseExpression();
return new PbsAst.TupleItem(label.lexeme(), value, span(label.start(), value.span().getEnd()));
}
final var value = parseExpression();
return new PbsAst.TupleItem(null, value, value.span());
}
private PbsAst.Expression parseNewExpression(final PbsToken newToken) {
final var typeName = consume(PbsTokenKind.IDENTIFIER, "Expected type name after 'new'");
String ctorName = null;
if (cursor.match(PbsTokenKind.DOT)) {
ctorName = consume(PbsTokenKind.IDENTIFIER, "Expected constructor name after '.' in new target").lexeme();
}
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after new target");
final var arguments = new ArrayList<PbsAst.Expression>();
if (!cursor.check(PbsTokenKind.RIGHT_PAREN)) {
do {
arguments.add(parseExpression());
} while (cursor.match(PbsTokenKind.COMMA));
}
final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after new arguments");
return new PbsAst.NewExpr(
typeName.lexeme(),
ctorName,
ReadOnlyList.wrap(arguments),
span(newToken.start(), close.end()));
}
private PbsAst.Expression parseBindExpression(final PbsToken bindToken) {
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after 'bind'");
final var contextExpression = parseExpression();
consume(PbsTokenKind.COMMA, "Expected ',' in bind(context, fn_name)");
final var functionName = consume(PbsTokenKind.IDENTIFIER, "Expected function identifier in bind(context, fn_name)");
final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after bind arguments");
return new PbsAst.BindExpr(
contextExpression,
functionName.lexeme(),
span(bindToken.start(), close.end()));
}
private PbsAst.Expression parseSomeExpression(final PbsToken someToken) {
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after 'some'");
final var value = parseExpression();
final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after some(payload)");
return new PbsAst.SomeExpr(value, span(someToken.start(), close.end()));
}
private PbsAst.Expression parseOkExpression(final PbsToken okToken) {
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after 'ok'");
final var value = parseExpression();
final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after ok(payload)");
return new PbsAst.OkExpr(value, span(okToken.start(), close.end()));
}
private PbsAst.Expression parseErrExpression(final PbsToken errToken) {
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after 'err'");
final var errorPath = parseErrorPath();
final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after err(Error.case)");
return new PbsAst.ErrExpr(errorPath, span(errToken.start(), close.end()));
return primaryParser.parseParenthesizedPrimary(open);
}
private PbsAst.SwitchPattern parseSwitchPattern() {

View File

@ -0,0 +1,339 @@
package p.studio.compiler.pbs.parser;
import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.pbs.lexer.PbsToken;
import p.studio.compiler.pbs.lexer.PbsTokenKind;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.ArrayList;
final class PbsExprPrimaryParser {
private static final int MAX_TUPLE_LITERAL_ARITY = 6;
@FunctionalInterface
interface ExpressionParserDelegate {
PbsAst.Expression parse();
}
@FunctionalInterface
interface ErrorPathParserDelegate {
PbsAst.ErrorPath parse();
}
private final PbsExprParserContext context;
private final PbsTokenCursor cursor;
private final ExpressionParserDelegate expressionParserDelegate;
private final ErrorPathParserDelegate errorPathParserDelegate;
PbsExprPrimaryParser(
final PbsExprParserContext context,
final ExpressionParserDelegate expressionParserDelegate,
final ErrorPathParserDelegate errorPathParserDelegate) {
this.context = context;
this.cursor = context.cursor();
this.expressionParserDelegate = expressionParserDelegate;
this.errorPathParserDelegate = errorPathParserDelegate;
}
PbsAst.Expression parsePostfix() {
var expression = parsePrimary();
while (true) {
if (cursor.match(PbsTokenKind.LEFT_PAREN)) {
final var arguments = new ArrayList<PbsAst.Expression>();
if (!cursor.check(PbsTokenKind.RIGHT_PAREN)) {
do {
arguments.add(expressionParserDelegate.parse());
} while (cursor.match(PbsTokenKind.COMMA));
}
final var close = context.consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after call arguments");
expression = new PbsAst.CallExpr(expression, ReadOnlyList.wrap(arguments),
context.span(expression.span().getStart(), close.end()));
continue;
}
if (cursor.match(PbsTokenKind.DOT)) {
final var member = context.consumeMemberName("Expected member name after '.'");
expression = new PbsAst.MemberExpr(
expression,
member.lexeme(),
context.span(expression.span().getStart(), member.end()));
continue;
}
if (cursor.match(PbsTokenKind.BANG)) {
final var bang = cursor.previous();
expression = new PbsAst.PropagateExpr(expression, context.span(expression.span().getStart(), bang.end()));
continue;
}
if (cursor.match(PbsTokenKind.QUESTION)) {
final var question = cursor.previous();
context.report(question, ParseErrors.E_PARSE_INVALID_PROPAGATE_OPERATOR,
"Use '!' as propagation operator; '?' is not valid PBS syntax");
continue;
}
return expression;
}
}
PbsAst.Expression parsePrimary() {
if (cursor.match(PbsTokenKind.TRUE)) {
final var token = cursor.previous();
return new PbsAst.BoolLiteralExpr(true, context.span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.FALSE)) {
final var token = cursor.previous();
return new PbsAst.BoolLiteralExpr(false, context.span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.INT_LITERAL)) {
final var token = cursor.previous();
return new PbsAst.IntLiteralExpr(parseLongOrDefault(token.lexeme()), context.span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.FLOAT_LITERAL)) {
final var token = cursor.previous();
return new PbsAst.FloatLiteralExpr(parseDoubleOrDefault(token.lexeme()), context.span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.BOUNDED_LITERAL)) {
final var token = cursor.previous();
final var raw = token.lexeme().substring(0, Math.max(token.lexeme().length() - 1, 0));
return new PbsAst.BoundedLiteralExpr(parseIntOrDefault(raw), context.span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.STRING_LITERAL)) {
final var token = cursor.previous();
return new PbsAst.StringLiteralExpr(unescapeString(token.lexeme()), context.span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.THIS)) {
final var token = cursor.previous();
return new PbsAst.ThisExpr(context.span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.NEW)) {
return parseNewExpression(cursor.previous());
}
if (cursor.match(PbsTokenKind.BIND)) {
return parseBindExpression(cursor.previous());
}
if (cursor.match(PbsTokenKind.SOME)) {
return parseSomeExpression(cursor.previous());
}
if (cursor.match(PbsTokenKind.NONE)) {
final var token = cursor.previous();
return new PbsAst.NoneExpr(context.span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.OK)) {
return parseOkExpression(cursor.previous());
}
if (cursor.match(PbsTokenKind.ERR)) {
return parseErrExpression(cursor.previous());
}
if (cursor.match(PbsTokenKind.IDENTIFIER)) {
final var token = cursor.previous();
return new PbsAst.IdentifierExpr(token.lexeme(), context.span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.LEFT_PAREN)) {
return parseParenthesizedPrimary(cursor.previous());
}
if (cursor.check(PbsTokenKind.LEFT_BRACE)) {
final var block = context.parseSurfaceBlock("Expected block expression");
return new PbsAst.BlockExpr(block, block.span());
}
final var token = cursor.peek();
context.report(token, ParseErrors.E_PARSE_UNEXPECTED_TOKEN, "Unexpected token in expression: " + token.kind());
cursor.advance();
return new PbsAst.IntLiteralExpr(0L, context.span(token.start(), token.end()));
}
PbsAst.Expression parseParenthesizedPrimary(final PbsToken open) {
if (cursor.match(PbsTokenKind.RIGHT_PAREN)) {
final var close = cursor.previous();
return new PbsAst.UnitExpr(context.span(open.start(), close.end()));
}
if (cursor.check(PbsTokenKind.IDENTIFIER) && cursor.checkNext(PbsTokenKind.COLON)) {
final var items = parseTupleItems(true);
final var close = context.consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after tuple literal");
if (items.size() < 2) {
context.report(close, ParseErrors.E_PARSE_INVALID_TUPLE_LITERAL,
"Single-slot tuple literal is not allowed in PBS core syntax");
}
if (items.size() > MAX_TUPLE_LITERAL_ARITY) {
context.report(close, ParseErrors.E_PARSE_INVALID_TUPLE_LITERAL,
"Tuple literal arity must be between 2 and 6 items");
}
return new PbsAst.TupleExpr(ReadOnlyList.wrap(items), context.span(open.start(), close.end()));
}
final var first = expressionParserDelegate.parse();
if (!cursor.match(PbsTokenKind.COMMA)) {
final var close = context.consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after grouped expression");
return new PbsAst.GroupExpr(first, context.span(open.start(), close.end()));
}
final var items = new ArrayList<PbsAst.TupleItem>();
items.add(new PbsAst.TupleItem(null, first, first.span()));
var hasLabels = false;
while (!cursor.check(PbsTokenKind.RIGHT_PAREN) && !cursor.isAtEnd()) {
final var item = parseTupleItem();
if (item.label() != null) {
hasLabels = true;
}
items.add(item);
if (!cursor.match(PbsTokenKind.COMMA)) {
break;
}
}
if (items.size() < 2) {
context.report(cursor.peek(), ParseErrors.E_PARSE_INVALID_TUPLE_LITERAL,
"Tuple literals require at least two items");
}
final var close = context.consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after tuple literal");
if (items.size() > MAX_TUPLE_LITERAL_ARITY) {
context.report(close, ParseErrors.E_PARSE_INVALID_TUPLE_LITERAL,
"Tuple literal arity must be between 2 and 6 items");
}
if (hasLabels) {
for (final var item : items) {
if (item.label() == null) {
context.report(close, ParseErrors.E_PARSE_INVALID_TUPLE_LITERAL,
"Mixed labeled/unlabeled tuple items are not allowed");
break;
}
}
}
return new PbsAst.TupleExpr(ReadOnlyList.wrap(items), context.span(open.start(), close.end()));
}
private ArrayList<PbsAst.TupleItem> parseTupleItems(final boolean labeledOnly) {
final var items = new ArrayList<PbsAst.TupleItem>();
do {
final var item = parseTupleItem();
items.add(item);
if (labeledOnly && item.label() == null) {
context.report(cursor.previous(), ParseErrors.E_PARSE_INVALID_TUPLE_LITERAL,
"Named tuple literal items must use labels");
}
} while (cursor.match(PbsTokenKind.COMMA) && !cursor.check(PbsTokenKind.RIGHT_PAREN));
return items;
}
private PbsAst.TupleItem parseTupleItem() {
if (cursor.check(PbsTokenKind.IDENTIFIER) && cursor.checkNext(PbsTokenKind.COLON)) {
final var label = cursor.advance();
context.consume(PbsTokenKind.COLON, "Expected ':' after tuple label");
final var value = expressionParserDelegate.parse();
return new PbsAst.TupleItem(label.lexeme(), value, context.span(label.start(), value.span().getEnd()));
}
final var value = expressionParserDelegate.parse();
return new PbsAst.TupleItem(null, value, value.span());
}
private PbsAst.Expression parseNewExpression(final PbsToken newToken) {
final var typeName = context.consume(PbsTokenKind.IDENTIFIER, "Expected type name after 'new'");
String ctorName = null;
if (cursor.match(PbsTokenKind.DOT)) {
ctorName = context.consume(PbsTokenKind.IDENTIFIER, "Expected constructor name after '.' in new target").lexeme();
}
context.consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after new target");
final var arguments = new ArrayList<PbsAst.Expression>();
if (!cursor.check(PbsTokenKind.RIGHT_PAREN)) {
do {
arguments.add(expressionParserDelegate.parse());
} while (cursor.match(PbsTokenKind.COMMA));
}
final var close = context.consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after new arguments");
return new PbsAst.NewExpr(
typeName.lexeme(),
ctorName,
ReadOnlyList.wrap(arguments),
context.span(newToken.start(), close.end()));
}
private PbsAst.Expression parseBindExpression(final PbsToken bindToken) {
context.consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after 'bind'");
final var contextExpression = expressionParserDelegate.parse();
context.consume(PbsTokenKind.COMMA, "Expected ',' in bind(context, fn_name)");
final var functionName = context.consume(PbsTokenKind.IDENTIFIER, "Expected function identifier in bind(context, fn_name)");
final var close = context.consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after bind arguments");
return new PbsAst.BindExpr(
contextExpression,
functionName.lexeme(),
context.span(bindToken.start(), close.end()));
}
private PbsAst.Expression parseSomeExpression(final PbsToken someToken) {
context.consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after 'some'");
final var value = expressionParserDelegate.parse();
final var close = context.consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after some(payload)");
return new PbsAst.SomeExpr(value, context.span(someToken.start(), close.end()));
}
private PbsAst.Expression parseOkExpression(final PbsToken okToken) {
context.consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after 'ok'");
final var value = expressionParserDelegate.parse();
final var close = context.consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after ok(payload)");
return new PbsAst.OkExpr(value, context.span(okToken.start(), close.end()));
}
private PbsAst.Expression parseErrExpression(final PbsToken errToken) {
context.consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after 'err'");
final var errorPath = errorPathParserDelegate.parse();
final var close = context.consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after err(Error.case)");
return new PbsAst.ErrExpr(errorPath, context.span(errToken.start(), close.end()));
}
private long parseLongOrDefault(final String text) {
try {
return Long.parseLong(text);
} catch (NumberFormatException ignored) {
return 0L;
}
}
private int parseIntOrDefault(final String text) {
try {
return Integer.parseInt(text);
} catch (NumberFormatException ignored) {
return 0;
}
}
private double parseDoubleOrDefault(final String text) {
try {
return Double.parseDouble(text);
} catch (NumberFormatException ignored) {
return 0.0;
}
}
private String unescapeString(final String lexeme) {
if (lexeme.length() < 2) {
return "";
}
final var raw = lexeme.substring(1, lexeme.length() - 1);
final var sb = new StringBuilder(raw.length());
for (int i = 0; i < raw.length(); i++) {
final char c = raw.charAt(i);
if (c != '\\' || i + 1 >= raw.length()) {
sb.append(c);
continue;
}
final char next = raw.charAt(++i);
switch (next) {
case 'n' -> sb.append('\n');
case 'r' -> sb.append('\r');
case 't' -> sb.append('\t');
case '"' -> sb.append('"');
case '\\' -> sb.append('\\');
default -> sb.append('\\').append(next);
}
}
return sb.toString();
}
}