From 6b85dc33e413b00fbcacdb94a5aaa78adb7ef82a Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Tue, 10 Mar 2026 09:40:22 +0000 Subject: [PATCH] implements PR-10.2 --- .../pbs/parser/PbsAttributeParser.java | 165 ++++++++++ .../studio/compiler/pbs/parser/PbsParser.java | 288 ++---------------- .../compiler/pbs/parser/PbsTypeParser.java | 181 +++++++++++ 3 files changed, 365 insertions(+), 269 deletions(-) create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsAttributeParser.java create mode 100644 prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsTypeParser.java diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsAttributeParser.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsAttributeParser.java new file mode 100644 index 00000000..83c51539 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsAttributeParser.java @@ -0,0 +1,165 @@ +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; +import java.util.function.Predicate; + +final class PbsAttributeParser { + private final PbsParserContext context; + private final PbsTokenCursor cursor; + private final Predicate topLevelRestartPredicate; + + PbsAttributeParser( + final PbsParserContext context, + final Predicate topLevelRestartPredicate) { + this.context = context; + this.cursor = context.cursor(); + this.topLevelRestartPredicate = topLevelRestartPredicate; + } + + ReadOnlyList parseAttributeList() { + final var attributes = new ArrayList(); + while (cursor.check(PbsTokenKind.LEFT_BRACKET)) { + attributes.add(parseAttribute()); + } + return ReadOnlyList.wrap(attributes); + } + + private PbsAst.Attribute parseAttribute() { + final var leftBracket = consume(PbsTokenKind.LEFT_BRACKET, "Expected '[' to start attribute"); + final var name = consume(PbsTokenKind.IDENTIFIER, "Expected attribute identifier"); + final var arguments = new ArrayList(); + if (cursor.match(PbsTokenKind.LEFT_PAREN)) { + parseAttributeArguments(arguments); + } + if ("Slot".equals(name.lexeme())) { + report(name, ParseErrors.E_PARSE_INVALID_DECL_SHAPE, + "Attribute 'Slot' is not part of PBS syntax; builtin slots are inferred by the compiler"); + } + + long end = Math.max(leftBracket.end(), name.end()); + if (cursor.match(PbsTokenKind.RIGHT_BRACKET)) { + end = cursor.previous().end(); + } else { + report(cursor.peek(), ParseErrors.E_PARSE_EXPECTED_TOKEN, "Expected ']' to close attribute"); + end = recoverUntilAttributeCloseOrTopLevel(end); + } + return new PbsAst.Attribute( + name.lexeme(), + ReadOnlyList.wrap(arguments), + context.span(leftBracket.start(), end)); + } + + private void parseAttributeArguments(final ArrayList arguments) { + if (!cursor.check(PbsTokenKind.RIGHT_PAREN)) { + do { + arguments.add(parseAttributeArgument()); + } while (cursor.match(PbsTokenKind.COMMA) && !cursor.check(PbsTokenKind.RIGHT_PAREN)); + } + + if (cursor.match(PbsTokenKind.RIGHT_PAREN)) { + return; + } + report(cursor.peek(), ParseErrors.E_PARSE_EXPECTED_TOKEN, "Expected ')' after attribute arguments"); + recoverUntilAttributeArgumentClose(); + } + + private PbsAst.AttributeArgument parseAttributeArgument() { + final var argumentStart = cursor.peek(); + final var name = consume(PbsTokenKind.IDENTIFIER, "Expected attribute argument name"); + consume(PbsTokenKind.EQUAL, "Expected '=' in attribute argument"); + final var value = parseAttributeValue(); + return new PbsAst.AttributeArgument( + name.lexeme(), + value, + context.span(argumentStart.start(), value.span().getEnd())); + } + + private PbsAst.AttributeValue parseAttributeValue() { + if (cursor.match(PbsTokenKind.STRING_LITERAL)) { + final var token = cursor.previous(); + return new PbsAst.AttributeStringValue(token.lexeme(), context.span(token.start(), token.end())); + } + if (cursor.match(PbsTokenKind.INT_LITERAL)) { + final var token = cursor.previous(); + final var parsedLong = parseLongOrNull(token.lexeme()); + if (parsedLong != null) { + return new PbsAst.AttributeIntValue(parsedLong, context.span(token.start(), token.end())); + } + report(token, ParseErrors.E_PARSE_EXPECTED_TOKEN, "Invalid integer literal in attribute argument"); + return new PbsAst.AttributeErrorValue(token.lexeme(), context.span(token.start(), token.end())); + } + if (cursor.match(PbsTokenKind.TRUE)) { + final var token = cursor.previous(); + return new PbsAst.AttributeBoolValue(true, context.span(token.start(), token.end())); + } + if (cursor.match(PbsTokenKind.FALSE)) { + final var token = cursor.previous(); + return new PbsAst.AttributeBoolValue(false, context.span(token.start(), token.end())); + } + + final var token = cursor.peek(); + report(token, ParseErrors.E_PARSE_EXPECTED_TOKEN, "Expected attribute value (string, int, or bool)"); + if (!cursor.isAtEnd() + && !cursor.check(PbsTokenKind.COMMA) + && !cursor.check(PbsTokenKind.RIGHT_PAREN) + && !cursor.check(PbsTokenKind.RIGHT_BRACKET)) { + cursor.advance(); + } + return new PbsAst.AttributeErrorValue("", context.span(token.start(), token.end())); + } + + private long recoverUntilAttributeCloseOrTopLevel(final long fallbackEnd) { + long end = fallbackEnd; + while (!cursor.isAtEnd()) { + if (cursor.match(PbsTokenKind.RIGHT_BRACKET)) { + return cursor.previous().end(); + } + if (topLevelRestartPredicate.test(cursor.peek().kind())) { + return end; + } + end = cursor.advance().end(); + } + return end; + } + + private void recoverUntilAttributeArgumentClose() { + while (!cursor.isAtEnd()) { + if (cursor.match(PbsTokenKind.RIGHT_PAREN)) { + return; + } + if (cursor.check(PbsTokenKind.RIGHT_BRACKET) || topLevelRestartPredicate.test(cursor.peek().kind())) { + return; + } + cursor.advance(); + } + } + + private PbsToken consume(final PbsTokenKind kind, final String message) { + if (cursor.check(kind)) { + return cursor.advance(); + } + final var token = cursor.peek(); + report(token, ParseErrors.E_PARSE_EXPECTED_TOKEN, message + ", found " + token.kind()); + if (!cursor.isAtEnd()) { + return cursor.advance(); + } + return token; + } + + private void report(final PbsToken token, final ParseErrors parseErrors, final String message) { + context.report(token, parseErrors, message); + } + + private Long parseLongOrNull(final String text) { + try { + return Long.parseLong(text); + } catch (NumberFormatException ignored) { + return null; + } + } +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsParser.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsParser.java index 38b6c09a..e190b271 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsParser.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsParser.java @@ -20,7 +20,6 @@ import java.util.List; * navigation is delegated to {@link PbsTokenCursor}. */ public final class PbsParser { - private static final int MAX_NAMED_TUPLE_ARITY = 6; private static final boolean REQUIRE_SEMICOLON = true; private static final boolean ALLOW_TAIL_EXPRESSION = true; @@ -32,6 +31,8 @@ public final class PbsParser { private final PbsParserContext context; private final PbsTokenCursor cursor; private final PbsExprParser exprParser; + private final PbsAttributeParser attributeParser; + private final PbsTypeParser typeParser; private PbsParser( final ReadOnlyList tokens, @@ -41,6 +42,8 @@ public final class PbsParser { this.context = new PbsParserContext(new PbsTokenCursor(tokens), fileId, diagnostics, parseMode); this.cursor = context.cursor(); this.exprParser = new PbsExprParser(context, this::parseExpressionSurfaceBlock); + this.attributeParser = new PbsAttributeParser(context, this::isTopLevelRestartToken); + this.typeParser = new PbsTypeParser(context); } /** @@ -454,121 +457,7 @@ public final class PbsParser { } private ReadOnlyList parseAttributeList() { - final var attributes = new ArrayList(); - while (cursor.check(PbsTokenKind.LEFT_BRACKET)) { - attributes.add(parseAttribute()); - } - return ReadOnlyList.wrap(attributes); - } - - private PbsAst.Attribute parseAttribute() { - final var leftBracket = consume(PbsTokenKind.LEFT_BRACKET, "Expected '[' to start attribute"); - final var name = consume(PbsTokenKind.IDENTIFIER, "Expected attribute identifier"); - final var arguments = new ArrayList(); - if (cursor.match(PbsTokenKind.LEFT_PAREN)) { - parseAttributeArguments(arguments); - } - if ("Slot".equals(name.lexeme())) { - report(name, ParseErrors.E_PARSE_INVALID_DECL_SHAPE, - "Attribute 'Slot' is not part of PBS syntax; builtin slots are inferred by the compiler"); - } - - long end = Math.max(leftBracket.end(), name.end()); - if (cursor.match(PbsTokenKind.RIGHT_BRACKET)) { - end = cursor.previous().end(); - } else { - report(cursor.peek(), ParseErrors.E_PARSE_EXPECTED_TOKEN, "Expected ']' to close attribute"); - end = recoverUntilAttributeCloseOrTopLevel(end); - } - return new PbsAst.Attribute( - name.lexeme(), - ReadOnlyList.wrap(arguments), - span(leftBracket.start(), end)); - } - - private void parseAttributeArguments(final ArrayList arguments) { - if (!cursor.check(PbsTokenKind.RIGHT_PAREN)) { - do { - arguments.add(parseAttributeArgument()); - } while (cursor.match(PbsTokenKind.COMMA) && !cursor.check(PbsTokenKind.RIGHT_PAREN)); - } - - if (cursor.match(PbsTokenKind.RIGHT_PAREN)) { - return; - } - report(cursor.peek(), ParseErrors.E_PARSE_EXPECTED_TOKEN, "Expected ')' after attribute arguments"); - recoverUntilAttributeArgumentClose(); - } - - private PbsAst.AttributeArgument parseAttributeArgument() { - final var argumentStart = cursor.peek(); - final var name = consume(PbsTokenKind.IDENTIFIER, "Expected attribute argument name"); - consume(PbsTokenKind.EQUAL, "Expected '=' in attribute argument"); - final var value = parseAttributeValue(); - return new PbsAst.AttributeArgument( - name.lexeme(), - value, - span(argumentStart.start(), value.span().getEnd())); - } - - private PbsAst.AttributeValue parseAttributeValue() { - if (cursor.match(PbsTokenKind.STRING_LITERAL)) { - final var token = cursor.previous(); - return new PbsAst.AttributeStringValue(token.lexeme(), span(token.start(), token.end())); - } - if (cursor.match(PbsTokenKind.INT_LITERAL)) { - final var token = cursor.previous(); - final var parsedLong = parseLongOrNull(token.lexeme()); - if (parsedLong != null) { - return new PbsAst.AttributeIntValue(parsedLong, span(token.start(), token.end())); - } - report(token, ParseErrors.E_PARSE_EXPECTED_TOKEN, "Invalid integer literal in attribute argument"); - return new PbsAst.AttributeErrorValue(token.lexeme(), span(token.start(), token.end())); - } - if (cursor.match(PbsTokenKind.TRUE)) { - final var token = cursor.previous(); - return new PbsAst.AttributeBoolValue(true, span(token.start(), token.end())); - } - if (cursor.match(PbsTokenKind.FALSE)) { - final var token = cursor.previous(); - return new PbsAst.AttributeBoolValue(false, span(token.start(), token.end())); - } - - final var token = cursor.peek(); - report(token, ParseErrors.E_PARSE_EXPECTED_TOKEN, "Expected attribute value (string, int, or bool)"); - if (!cursor.isAtEnd() - && !cursor.check(PbsTokenKind.COMMA) - && !cursor.check(PbsTokenKind.RIGHT_PAREN) - && !cursor.check(PbsTokenKind.RIGHT_BRACKET)) { - cursor.advance(); - } - return new PbsAst.AttributeErrorValue("", span(token.start(), token.end())); - } - - private long recoverUntilAttributeCloseOrTopLevel(final long fallbackEnd) { - long end = fallbackEnd; - while (!cursor.isAtEnd()) { - if (cursor.match(PbsTokenKind.RIGHT_BRACKET)) { - return cursor.previous().end(); - } - if (isTopLevelRestartToken(cursor.peek().kind())) { - return end; - } - end = cursor.advance().end(); - } - return end; - } - - private void recoverUntilAttributeArgumentClose() { - while (!cursor.isAtEnd()) { - if (cursor.match(PbsTokenKind.RIGHT_PAREN)) { - return; - } - if (cursor.check(PbsTokenKind.RIGHT_BRACKET) || isTopLevelRestartToken(cursor.peek().kind())) { - return; - } - cursor.advance(); - } + return attributeParser.parseAttributeList(); } private StructBodyParse parseStructBodyAndConsumeRightBrace(final PbsToken leftBrace) { @@ -737,9 +626,9 @@ public final class PbsParser { return new PbsAst.FunctionDecl( name.lexeme(), ReadOnlyList.wrap(parameters), - returnSpec.kind, - returnSpec.returnType, - returnSpec.resultErrorType, + returnSpec.kind(), + returnSpec.returnType(), + returnSpec.resultErrorType(), body, span(fnToken.start(), body.span().getEnd())); } @@ -754,7 +643,7 @@ public final class PbsParser { final var returnSpec = parseCallableReturnSpec(rightParen); - long end = returnSpec.returnType.span().getEnd(); + long end = returnSpec.returnType().span().getEnd(); if (REQUIRE_SEMICOLON) { end = consume(PbsTokenKind.SEMICOLON, "Expected ';' after function signature").end(); } @@ -762,9 +651,9 @@ public final class PbsParser { return new PbsAst.FunctionSignature( name.lexeme(), ReadOnlyList.wrap(parameters), - returnSpec.kind, - returnSpec.returnType, - returnSpec.resultErrorType, + returnSpec.kind(), + returnSpec.returnType(), + returnSpec.resultErrorType(), attributes, span(fnToken.start(), end)); } @@ -796,9 +685,9 @@ public final class PbsParser { return new PbsAst.CallbackDecl( name.lexeme(), ReadOnlyList.wrap(parameters), - returnSpec.kind, - returnSpec.returnType, - returnSpec.resultErrorType, + returnSpec.kind(), + returnSpec.returnType(), + returnSpec.resultErrorType(), span(declareToken.start(), semicolon.end())); } @@ -865,148 +754,15 @@ public final class PbsParser { } private ArrayList parseParametersUntilRightParen() { - final var parameters = new ArrayList(); - if (cursor.check(PbsTokenKind.RIGHT_PAREN)) { - return parameters; - } - - do { - final var parameterStart = cursor.peek(); - final var parameterName = consume(PbsTokenKind.IDENTIFIER, "Expected parameter name"); - consume(PbsTokenKind.COLON, "Expected ':' after parameter name"); - final var typeRef = parseTypeRef(); - parameters.add(new PbsAst.Parameter( - parameterName.lexeme(), - typeRef, - span(parameterStart.start(), typeRef.span().getEnd()))); - } while (cursor.match(PbsTokenKind.COMMA)); - - return parameters; + return typeParser.parseParametersUntilRightParen(); } - private ParsedReturnSpec parseCallableReturnSpec(final PbsToken anchorToken) { - if (cursor.match(PbsTokenKind.ARROW)) { - return parseReturnSpecFromMarker(); - } - - if (cursor.match(PbsTokenKind.COLON)) { - report(cursor.previous(), ParseErrors.E_PARSE_INVALID_RETURN_ANNOTATION, - "Return annotations must use '->' syntax"); - return parseReturnSpecFromMarker(); - } - - final var inferredSpan = span(anchorToken.end(), anchorToken.end()); - return new ParsedReturnSpec( - PbsAst.ReturnKind.INFERRED_UNIT, - PbsAst.TypeRef.unit(inferredSpan), - null); + private PbsTypeParser.ParsedReturnSpec parseCallableReturnSpec(final PbsToken anchorToken) { + return typeParser.parseCallableReturnSpec(anchorToken); } - private ParsedReturnSpec parseReturnSpecFromMarker() { - if (cursor.match(PbsTokenKind.RESULT)) { - final var resultToken = cursor.previous(); - consume(PbsTokenKind.LESS, "Expected '<' after 'result'"); - final var errorType = parseTypeRef(); - consume(PbsTokenKind.GREATER, "Expected '>' after result error type"); - - final PbsAst.TypeRef payloadType; - if (isTypeStart(cursor.peek().kind())) { - payloadType = parseTypeRef(); - } else { - payloadType = PbsAst.TypeRef.unit(span(resultToken.end(), resultToken.end())); - } - - return new ParsedReturnSpec( - PbsAst.ReturnKind.RESULT, - payloadType, - errorType); - } - - final var typeRef = parseTypeRef(); - if (typeRef.kind() == PbsAst.TypeRefKind.UNIT) { - return new ParsedReturnSpec(PbsAst.ReturnKind.EXPLICIT_UNIT, typeRef, null); - } - return new ParsedReturnSpec(PbsAst.ReturnKind.PLAIN, typeRef, null); - } - - /** - * Parses type references including optional, unit, grouped, and named-tuple forms. - */ private PbsAst.TypeRef parseTypeRef() { - if (cursor.match(PbsTokenKind.OPTIONAL)) { - final var optionalToken = cursor.previous(); - final var inner = parseTypeRef(); - if (inner.kind() == PbsAst.TypeRefKind.UNIT) { - report(optionalToken, ParseErrors.E_PARSE_INVALID_TYPE_SURFACE, - "'optional void' is not a valid type surface"); - } - return PbsAst.TypeRef.optional(inner, span(optionalToken.start(), inner.span().getEnd())); - } - - if (cursor.match(PbsTokenKind.VOID)) { - final var token = cursor.previous(); - return PbsAst.TypeRef.unit(span(token.start(), token.end())); - } - - if (cursor.match(PbsTokenKind.SELF)) { - final var token = cursor.previous(); - return PbsAst.TypeRef.self(span(token.start(), token.end())); - } - - if (cursor.match(PbsTokenKind.IDENTIFIER)) { - final var token = cursor.previous(); - return PbsAst.TypeRef.simple(token.lexeme(), span(token.start(), token.end())); - } - - if (cursor.match(PbsTokenKind.LEFT_PAREN)) { - final var open = cursor.previous(); - if (cursor.match(PbsTokenKind.RIGHT_PAREN)) { - return PbsAst.TypeRef.unit(span(open.start(), cursor.previous().end())); - } - - if (cursor.check(PbsTokenKind.IDENTIFIER) && cursor.checkNext(PbsTokenKind.COLON)) { - final var fields = parseNamedTupleTypeFields(); - final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after named tuple type"); - return PbsAst.TypeRef.namedTuple(ReadOnlyList.wrap(fields), span(open.start(), close.end())); - } - - final var inner = parseTypeRef(); - final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after grouped type"); - return PbsAst.TypeRef.group(inner, span(open.start(), close.end())); - } - - final var token = cursor.peek(); - report(token, ParseErrors.E_PARSE_EXPECTED_TOKEN, "Expected type surface"); - if (!cursor.isAtEnd()) { - cursor.advance(); - } - return PbsAst.TypeRef.error(span(token.start(), token.end())); - } - - private ArrayList parseNamedTupleTypeFields() { - final var fields = new ArrayList(); - do { - final var label = consume(PbsTokenKind.IDENTIFIER, "Expected tuple field label"); - consume(PbsTokenKind.COLON, "Expected ':' after tuple field label"); - final var typeRef = parseTypeRef(); - fields.add(new PbsAst.NamedTypeField( - label.lexeme(), - typeRef, - span(label.start(), typeRef.span().getEnd()))); - if (fields.size() == MAX_NAMED_TUPLE_ARITY + 1) { - report(label, ParseErrors.E_PARSE_INVALID_TYPE_SURFACE, - "Named tuple type arity must be between 1 and 6 fields"); - } - } while (cursor.match(PbsTokenKind.COMMA) && !cursor.check(PbsTokenKind.RIGHT_PAREN)); - return fields; - } - - private boolean isTypeStart(final PbsTokenKind kind) { - return kind == PbsTokenKind.IDENTIFIER - || kind == PbsTokenKind.SELF - || kind == PbsTokenKind.OPTIONAL - || kind == PbsTokenKind.VOID - || kind == PbsTokenKind.LEFT_PAREN; + return typeParser.parseTypeRef(); } /** @@ -1416,12 +1172,6 @@ public final class PbsParser { context.report(token, parseErrors, message); } - private record ParsedReturnSpec( - PbsAst.ReturnKind kind, - PbsAst.TypeRef returnType, - PbsAst.TypeRef resultErrorType) { - } - private record StructBodyParse( ReadOnlyList methods, ReadOnlyList ctors, diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsTypeParser.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsTypeParser.java new file mode 100644 index 00000000..d03c0b6a --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsTypeParser.java @@ -0,0 +1,181 @@ +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 java.util.ArrayList; + +final class PbsTypeParser { + private static final int MAX_NAMED_TUPLE_ARITY = 6; + + private final PbsParserContext context; + private final PbsTokenCursor cursor; + + PbsTypeParser(final PbsParserContext context) { + this.context = context; + this.cursor = context.cursor(); + } + + ArrayList parseParametersUntilRightParen() { + final var parameters = new ArrayList(); + if (cursor.check(PbsTokenKind.RIGHT_PAREN)) { + return parameters; + } + + do { + final var parameterStart = cursor.peek(); + final var parameterName = consume(PbsTokenKind.IDENTIFIER, "Expected parameter name"); + consume(PbsTokenKind.COLON, "Expected ':' after parameter name"); + final var typeRef = parseTypeRef(); + parameters.add(new PbsAst.Parameter( + parameterName.lexeme(), + typeRef, + context.span(parameterStart.start(), typeRef.span().getEnd()))); + } while (cursor.match(PbsTokenKind.COMMA)); + + return parameters; + } + + ParsedReturnSpec parseCallableReturnSpec(final PbsToken anchorToken) { + if (cursor.match(PbsTokenKind.ARROW)) { + return parseReturnSpecFromMarker(); + } + + if (cursor.match(PbsTokenKind.COLON)) { + context.report(cursor.previous(), ParseErrors.E_PARSE_INVALID_RETURN_ANNOTATION, + "Return annotations must use '->' syntax"); + return parseReturnSpecFromMarker(); + } + + final var inferredSpan = context.span(anchorToken.end(), anchorToken.end()); + return new ParsedReturnSpec( + PbsAst.ReturnKind.INFERRED_UNIT, + PbsAst.TypeRef.unit(inferredSpan), + null); + } + + PbsAst.TypeRef parseTypeRef() { + if (cursor.match(PbsTokenKind.OPTIONAL)) { + final var optionalToken = cursor.previous(); + final var inner = parseTypeRef(); + if (inner.kind() == PbsAst.TypeRefKind.UNIT) { + context.report(optionalToken, ParseErrors.E_PARSE_INVALID_TYPE_SURFACE, + "'optional void' is not a valid type surface"); + } + return PbsAst.TypeRef.optional(inner, context.span(optionalToken.start(), inner.span().getEnd())); + } + + if (cursor.match(PbsTokenKind.VOID)) { + final var token = cursor.previous(); + return PbsAst.TypeRef.unit(context.span(token.start(), token.end())); + } + + if (cursor.match(PbsTokenKind.SELF)) { + final var token = cursor.previous(); + return PbsAst.TypeRef.self(context.span(token.start(), token.end())); + } + + if (cursor.match(PbsTokenKind.IDENTIFIER)) { + final var token = cursor.previous(); + return PbsAst.TypeRef.simple(token.lexeme(), context.span(token.start(), token.end())); + } + + if (cursor.match(PbsTokenKind.LEFT_PAREN)) { + final var open = cursor.previous(); + if (cursor.match(PbsTokenKind.RIGHT_PAREN)) { + return PbsAst.TypeRef.unit(context.span(open.start(), cursor.previous().end())); + } + + if (cursor.check(PbsTokenKind.IDENTIFIER) && cursor.checkNext(PbsTokenKind.COLON)) { + final var fields = parseNamedTupleTypeFields(); + final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after named tuple type"); + return PbsAst.TypeRef.namedTuple( + p.studio.utilities.structures.ReadOnlyList.wrap(fields), + context.span(open.start(), close.end())); + } + + final var inner = parseTypeRef(); + final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after grouped type"); + return PbsAst.TypeRef.group(inner, context.span(open.start(), close.end())); + } + + final var token = cursor.peek(); + context.report(token, ParseErrors.E_PARSE_EXPECTED_TOKEN, "Expected type surface"); + if (!cursor.isAtEnd()) { + cursor.advance(); + } + return PbsAst.TypeRef.error(context.span(token.start(), token.end())); + } + + private ParsedReturnSpec parseReturnSpecFromMarker() { + if (cursor.match(PbsTokenKind.RESULT)) { + final var resultToken = cursor.previous(); + consume(PbsTokenKind.LESS, "Expected '<' after 'result'"); + final var errorType = parseTypeRef(); + consume(PbsTokenKind.GREATER, "Expected '>' after result error type"); + + final PbsAst.TypeRef payloadType; + if (isTypeStart(cursor.peek().kind())) { + payloadType = parseTypeRef(); + } else { + payloadType = PbsAst.TypeRef.unit(context.span(resultToken.end(), resultToken.end())); + } + + return new ParsedReturnSpec( + PbsAst.ReturnKind.RESULT, + payloadType, + errorType); + } + + final var typeRef = parseTypeRef(); + if (typeRef.kind() == PbsAst.TypeRefKind.UNIT) { + return new ParsedReturnSpec(PbsAst.ReturnKind.EXPLICIT_UNIT, typeRef, null); + } + return new ParsedReturnSpec(PbsAst.ReturnKind.PLAIN, typeRef, null); + } + + private ArrayList parseNamedTupleTypeFields() { + final var fields = new ArrayList(); + do { + final var label = consume(PbsTokenKind.IDENTIFIER, "Expected tuple field label"); + consume(PbsTokenKind.COLON, "Expected ':' after tuple field label"); + final var typeRef = parseTypeRef(); + fields.add(new PbsAst.NamedTypeField( + label.lexeme(), + typeRef, + context.span(label.start(), typeRef.span().getEnd()))); + if (fields.size() == MAX_NAMED_TUPLE_ARITY + 1) { + context.report(label, ParseErrors.E_PARSE_INVALID_TYPE_SURFACE, + "Named tuple type arity must be between 1 and 6 fields"); + } + } while (cursor.match(PbsTokenKind.COMMA) && !cursor.check(PbsTokenKind.RIGHT_PAREN)); + return fields; + } + + private boolean isTypeStart(final PbsTokenKind kind) { + return kind == PbsTokenKind.IDENTIFIER + || kind == PbsTokenKind.SELF + || kind == PbsTokenKind.OPTIONAL + || kind == PbsTokenKind.VOID + || kind == PbsTokenKind.LEFT_PAREN; + } + + private PbsToken consume(final PbsTokenKind kind, final String message) { + if (cursor.check(kind)) { + return cursor.advance(); + } + final var token = cursor.peek(); + context.report(token, ParseErrors.E_PARSE_EXPECTED_TOKEN, message + ", found " + token.kind()); + if (!cursor.isAtEnd()) { + return cursor.advance(); + } + return token; + } + + record ParsedReturnSpec( + PbsAst.ReturnKind kind, + PbsAst.TypeRef returnType, + PbsAst.TypeRef resultErrorType) { + } +}