From ea44d40410c1e15401cc1d87df88517cc3616bea Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Tue, 10 Mar 2026 09:57:04 +0000 Subject: [PATCH] implements PR-11.3 --- .../compiler/pbs/parser/PbsExprParser.java | 251 +------------ .../pbs/parser/PbsExprPrimaryParser.java | 339 ++++++++++++++++++ 2 files changed, 345 insertions(+), 245 deletions(-) create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsExprPrimaryParser.java diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsExprParser.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsExprParser.java index 4fb7ae3c..a42aa961 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsExprParser.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsExprParser.java @@ -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(); - 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(); - 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 parseTupleItems(final boolean labeledOnly) { - final var items = new ArrayList(); - 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(); - 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() { diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsExprPrimaryParser.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsExprPrimaryParser.java new file mode 100644 index 00000000..d0f249ff --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsExprPrimaryParser.java @@ -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(); + 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(); + 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 parseTupleItems(final boolean labeledOnly) { + final var items = new ArrayList(); + 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(); + 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(); + } +}