implements PR-10.2

This commit is contained in:
bQUARKz 2026-03-10 09:40:22 +00:00
parent e56fe2b9b7
commit 6b85dc33e4
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
3 changed files with 365 additions and 269 deletions

View File

@ -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<PbsTokenKind> topLevelRestartPredicate;
PbsAttributeParser(
final PbsParserContext context,
final Predicate<PbsTokenKind> topLevelRestartPredicate) {
this.context = context;
this.cursor = context.cursor();
this.topLevelRestartPredicate = topLevelRestartPredicate;
}
ReadOnlyList<PbsAst.Attribute> parseAttributeList() {
final var attributes = new ArrayList<PbsAst.Attribute>();
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<PbsAst.AttributeArgument>();
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<PbsAst.AttributeArgument> 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("<error>", 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;
}
}
}

View File

@ -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<PbsToken> 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<PbsAst.Attribute> parseAttributeList() {
final var attributes = new ArrayList<PbsAst.Attribute>();
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<PbsAst.AttributeArgument>();
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<PbsAst.AttributeArgument> 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("<error>", 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<PbsAst.Parameter> parseParametersUntilRightParen() {
final var parameters = new ArrayList<PbsAst.Parameter>();
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<PbsAst.NamedTypeField> parseNamedTupleTypeFields() {
final var fields = new ArrayList<PbsAst.NamedTypeField>();
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<PbsAst.FunctionDecl> methods,
ReadOnlyList<PbsAst.CtorDecl> ctors,

View File

@ -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<PbsAst.Parameter> parseParametersUntilRightParen() {
final var parameters = new ArrayList<PbsAst.Parameter>();
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<PbsAst.NamedTypeField> parseNamedTupleTypeFields() {
final var fields = new ArrayList<PbsAst.NamedTypeField>();
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) {
}
}