diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsDeclarationParser.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsDeclarationParser.java new file mode 100644 index 00000000..b4b2f9d4 --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsDeclarationParser.java @@ -0,0 +1,682 @@ +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.compiler.source.Span; +import p.studio.compiler.source.diagnostics.RelatedSpan; +import p.studio.utilities.structures.ReadOnlyList; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +final class PbsDeclarationParser { + private static final boolean REQUIRE_SEMICOLON = true; + + @FunctionalInterface + interface BlockParserDelegate { + PbsAst.Block parse(); + } + + @FunctionalInterface + interface DeclarationTerminatorDelegate { + long consume(); + } + + @FunctionalInterface + interface TopLevelSynchronizer { + void synchronize(); + } + + private final PbsParserContext context; + private final PbsTokenCursor cursor; + private final PbsExprParser exprParser; + private final PbsAttributeParser attributeParser; + private final PbsTypeParser typeParser; + private final BlockParserDelegate blockParserDelegate; + private final DeclarationTerminatorDelegate declarationTerminatorDelegate; + private final TopLevelSynchronizer topLevelSynchronizer; + + PbsDeclarationParser( + final PbsParserContext context, + final PbsExprParser exprParser, + final PbsAttributeParser attributeParser, + final PbsTypeParser typeParser, + final BlockParserDelegate blockParserDelegate, + final DeclarationTerminatorDelegate declarationTerminatorDelegate, + final TopLevelSynchronizer topLevelSynchronizer) { + this.context = context; + this.cursor = context.cursor(); + this.exprParser = exprParser; + this.attributeParser = attributeParser; + this.typeParser = typeParser; + this.blockParserDelegate = blockParserDelegate; + this.declarationTerminatorDelegate = declarationTerminatorDelegate; + this.topLevelSynchronizer = topLevelSynchronizer; + } + + PbsAst.ImportDecl parseImport(final PbsToken importToken) { + final var items = new ArrayList(); + if (cursor.match(PbsTokenKind.LEFT_BRACE)) { + while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) { + if (cursor.match(PbsTokenKind.IDENTIFIER)) { + final var itemName = cursor.previous(); + String alias = null; + if (cursor.match(PbsTokenKind.AS)) { + alias = consume(PbsTokenKind.IDENTIFIER, "Expected alias identifier after 'as'").lexeme(); + } + items.add(new PbsAst.ImportItem( + itemName.lexeme(), + alias, + span(itemName.start(), cursor.previous().end()))); + cursor.match(PbsTokenKind.COMMA); + continue; + } + report(cursor.peek(), ParseErrors.E_PARSE_UNEXPECTED_TOKEN, "Invalid import item"); + cursor.advance(); + } + consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' in import list"); + consume(PbsTokenKind.FROM, "Expected 'from' in named import"); + } + + final var moduleRef = parseModuleRef(); + final var semicolon = consume(PbsTokenKind.SEMICOLON, "Expected ';' after import"); + return new PbsAst.ImportDecl( + ReadOnlyList.wrap(items), + moduleRef, + span(importToken.start(), semicolon.end())); + } + + PbsAst.ModuleRef parseModuleRef() { + final var at = consume(PbsTokenKind.AT, "Expected '@' in module reference"); + final var project = consume(PbsTokenKind.IDENTIFIER, "Expected project identifier in module reference"); + consume(PbsTokenKind.COLON, "Expected ':' in module reference"); + final var segments = new ArrayList(); + final var firstSegment = consume(PbsTokenKind.IDENTIFIER, "Expected module identifier"); + segments.add(firstSegment.lexeme()); + var end = firstSegment.end(); + while (cursor.match(PbsTokenKind.SLASH)) { + final var segment = consume(PbsTokenKind.IDENTIFIER, "Expected module path segment after '/'"); + segments.add(segment.lexeme()); + end = segment.end(); + } + return new PbsAst.ModuleRef(project.lexeme(), ReadOnlyList.wrap(segments), span(at.start(), end)); + } + + PbsAst.TopDecl parseDeclareTopDecl( + final PbsToken declareToken, + final ReadOnlyList pendingAttributes) { + if (cursor.match(PbsTokenKind.STRUCT)) { + rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "struct declarations"); + return parseStructDeclaration(declareToken); + } + if (cursor.match(PbsTokenKind.CONTRACT)) { + rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "contract declarations"); + return parseContractDeclaration(declareToken); + } + if (cursor.match(PbsTokenKind.SERVICE)) { + rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "service declarations"); + return parseServiceDeclaration(declareToken); + } + if (cursor.match(PbsTokenKind.ERROR)) { + rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "error declarations"); + return parseErrorDeclaration(declareToken); + } + if (cursor.match(PbsTokenKind.ENUM)) { + rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "enum declarations"); + return parseEnumDeclaration(declareToken); + } + if (cursor.match(PbsTokenKind.CALLBACK)) { + rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "callback declarations"); + return parseCallbackDeclaration(declareToken); + } + if (cursor.match(PbsTokenKind.CONST)) { + return parseConstDeclaration(declareToken, pendingAttributes); + } + if (cursor.match(PbsTokenKind.HOST)) { + rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "host declarations"); + if (isNotInterfaceMode()) { + final var end = declarationTerminatorDelegate.consume(); + report(cursor.previous(), ParseErrors.E_PARSE_RESERVED_DECLARATION, + "'declare host' is reserved and not supported in ordinary source modules"); + return new PbsAst.InvalidDecl("reserved declare host", span(declareToken.start(), end)); + } + return parseHostDeclaration(declareToken); + } + if (cursor.match(PbsTokenKind.BUILTIN)) { + consume(PbsTokenKind.TYPE, "Expected 'type' in 'declare builtin type' declaration"); + if (isNotInterfaceMode()) { + final var end = declarationTerminatorDelegate.consume(); + report(cursor.previous(), ParseErrors.E_PARSE_RESERVED_DECLARATION, + "'declare builtin type' is reserved and not supported in ordinary source modules"); + return new PbsAst.InvalidDecl("reserved declare builtin type", span(declareToken.start(), end)); + } + return parseBuiltinTypeDeclaration(declareToken, pendingAttributes); + } + + if (!pendingAttributes.isEmpty()) { + reportAttributesNotAllowed(pendingAttributes, "Attributes are not valid for this declaration form"); + } + + report(cursor.peek(), ParseErrors.E_PARSE_UNEXPECTED_TOKEN, + "Expected declaration kind after 'declare'"); + topLevelSynchronizer.synchronize(); + final var token = cursor.previous(); + return new PbsAst.InvalidDecl("invalid declare form", span(declareToken.start(), token.end())); + } + + PbsAst.FunctionDecl parseFunction(final PbsToken fnToken) { + return parseFunctionLike(fnToken); + } + + PbsAst.ImplementsDecl parseImplementsDeclaration(final PbsToken implementsToken) { + final var contractName = consume(PbsTokenKind.IDENTIFIER, "Expected contract name in 'implements'"); + consume(PbsTokenKind.FOR, "Expected 'for' in 'implements' declaration"); + final var ownerName = consume(PbsTokenKind.IDENTIFIER, "Expected owner name after 'for'"); + consume(PbsTokenKind.USING, "Expected 'using' in 'implements' declaration"); + final var binderName = consume(PbsTokenKind.IDENTIFIER, "Expected binder name after 'using'"); + + final var methods = new ArrayList(); + long end; + if (cursor.check(PbsTokenKind.LEFT_BRACE)) { + cursor.advance(); + while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) { + if (!cursor.match(PbsTokenKind.FN)) { + report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE, + "Implements body accepts only function declarations"); + cursor.advance(); + continue; + } + methods.add(parseFunctionLike(cursor.previous())); + } + end = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end implements body").end(); + } else { + report(cursor.peek(), ParseErrors.E_PARSE_EXPECTED_TOKEN, + "Expected '{' to start 'implements' body"); + end = declarationTerminatorDelegate.consume(); + } + + return new PbsAst.ImplementsDecl( + contractName.lexeme(), + ownerName.lexeme(), + binderName.lexeme(), + ReadOnlyList.wrap(methods), + span(implementsToken.start(), end)); + } + + private PbsAst.HostDecl parseHostDeclaration(final PbsToken declareToken) { + final var name = consume(PbsTokenKind.IDENTIFIER, "Expected host declaration name"); + consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start host declaration body"); + final var signatures = new ArrayList(); + while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) { + var attributes = ReadOnlyList.empty(); + if (cursor.check(PbsTokenKind.LEFT_BRACKET)) { + attributes = attributeParser.parseAttributeList(); + } + if (!cursor.match(PbsTokenKind.FN)) { + report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE, + "Host declaration body accepts only function signatures"); + cursor.advance(); + continue; + } + signatures.add(parseFunctionSignature(cursor.previous(), attributes)); + } + final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end host declaration body"); + return new PbsAst.HostDecl( + name.lexeme(), + ReadOnlyList.wrap(signatures), + span(declareToken.start(), rightBrace.end())); + } + + private PbsAst.BuiltinTypeDecl parseBuiltinTypeDeclaration( + final PbsToken declareToken, + final ReadOnlyList declarationAttributes) { + final var name = consume(PbsTokenKind.IDENTIFIER, "Expected builtin type name"); + consume(PbsTokenKind.LEFT_PAREN, "Expected '(' in builtin type declaration"); + final var fields = parseBuiltinTypeFields(); + consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after builtin type fields"); + + consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start builtin type body"); + final var signatures = new ArrayList(); + while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) { + var attributes = ReadOnlyList.empty(); + if (cursor.check(PbsTokenKind.LEFT_BRACKET)) { + attributes = attributeParser.parseAttributeList(); + } + if (!cursor.match(PbsTokenKind.FN)) { + report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE, + "Builtin type body accepts only function signatures"); + cursor.advance(); + continue; + } + signatures.add(parseFunctionSignature(cursor.previous(), attributes)); + } + final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end builtin type body"); + return new PbsAst.BuiltinTypeDecl( + name.lexeme(), + ReadOnlyList.wrap(fields), + ReadOnlyList.wrap(signatures), + declarationAttributes, + span(declareToken.start(), rightBrace.end())); + } + + private ArrayList parseBuiltinTypeFields() { + final var fields = new ArrayList(); + if (cursor.check(PbsTokenKind.RIGHT_PAREN)) { + return fields; + } + + do { + final var start = cursor.peek(); + var attributes = ReadOnlyList.empty(); + if (cursor.check(PbsTokenKind.LEFT_BRACKET)) { + attributes = attributeParser.parseAttributeList(); + } + + var isPublic = false; + if (cursor.match(PbsTokenKind.PUB)) { + isPublic = true; + if (cursor.match(PbsTokenKind.MUT)) { + final var mutToken = cursor.previous(); + report(mutToken, ParseErrors.E_PARSE_INVALID_DECL_SHAPE, + "'mut' is not allowed in builtin type fields"); + } + } else if (cursor.match(PbsTokenKind.MUT)) { + final var mutToken = cursor.previous(); + report(mutToken, ParseErrors.E_PARSE_INVALID_DECL_SHAPE, + "'mut' is not allowed in builtin type fields"); + } + + final var fieldName = consume(PbsTokenKind.IDENTIFIER, "Expected builtin field name"); + consume(PbsTokenKind.COLON, "Expected ':' after builtin field name"); + final var typeRef = typeParser.parseTypeRef(); + fields.add(new PbsAst.BuiltinFieldDecl( + fieldName.lexeme(), + typeRef, + isPublic, + attributes, + span(start.start(), typeRef.span().getEnd()))); + } while (cursor.match(PbsTokenKind.COMMA) && !cursor.check(PbsTokenKind.RIGHT_PAREN)); + + return fields; + } + + private PbsAst.StructDecl parseStructDeclaration(final PbsToken declareToken) { + final var name = consume(PbsTokenKind.IDENTIFIER, "Expected struct name"); + consume(PbsTokenKind.LEFT_PAREN, "Expected '(' in struct declaration"); + final var fields = parseStructFields(); + consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after struct fields"); + + final var methods = new ArrayList(); + final var ctors = new ArrayList(); + final boolean hasBody; + long end; + if (cursor.match(PbsTokenKind.SEMICOLON)) { + hasBody = false; + end = cursor.previous().end(); + } else if (cursor.match(PbsTokenKind.LEFT_BRACE)) { + hasBody = true; + final var bodyParse = parseStructBodyAndConsumeRightBrace(cursor.previous()); + methods.addAll(bodyParse.methods().asList()); + ctors.addAll(bodyParse.ctors().asList()); + end = bodyParse.end(); + } else { + report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE, + "Struct declaration must end with ';' or contain a '{...}' body"); + hasBody = false; + end = declarationTerminatorDelegate.consume(); + } + + return new PbsAst.StructDecl( + name.lexeme(), + ReadOnlyList.wrap(fields), + ReadOnlyList.wrap(methods), + ReadOnlyList.wrap(ctors), + hasBody, + span(declareToken.start(), end)); + } + + private ArrayList parseStructFields() { + final var fields = new ArrayList(); + if (cursor.check(PbsTokenKind.RIGHT_PAREN)) { + return fields; + } + + do { + final var start = cursor.peek(); + var isPublic = false; + var isMutable = false; + if (cursor.match(PbsTokenKind.PUB)) { + isPublic = true; + if (cursor.match(PbsTokenKind.MUT)) { + isMutable = true; + } + } else if (cursor.match(PbsTokenKind.MUT)) { + final var mutToken = cursor.previous(); + report(mutToken, ParseErrors.E_PARSE_INVALID_DECL_SHAPE, + "'mut' is only valid immediately after 'pub' in struct fields"); + } + + final var fieldName = consume(PbsTokenKind.IDENTIFIER, "Expected struct field name"); + consume(PbsTokenKind.COLON, "Expected ':' after struct field name"); + final var typeRef = typeParser.parseTypeRef(); + fields.add(new PbsAst.StructField( + fieldName.lexeme(), + typeRef, + isPublic, + isMutable, + span(start.start(), typeRef.span().getEnd()))); + } while (cursor.match(PbsTokenKind.COMMA) && !cursor.check(PbsTokenKind.RIGHT_PAREN)); + + return fields; + } + + private StructBodyParse parseStructBodyAndConsumeRightBrace(final PbsToken leftBrace) { + final var methods = new ArrayList(); + final var ctors = new ArrayList(); + while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) { + if (cursor.match(PbsTokenKind.FN)) { + methods.add(parseFunctionLike(cursor.previous())); + continue; + } + if (cursor.match(PbsTokenKind.CTOR)) { + ctors.add(parseCtorDeclarationInBody(cursor.previous())); + continue; + } + report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE, + "Struct body accepts only 'fn' methods and 'ctor' declarations"); + cursor.advance(); + } + final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end struct body"); + return new StructBodyParse(ReadOnlyList.wrap(methods), ReadOnlyList.wrap(ctors), rightBrace.end()); + } + + private PbsAst.CtorDecl parseCtorDeclarationInBody(final PbsToken ctorToken) { + final var name = consume(PbsTokenKind.IDENTIFIER, "Expected constructor name"); + consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after constructor name"); + final var parameters = typeParser.parseParametersUntilRightParen(); + consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after constructor parameters"); + final var body = parseBlock(); + return new PbsAst.CtorDecl( + name.lexeme(), + ReadOnlyList.wrap(parameters), + body, + span(ctorToken.start(), body.span().getEnd())); + } + + private PbsAst.ContractDecl parseContractDeclaration(final PbsToken declareToken) { + final var name = consume(PbsTokenKind.IDENTIFIER, "Expected contract name"); + consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start contract body"); + final var signatures = new ArrayList(); + while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) { + var attributes = ReadOnlyList.empty(); + if (cursor.check(PbsTokenKind.LEFT_BRACKET)) { + attributes = attributeParser.parseAttributeList(); + if (isOrdinaryMode()) { + reportAttributesNotAllowed(attributes, "Attributes are not allowed in ordinary .pbs source modules"); + attributes = ReadOnlyList.empty(); + } + } + if (!cursor.match(PbsTokenKind.FN)) { + report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE, + "Contract body accepts only function signatures"); + cursor.advance(); + continue; + } + signatures.add(parseFunctionSignature(cursor.previous(), attributes)); + } + final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end contract body"); + return new PbsAst.ContractDecl(name.lexeme(), ReadOnlyList.wrap(signatures), span(declareToken.start(), rightBrace.end())); + } + + private PbsAst.ServiceDecl parseServiceDeclaration(final PbsToken declareToken) { + final var name = consume(PbsTokenKind.IDENTIFIER, "Expected service name"); + consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start service body"); + final var methods = new ArrayList(); + while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) { + if (!cursor.match(PbsTokenKind.FN)) { + report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE, + "Service body accepts only method declarations"); + cursor.advance(); + continue; + } + methods.add(parseFunctionLike(cursor.previous())); + } + final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end service body"); + return new PbsAst.ServiceDecl(name.lexeme(), ReadOnlyList.wrap(methods), span(declareToken.start(), rightBrace.end())); + } + + private PbsAst.ErrorDecl parseErrorDeclaration(final PbsToken declareToken) { + final var name = consume(PbsTokenKind.IDENTIFIER, "Expected error name"); + consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start error body"); + final var cases = new ArrayList(); + while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) { + final var caseToken = consume(PbsTokenKind.IDENTIFIER, "Expected error case label"); + cases.add(caseToken.lexeme()); + consume(PbsTokenKind.SEMICOLON, "Expected ';' after error case label"); + } + final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end error body"); + return new PbsAst.ErrorDecl(name.lexeme(), ReadOnlyList.wrap(cases), span(declareToken.start(), rightBrace.end())); + } + + private PbsAst.EnumDecl parseEnumDeclaration(final PbsToken declareToken) { + final var name = consume(PbsTokenKind.IDENTIFIER, "Expected enum name"); + consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after enum name"); + + final var cases = new ArrayList(); + final var caseLabels = new HashMap(); + final var explicitCaseIds = new HashMap(); + var hasExplicitCases = false; + var hasImplicitCases = false; + if (!cursor.check(PbsTokenKind.RIGHT_PAREN)) { + do { + final var caseName = consume(PbsTokenKind.IDENTIFIER, "Expected enum case label"); + final var caseNameSpan = span(caseName.start(), caseName.end()); + Long explicitValue = null; + if (cursor.match(PbsTokenKind.EQUAL)) { + final var intToken = consume(PbsTokenKind.INT_LITERAL, "Expected integer literal after '=' in enum case"); + final var intTokenSpan = span(intToken.start(), intToken.end()); + explicitValue = parseLongOrNull(intToken.lexeme()); + hasExplicitCases = true; + if (explicitValue == null) { + report(intToken, ParseErrors.E_PARSE_INVALID_ENUM_FORM, + "Invalid explicit enum identifier"); + } else { + final var firstIdSpan = explicitCaseIds.putIfAbsent(explicitValue, intTokenSpan); + if (firstIdSpan != null) { + p.studio.compiler.source.diagnostics.Diagnostics.error(context.diagnostics(), + ParseErrors.E_PARSE_DUPLICATE_ENUM_CASE_ID.name(), + "Duplicate explicit enum identifier '%s'".formatted(explicitValue), + intTokenSpan, + List.of(new RelatedSpan("First explicit enum identifier is here", firstIdSpan))); + } + } + } else { + hasImplicitCases = true; + } + + final var firstLabelSpan = caseLabels.putIfAbsent(caseName.lexeme(), caseNameSpan); + if (firstLabelSpan != null) { + p.studio.compiler.source.diagnostics.Diagnostics.error(context.diagnostics(), + ParseErrors.E_PARSE_DUPLICATE_ENUM_CASE_LABEL.name(), + "Duplicate enum case label '%s'".formatted(caseName.lexeme()), + caseNameSpan, + List.of(new RelatedSpan("First enum case label is here", firstLabelSpan))); + } + + cases.add(new PbsAst.EnumCase(caseName.lexeme(), explicitValue, span(caseName.start(), cursor.previous().end()))); + } while (cursor.match(PbsTokenKind.COMMA) && !cursor.check(PbsTokenKind.RIGHT_PAREN)); + } + + if (hasExplicitCases && hasImplicitCases) { + report(name, ParseErrors.E_PARSE_INVALID_ENUM_FORM, + "Enum declarations cannot mix implicit and explicit case identifiers"); + } + + final var rightParen = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after enum cases"); + final var semicolon = consume(PbsTokenKind.SEMICOLON, "Expected ';' after enum declaration"); + return new PbsAst.EnumDecl(name.lexeme(), ReadOnlyList.wrap(cases), span(declareToken.start(), Math.max(rightParen.end(), semicolon.end()))); + } + + private PbsAst.FunctionDecl parseFunctionLike(final PbsToken fnToken) { + final var name = consumeCallableName(); + consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after function name"); + final var parameters = typeParser.parseParametersUntilRightParen(); + final var rightParen = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after parameter list"); + + final var returnSpec = typeParser.parseCallableReturnSpec(rightParen); + + final var body = parseBlock(); + return new PbsAst.FunctionDecl( + name.lexeme(), + ReadOnlyList.wrap(parameters), + returnSpec.kind(), + returnSpec.returnType(), + returnSpec.resultErrorType(), + body, + span(fnToken.start(), body.span().getEnd())); + } + + private PbsAst.FunctionSignature parseFunctionSignature( + final PbsToken fnToken, + final ReadOnlyList attributes) { + final var name = consumeCallableName(); + consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after function name"); + final var parameters = typeParser.parseParametersUntilRightParen(); + final var rightParen = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after parameter list"); + + final var returnSpec = typeParser.parseCallableReturnSpec(rightParen); + + long end = returnSpec.returnType().span().getEnd(); + if (REQUIRE_SEMICOLON) { + end = consume(PbsTokenKind.SEMICOLON, "Expected ';' after function signature").end(); + } + + return new PbsAst.FunctionSignature( + name.lexeme(), + ReadOnlyList.wrap(parameters), + returnSpec.kind(), + returnSpec.returnType(), + returnSpec.resultErrorType(), + attributes, + span(fnToken.start(), end)); + } + + private PbsToken consumeCallableName() { + if (cursor.check(PbsTokenKind.IDENTIFIER) || cursor.check(PbsTokenKind.ERROR)) { + return cursor.advance(); + } + final var token = cursor.peek(); + report(token, ParseErrors.E_PARSE_EXPECTED_TOKEN, "Expected a function name, but found " + token.kind()); + if (!cursor.isAtEnd()) { + return cursor.advance(); + } + return token; + } + + private PbsAst.CallbackDecl parseCallbackDeclaration(final PbsToken declareToken) { + final var name = consume(PbsTokenKind.IDENTIFIER, "Expected callback name"); + consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after callback name"); + final var parameters = typeParser.parseParametersUntilRightParen(); + final var rightParen = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after callback parameter list"); + + final var returnSpec = typeParser.parseCallableReturnSpec(rightParen); + + final var semicolon = consume(PbsTokenKind.SEMICOLON, "Expected ';' after callback declaration"); + return new PbsAst.CallbackDecl( + name.lexeme(), + ReadOnlyList.wrap(parameters), + returnSpec.kind(), + returnSpec.returnType(), + returnSpec.resultErrorType(), + span(declareToken.start(), semicolon.end())); + } + + private PbsAst.ConstDecl parseConstDeclaration( + final PbsToken declareToken, + final ReadOnlyList attributes) { + final var name = consume(PbsTokenKind.IDENTIFIER, "Expected constant name"); + consume(PbsTokenKind.COLON, "Expected ':' after constant name"); + final var typeRef = typeParser.parseTypeRef(); + + PbsAst.Expression initializer = null; + if (cursor.match(PbsTokenKind.EQUAL)) { + initializer = exprParser.parseExpression(); + } + + final var semicolon = consume(PbsTokenKind.SEMICOLON, "Expected ';' after constant declaration"); + return new PbsAst.ConstDecl( + name.lexeme(), + typeRef, + initializer, + attributes, + span(declareToken.start(), semicolon.end())); + } + + private PbsAst.Block parseBlock() { + return blockParserDelegate.parse(); + } + + private void rejectAttributesBeforeUnsupportedTopDecl( + final ReadOnlyList attributes, + final String targetSurface) { + if (attributes.isEmpty()) { + return; + } + reportAttributesNotAllowed(attributes, "Attributes are not allowed before " + targetSurface); + } + + private void reportAttributesNotAllowed( + final ReadOnlyList attributes, + final String message) { + for (final var attribute : attributes) { + p.studio.compiler.source.diagnostics.Diagnostics.error(context.diagnostics(), + ParseErrors.E_PARSE_ATTRIBUTES_NOT_ALLOWED.name(), + message, + attribute.span()); + } + } + + private boolean isNotInterfaceMode() { + return context.parseMode() != PbsParser.ParseMode.INTERFACE_MODULE; + } + + private boolean isOrdinaryMode() { + return context.parseMode() == PbsParser.ParseMode.ORDINARY; + } + + 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 Span span(final long start, final long end) { + return context.span(start, end); + } + + private Long parseLongOrNull(final String text) { + try { + return Long.parseLong(text); + } catch (NumberFormatException ignored) { + return null; + } + } + + private record StructBodyParse( + ReadOnlyList methods, + ReadOnlyList ctors, + long end) { + } +} 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 e190b271..b3a17d72 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 @@ -4,13 +4,10 @@ import p.studio.compiler.pbs.ast.PbsAst; import p.studio.compiler.pbs.lexer.PbsToken; import p.studio.compiler.pbs.lexer.PbsTokenKind; import p.studio.compiler.source.Span; -import p.studio.compiler.source.diagnostics.RelatedSpan; import p.studio.compiler.source.identifiers.FileId; import p.studio.utilities.structures.ReadOnlyList; import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; /** * High-level manual parser for PBS source files. @@ -33,6 +30,8 @@ public final class PbsParser { private final PbsExprParser exprParser; private final PbsAttributeParser attributeParser; private final PbsTypeParser typeParser; + private final PbsDeclarationParser declarationParser; + private final PbsTopLevelParser topLevelParser; private PbsParser( final ReadOnlyList tokens, @@ -44,6 +43,15 @@ public final class PbsParser { this.exprParser = new PbsExprParser(context, this::parseExpressionSurfaceBlock); this.attributeParser = new PbsAttributeParser(context, this::isTopLevelRestartToken); this.typeParser = new PbsTypeParser(context); + this.declarationParser = new PbsDeclarationParser( + context, + exprParser, + attributeParser, + typeParser, + this::parseBlock, + this::consumeDeclarationTerminator, + this::synchronizeTopLevel); + this.topLevelParser = new PbsTopLevelParser(context, attributeParser, declarationParser); } /** @@ -68,130 +76,21 @@ public final class PbsParser { * Parses a full file as a sequence of imports and top-level declarations. */ private PbsAst.File parseFile() { - final var imports = new ArrayList(); - final var topDecls = new ArrayList(); - - while (!cursor.isAtEnd()) { - var pendingAttributes = ReadOnlyList.empty(); - if (cursor.check(PbsTokenKind.LEFT_BRACKET)) { - pendingAttributes = parseAttributeList(); - if (isOrdinaryMode()) { - reportAttributesNotAllowed( - pendingAttributes, - "Attributes are not allowed in ordinary .pbs source modules"); - pendingAttributes = ReadOnlyList.empty(); - } - } - - if (cursor.match(PbsTokenKind.IMPORT)) { - rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "import declarations"); - imports.add(parseImport(cursor.previous())); - continue; - } - - if (cursor.match(PbsTokenKind.FN)) { - rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "top-level functions"); - topDecls.add(parseFunction(cursor.previous())); - continue; - } - - if (cursor.match(PbsTokenKind.DECLARE)) { - topDecls.add(parseDeclareTopDecl(cursor.previous(), pendingAttributes)); - continue; - } - - if (cursor.match(PbsTokenKind.IMPLEMENTS)) { - rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "implements declarations"); - topDecls.add(parseImplementsDeclaration(cursor.previous())); - continue; - } - - if (cursor.match(PbsTokenKind.MOD, PbsTokenKind.PUB)) { - rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "barrel-only visibility modifiers"); - final var token = cursor.previous(); - report(token, ParseErrors.E_PARSE_VISIBILITY_IN_SOURCE, - "Visibility modifiers are barrel-only and cannot appear in .pbs declarations"); - topDecls.add(new PbsAst.InvalidDecl("visibility modifier in source", span(token.start(), token.end()))); - synchronizeTopLevel(); - continue; - } - - if (cursor.check(PbsTokenKind.EOF)) { - if (!pendingAttributes.isEmpty()) { - reportAttributesNotAllowed(pendingAttributes, "Attributes must precede a valid declaration"); - } - break; - } - - if (!pendingAttributes.isEmpty()) { - reportAttributesNotAllowed(pendingAttributes, "Attributes must precede a valid declaration"); - } - final var token = cursor.peek(); - report(token, ParseErrors.E_PARSE_UNEXPECTED_TOKEN, - "Expected top-level declaration ('fn', 'declare', 'implements') or import"); - topDecls.add(new PbsAst.InvalidDecl("unexpected top-level token", span(token.start(), token.end()))); - synchronizeTopLevel(); - } - - final var eof = cursor.peek(); - return new PbsAst.File( - ReadOnlyList.wrap(imports), - ReadOnlyList.wrap(topDecls), - span(0, eof.end())); + return topLevelParser.parseFile(); } /** * Parses import syntax and stores it in the AST. */ private PbsAst.ImportDecl parseImport(final PbsToken importToken) { - final var items = new ArrayList(); - if (cursor.match(PbsTokenKind.LEFT_BRACE)) { - while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) { - if (cursor.match(PbsTokenKind.IDENTIFIER)) { - final var itemName = cursor.previous(); - String alias = null; - if (cursor.match(PbsTokenKind.AS)) { - alias = consume(PbsTokenKind.IDENTIFIER, "Expected alias identifier after 'as'").lexeme(); - } - items.add(new PbsAst.ImportItem( - itemName.lexeme(), - alias, - span(itemName.start(), cursor.previous().end()))); - cursor.match(PbsTokenKind.COMMA); - continue; - } - report(cursor.peek(), ParseErrors.E_PARSE_UNEXPECTED_TOKEN, "Invalid import item"); - cursor.advance(); - } - consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' in import list"); - consume(PbsTokenKind.FROM, "Expected 'from' in named import"); - } - - final var moduleRef = parseModuleRef(); - final var semicolon = consume(PbsTokenKind.SEMICOLON, "Expected ';' after import"); - return new PbsAst.ImportDecl( - ReadOnlyList.wrap(items), - moduleRef, - span(importToken.start(), semicolon.end())); + return declarationParser.parseImport(importToken); } /** * Parses a module reference such as {@code @core:math/tools}. */ private PbsAst.ModuleRef parseModuleRef() { - final var at = consume(PbsTokenKind.AT, "Expected '@' in module reference"); - final var project = consume(PbsTokenKind.IDENTIFIER, "Expected project identifier in module reference"); - consume(PbsTokenKind.COLON, "Expected ':' in module reference"); - final var segments = new ArrayList(); - final var firstSegment = consume(PbsTokenKind.IDENTIFIER, "Expected module identifier"); - segments.add(firstSegment.lexeme()); - var end = firstSegment.end(); - while (cursor.match(PbsTokenKind.SLASH)) { - final var segment = consume(PbsTokenKind.IDENTIFIER, "Expected module path segment after '/'"); - segments.add(segment.lexeme()); - end = segment.end(); - } - return new PbsAst.ModuleRef(project.lexeme(), ReadOnlyList.wrap(segments), span(at.start(), end)); + return declarationParser.parseModuleRef(); } /** @@ -200,260 +99,49 @@ public final class PbsParser { private PbsAst.TopDecl parseDeclareTopDecl( final PbsToken declareToken, final ReadOnlyList pendingAttributes) { - if (cursor.match(PbsTokenKind.STRUCT)) { - rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "struct declarations"); - return parseStructDeclaration(declareToken); - } - if (cursor.match(PbsTokenKind.CONTRACT)) { - rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "contract declarations"); - return parseContractDeclaration(declareToken); - } - if (cursor.match(PbsTokenKind.SERVICE)) { - rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "service declarations"); - return parseServiceDeclaration(declareToken); - } - if (cursor.match(PbsTokenKind.ERROR)) { - rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "error declarations"); - return parseErrorDeclaration(declareToken); - } - if (cursor.match(PbsTokenKind.ENUM)) { - rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "enum declarations"); - return parseEnumDeclaration(declareToken); - } - if (cursor.match(PbsTokenKind.CALLBACK)) { - rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "callback declarations"); - return parseCallbackDeclaration(declareToken); - } - if (cursor.match(PbsTokenKind.CONST)) { - return parseConstDeclaration(declareToken, pendingAttributes); - } - if (cursor.match(PbsTokenKind.HOST)) { - rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "host declarations"); - if (isNotInterfaceMode()) { - final var end = consumeDeclarationTerminator(); - report(cursor.previous(), ParseErrors.E_PARSE_RESERVED_DECLARATION, - "'declare host' is reserved and not supported in ordinary source modules"); - return new PbsAst.InvalidDecl("reserved declare host", span(declareToken.start(), end)); - } - return parseHostDeclaration(declareToken); - } - if (cursor.match(PbsTokenKind.BUILTIN)) { - consume(PbsTokenKind.TYPE, "Expected 'type' in 'declare builtin type' declaration"); - if (isNotInterfaceMode()) { - final var end = consumeDeclarationTerminator(); - report(cursor.previous(), ParseErrors.E_PARSE_RESERVED_DECLARATION, - "'declare builtin type' is reserved and not supported in ordinary source modules"); - return new PbsAst.InvalidDecl("reserved declare builtin type", span(declareToken.start(), end)); - } - return parseBuiltinTypeDeclaration(declareToken, pendingAttributes); - } - - if (!pendingAttributes.isEmpty()) { - reportAttributesNotAllowed(pendingAttributes, "Attributes are not valid for this declaration form"); - } - - report(cursor.peek(), ParseErrors.E_PARSE_UNEXPECTED_TOKEN, - "Expected declaration kind after 'declare'"); - synchronizeTopLevel(); - final var token = cursor.previous(); - return new PbsAst.InvalidDecl("invalid declare form", span(declareToken.start(), token.end())); + return declarationParser.parseDeclareTopDecl(declareToken, pendingAttributes); } private PbsAst.HostDecl parseHostDeclaration(final PbsToken declareToken) { - final var name = consume(PbsTokenKind.IDENTIFIER, "Expected host declaration name"); - consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start host declaration body"); - final var signatures = new ArrayList(); - while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) { - var attributes = ReadOnlyList.empty(); - if (cursor.check(PbsTokenKind.LEFT_BRACKET)) { - attributes = parseAttributeList(); - } - if (!cursor.match(PbsTokenKind.FN)) { - report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE, - "Host declaration body accepts only function signatures"); - cursor.advance(); - continue; - } - signatures.add(parseFunctionSignature(cursor.previous(), attributes)); - } - final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end host declaration body"); - return new PbsAst.HostDecl( - name.lexeme(), - ReadOnlyList.wrap(signatures), - span(declareToken.start(), rightBrace.end())); + throw new UnsupportedOperationException(); } private PbsAst.BuiltinTypeDecl parseBuiltinTypeDeclaration( final PbsToken declareToken, final ReadOnlyList declarationAttributes) { - final var name = consume(PbsTokenKind.IDENTIFIER, "Expected builtin type name"); - consume(PbsTokenKind.LEFT_PAREN, "Expected '(' in builtin type declaration"); - final var fields = parseBuiltinTypeFields(); - consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after builtin type fields"); - - consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start builtin type body"); - final var signatures = new ArrayList(); - while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) { - var attributes = ReadOnlyList.empty(); - if (cursor.check(PbsTokenKind.LEFT_BRACKET)) { - attributes = parseAttributeList(); - } - if (!cursor.match(PbsTokenKind.FN)) { - report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE, - "Builtin type body accepts only function signatures"); - cursor.advance(); - continue; - } - signatures.add(parseFunctionSignature(cursor.previous(), attributes)); - } - final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end builtin type body"); - return new PbsAst.BuiltinTypeDecl( - name.lexeme(), - ReadOnlyList.wrap(fields), - ReadOnlyList.wrap(signatures), - declarationAttributes, - span(declareToken.start(), rightBrace.end())); + throw new UnsupportedOperationException(); } private ArrayList parseBuiltinTypeFields() { - final var fields = new ArrayList(); - if (cursor.check(PbsTokenKind.RIGHT_PAREN)) { - return fields; - } - - do { - final var start = cursor.peek(); - var attributes = ReadOnlyList.empty(); - if (cursor.check(PbsTokenKind.LEFT_BRACKET)) { - attributes = parseAttributeList(); - } - - var isPublic = false; - if (cursor.match(PbsTokenKind.PUB)) { - isPublic = true; - if (cursor.match(PbsTokenKind.MUT)) { - final var mutToken = cursor.previous(); - report(mutToken, ParseErrors.E_PARSE_INVALID_DECL_SHAPE, - "'mut' is not allowed in builtin type fields"); - } - } else if (cursor.match(PbsTokenKind.MUT)) { - final var mutToken = cursor.previous(); - report(mutToken, ParseErrors.E_PARSE_INVALID_DECL_SHAPE, - "'mut' is not allowed in builtin type fields"); - } - - final var fieldName = consume(PbsTokenKind.IDENTIFIER, "Expected builtin field name"); - consume(PbsTokenKind.COLON, "Expected ':' after builtin field name"); - final var typeRef = parseTypeRef(); - fields.add(new PbsAst.BuiltinFieldDecl( - fieldName.lexeme(), - typeRef, - isPublic, - attributes, - span(start.start(), typeRef.span().getEnd()))); - } while (cursor.match(PbsTokenKind.COMMA) && !cursor.check(PbsTokenKind.RIGHT_PAREN)); - - return fields; + throw new UnsupportedOperationException(); } private void rejectAttributesBeforeUnsupportedTopDecl( final ReadOnlyList attributes, final String targetSurface) { - if (attributes.isEmpty()) { - return; - } - reportAttributesNotAllowed( - attributes, - "Attributes are not allowed before " + targetSurface); + throw new UnsupportedOperationException(); } private void reportAttributesNotAllowed( final ReadOnlyList attributes, final String message) { - for (final var attribute : attributes) { - p.studio.compiler.source.diagnostics.Diagnostics.error(context.diagnostics(), - ParseErrors.E_PARSE_ATTRIBUTES_NOT_ALLOWED.name(), - message, - attribute.span()); - } + throw new UnsupportedOperationException(); } private boolean isNotInterfaceMode() { - return context.parseMode() != ParseMode.INTERFACE_MODULE; + throw new UnsupportedOperationException(); } private boolean isOrdinaryMode() { - return context.parseMode() == ParseMode.ORDINARY; + throw new UnsupportedOperationException(); } private PbsAst.StructDecl parseStructDeclaration(final PbsToken declareToken) { - final var name = consume(PbsTokenKind.IDENTIFIER, "Expected struct name"); - consume(PbsTokenKind.LEFT_PAREN, "Expected '(' in struct declaration"); - final var fields = parseStructFields(); - consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after struct fields"); - - final var methods = new ArrayList(); - final var ctors = new ArrayList(); - final boolean hasBody; - long end; - if (cursor.match(PbsTokenKind.SEMICOLON)) { - hasBody = false; - end = cursor.previous().end(); - } else if (cursor.match(PbsTokenKind.LEFT_BRACE)) { - hasBody = true; - final var bodyParse = parseStructBodyAndConsumeRightBrace(cursor.previous()); - methods.addAll(bodyParse.methods().asList()); - ctors.addAll(bodyParse.ctors().asList()); - end = bodyParse.end(); - } else { - report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE, - "Struct declaration must end with ';' or contain a '{...}' body"); - hasBody = false; - end = consumeDeclarationTerminator(); - } - - return new PbsAst.StructDecl( - name.lexeme(), - ReadOnlyList.wrap(fields), - ReadOnlyList.wrap(methods), - ReadOnlyList.wrap(ctors), - hasBody, - span(declareToken.start(), end)); + throw new UnsupportedOperationException(); } private ArrayList parseStructFields() { - final var fields = new ArrayList(); - if (cursor.check(PbsTokenKind.RIGHT_PAREN)) { - return fields; - } - - do { - final var start = cursor.peek(); - var isPublic = false; - var isMutable = false; - if (cursor.match(PbsTokenKind.PUB)) { - isPublic = true; - if (cursor.match(PbsTokenKind.MUT)) { - isMutable = true; - } - } else if (cursor.match(PbsTokenKind.MUT)) { - final var mutToken = cursor.previous(); - report(mutToken, ParseErrors.E_PARSE_INVALID_DECL_SHAPE, - "'mut' is only valid immediately after 'pub' in struct fields"); - } - - final var fieldName = consume(PbsTokenKind.IDENTIFIER, "Expected struct field name"); - consume(PbsTokenKind.COLON, "Expected ':' after struct field name"); - final var typeRef = parseTypeRef(); - fields.add(new PbsAst.StructField( - fieldName.lexeme(), - typeRef, - isPublic, - isMutable, - span(start.start(), typeRef.span().getEnd()))); - } while (cursor.match(PbsTokenKind.COMMA) && !cursor.check(PbsTokenKind.RIGHT_PAREN)); - - return fields; + throw new UnsupportedOperationException(); } private ReadOnlyList parseAttributeList() { @@ -461,234 +149,55 @@ public final class PbsParser { } private StructBodyParse parseStructBodyAndConsumeRightBrace(final PbsToken leftBrace) { - final var methods = new ArrayList(); - final var ctors = new ArrayList(); - while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) { - if (cursor.match(PbsTokenKind.FN)) { - methods.add(parseFunctionLike(cursor.previous())); - continue; - } - if (cursor.match(PbsTokenKind.CTOR)) { - ctors.add(parseCtorDeclarationInBody(cursor.previous())); - continue; - } - report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE, - "Struct body accepts only 'fn' methods and 'ctor' declarations"); - cursor.advance(); - } - final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end struct body"); - return new StructBodyParse(ReadOnlyList.wrap(methods), ReadOnlyList.wrap(ctors), rightBrace.end()); + throw new UnsupportedOperationException(); } private PbsAst.CtorDecl parseCtorDeclarationInBody(final PbsToken ctorToken) { - final var name = consume(PbsTokenKind.IDENTIFIER, "Expected constructor name"); - consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after constructor name"); - final var parameters = parseParametersUntilRightParen(); - consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after constructor parameters"); - final var body = parseBlock(); - return new PbsAst.CtorDecl( - name.lexeme(), - ReadOnlyList.wrap(parameters), - body, - span(ctorToken.start(), body.span().getEnd())); + throw new UnsupportedOperationException(); } private PbsAst.ContractDecl parseContractDeclaration(final PbsToken declareToken) { - final var name = consume(PbsTokenKind.IDENTIFIER, "Expected contract name"); - consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start contract body"); - final var signatures = new ArrayList(); - while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) { - var attributes = ReadOnlyList.empty(); - if (cursor.check(PbsTokenKind.LEFT_BRACKET)) { - attributes = parseAttributeList(); - if (isOrdinaryMode()) { - reportAttributesNotAllowed(attributes, "Attributes are not allowed in ordinary .pbs source modules"); - attributes = ReadOnlyList.empty(); - } - } - if (!cursor.match(PbsTokenKind.FN)) { - report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE, - "Contract body accepts only function signatures"); - cursor.advance(); - continue; - } - signatures.add(parseFunctionSignature(cursor.previous(), attributes)); - } - final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end contract body"); - return new PbsAst.ContractDecl(name.lexeme(), ReadOnlyList.wrap(signatures), span(declareToken.start(), rightBrace.end())); + throw new UnsupportedOperationException(); } private PbsAst.ServiceDecl parseServiceDeclaration(final PbsToken declareToken) { - final var name = consume(PbsTokenKind.IDENTIFIER, "Expected service name"); - consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start service body"); - final var methods = new ArrayList(); - while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) { - if (!cursor.match(PbsTokenKind.FN)) { - report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE, - "Service body accepts only method declarations"); - cursor.advance(); - continue; - } - methods.add(parseFunctionLike(cursor.previous())); - } - final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end service body"); - return new PbsAst.ServiceDecl(name.lexeme(), ReadOnlyList.wrap(methods), span(declareToken.start(), rightBrace.end())); + throw new UnsupportedOperationException(); } private PbsAst.ErrorDecl parseErrorDeclaration(final PbsToken declareToken) { - final var name = consume(PbsTokenKind.IDENTIFIER, "Expected error name"); - consume(PbsTokenKind.LEFT_BRACE, "Expected '{' to start error body"); - final var cases = new ArrayList(); - while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) { - final var caseToken = consume(PbsTokenKind.IDENTIFIER, "Expected error case label"); - cases.add(caseToken.lexeme()); - consume(PbsTokenKind.SEMICOLON, "Expected ';' after error case label"); - } - final var rightBrace = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end error body"); - return new PbsAst.ErrorDecl(name.lexeme(), ReadOnlyList.wrap(cases), span(declareToken.start(), rightBrace.end())); + throw new UnsupportedOperationException(); } private PbsAst.EnumDecl parseEnumDeclaration(final PbsToken declareToken) { - final var name = consume(PbsTokenKind.IDENTIFIER, "Expected enum name"); - consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after enum name"); - - final var cases = new ArrayList(); - final var caseLabels = new HashMap(); - final var explicitCaseIds = new HashMap(); - var hasExplicitCases = false; - var hasImplicitCases = false; - if (!cursor.check(PbsTokenKind.RIGHT_PAREN)) { - do { - final var caseName = consume(PbsTokenKind.IDENTIFIER, "Expected enum case label"); - final var caseNameSpan = span(caseName.start(), caseName.end()); - Long explicitValue = null; - if (cursor.match(PbsTokenKind.EQUAL)) { - final var intToken = consume(PbsTokenKind.INT_LITERAL, "Expected integer literal after '=' in enum case"); - final var intTokenSpan = span(intToken.start(), intToken.end()); - explicitValue = parseLongOrNull(intToken.lexeme()); - hasExplicitCases = true; - if (explicitValue == null) { - report(intToken, ParseErrors.E_PARSE_INVALID_ENUM_FORM, - "Invalid explicit enum identifier"); - } else { - final var firstIdSpan = explicitCaseIds.putIfAbsent(explicitValue, intTokenSpan); - if (firstIdSpan != null) { - p.studio.compiler.source.diagnostics.Diagnostics.error(context.diagnostics(), - ParseErrors.E_PARSE_DUPLICATE_ENUM_CASE_ID.name(), - "Duplicate explicit enum identifier '%s'".formatted(explicitValue), - intTokenSpan, - List.of(new RelatedSpan("First explicit enum identifier is here", firstIdSpan))); - } - } - } else { - hasImplicitCases = true; - } - - final var firstLabelSpan = caseLabels.putIfAbsent(caseName.lexeme(), caseNameSpan); - if (firstLabelSpan != null) { - p.studio.compiler.source.diagnostics.Diagnostics.error(context.diagnostics(), - ParseErrors.E_PARSE_DUPLICATE_ENUM_CASE_LABEL.name(), - "Duplicate enum case label '%s'".formatted(caseName.lexeme()), - caseNameSpan, - List.of(new RelatedSpan("First enum case label is here", firstLabelSpan))); - } - - cases.add(new PbsAst.EnumCase(caseName.lexeme(), explicitValue, span(caseName.start(), cursor.previous().end()))); - } while (cursor.match(PbsTokenKind.COMMA) && !cursor.check(PbsTokenKind.RIGHT_PAREN)); - } - - if (hasExplicitCases && hasImplicitCases) { - report(name, ParseErrors.E_PARSE_INVALID_ENUM_FORM, - "Enum declarations cannot mix implicit and explicit case identifiers"); - } - - final var rightParen = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after enum cases"); - final var semicolon = consume(PbsTokenKind.SEMICOLON, "Expected ';' after enum declaration"); - return new PbsAst.EnumDecl(name.lexeme(), ReadOnlyList.wrap(cases), span(declareToken.start(), Math.max(rightParen.end(), semicolon.end()))); + throw new UnsupportedOperationException(); } /** * Parses a top-level function declaration. */ private PbsAst.FunctionDecl parseFunction(final PbsToken fnToken) { - return parseFunctionLike(fnToken); + return declarationParser.parseFunction(fnToken); } private PbsAst.FunctionDecl parseFunctionLike(final PbsToken fnToken) { - final var name = consumeCallableName(); - consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after function name"); - final var parameters = parseParametersUntilRightParen(); - final var rightParen = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after parameter list"); - - final var returnSpec = parseCallableReturnSpec(rightParen); - - final var body = parseBlock(); - return new PbsAst.FunctionDecl( - name.lexeme(), - ReadOnlyList.wrap(parameters), - returnSpec.kind(), - returnSpec.returnType(), - returnSpec.resultErrorType(), - body, - span(fnToken.start(), body.span().getEnd())); + throw new UnsupportedOperationException(); } private PbsAst.FunctionSignature parseFunctionSignature( final PbsToken fnToken, final ReadOnlyList attributes) { - final var name = consumeCallableName(); - consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after function name"); - final var parameters = parseParametersUntilRightParen(); - final var rightParen = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after parameter list"); - - final var returnSpec = parseCallableReturnSpec(rightParen); - - long end = returnSpec.returnType().span().getEnd(); - if (REQUIRE_SEMICOLON) { - end = consume(PbsTokenKind.SEMICOLON, "Expected ';' after function signature").end(); - } - - return new PbsAst.FunctionSignature( - name.lexeme(), - ReadOnlyList.wrap(parameters), - returnSpec.kind(), - returnSpec.returnType(), - returnSpec.resultErrorType(), - attributes, - span(fnToken.start(), end)); + throw new UnsupportedOperationException(); } private PbsToken consumeCallableName() { - if (cursor.check(PbsTokenKind.IDENTIFIER) || cursor.check(PbsTokenKind.ERROR)) { - return cursor.advance(); - } - final var token = cursor.peek(); - report(token, ParseErrors.E_PARSE_EXPECTED_TOKEN, "Expected a function name, but found " + token.kind()); - if (!cursor.isAtEnd()) { - return cursor.advance(); - } - return token; + throw new UnsupportedOperationException(); } /** * Parses `declare callback` declaration. */ private PbsAst.CallbackDecl parseCallbackDeclaration(final PbsToken declareToken) { - final var name = consume(PbsTokenKind.IDENTIFIER, "Expected callback name"); - consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after callback name"); - final var parameters = parseParametersUntilRightParen(); - final var rightParen = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after callback parameter list"); - - final var returnSpec = parseCallableReturnSpec(rightParen); - - final var semicolon = consume(PbsTokenKind.SEMICOLON, "Expected ';' after callback declaration"); - return new PbsAst.CallbackDecl( - name.lexeme(), - ReadOnlyList.wrap(parameters), - returnSpec.kind(), - returnSpec.returnType(), - returnSpec.resultErrorType(), - span(declareToken.start(), semicolon.end())); + throw new UnsupportedOperationException(); } /** @@ -697,60 +206,14 @@ public final class PbsParser { private PbsAst.ConstDecl parseConstDeclaration( final PbsToken declareToken, final ReadOnlyList attributes) { - final var name = consume(PbsTokenKind.IDENTIFIER, "Expected constant name"); - consume(PbsTokenKind.COLON, "Expected ':' after constant name"); - final var typeRef = parseTypeRef(); - - PbsAst.Expression initializer = null; - if (cursor.match(PbsTokenKind.EQUAL)) { - initializer = exprParser.parseExpression(); - } - - final var semicolon = consume(PbsTokenKind.SEMICOLON, "Expected ';' after constant declaration"); - return new PbsAst.ConstDecl( - name.lexeme(), - typeRef, - initializer, - attributes, - span(declareToken.start(), semicolon.end())); + throw new UnsupportedOperationException(); } /** * Parses `implements Contract for Owner using binder { ... }`. */ private PbsAst.ImplementsDecl parseImplementsDeclaration(final PbsToken implementsToken) { - final var contractName = consume(PbsTokenKind.IDENTIFIER, "Expected contract name in 'implements'"); - consume(PbsTokenKind.FOR, "Expected 'for' in 'implements' declaration"); - final var ownerName = consume(PbsTokenKind.IDENTIFIER, "Expected owner name after 'for'"); - consume(PbsTokenKind.USING, "Expected 'using' in 'implements' declaration"); - final var binderName = consume(PbsTokenKind.IDENTIFIER, "Expected binder name after 'using'"); - - final var methods = new ArrayList(); - long end; - if (cursor.check(PbsTokenKind.LEFT_BRACE)) { - cursor.advance(); - while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) { - if (!cursor.match(PbsTokenKind.FN)) { - report(cursor.peek(), ParseErrors.E_PARSE_INVALID_DECL_SHAPE, - "Implements body accepts only function declarations"); - cursor.advance(); - continue; - } - methods.add(parseFunctionLike(cursor.previous())); - } - end = consume(PbsTokenKind.RIGHT_BRACE, "Expected '}' to end implements body").end(); - } else { - report(cursor.peek(), ParseErrors.E_PARSE_EXPECTED_TOKEN, - "Expected '{' to start 'implements' body"); - end = consumeDeclarationTerminator(); - } - - return new PbsAst.ImplementsDecl( - contractName.lexeme(), - ownerName.lexeme(), - binderName.lexeme(), - ReadOnlyList.wrap(methods), - span(implementsToken.start(), end)); + return declarationParser.parseImplementsDeclaration(implementsToken); } private ArrayList parseParametersUntilRightParen() { diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsTopLevelParser.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsTopLevelParser.java new file mode 100644 index 00000000..82031d3e --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/parser/PbsTopLevelParser.java @@ -0,0 +1,141 @@ +package p.studio.compiler.pbs.parser; + +import p.studio.compiler.pbs.ast.PbsAst; +import p.studio.compiler.pbs.lexer.PbsTokenKind; +import p.studio.utilities.structures.ReadOnlyList; + +import java.util.ArrayList; + +final class PbsTopLevelParser { + private final PbsParserContext context; + private final PbsTokenCursor cursor; + private final PbsAttributeParser attributeParser; + private final PbsDeclarationParser declarationParser; + + PbsTopLevelParser( + final PbsParserContext context, + final PbsAttributeParser attributeParser, + final PbsDeclarationParser declarationParser) { + this.context = context; + this.cursor = context.cursor(); + this.attributeParser = attributeParser; + this.declarationParser = declarationParser; + } + + PbsAst.File parseFile() { + final var imports = new ArrayList(); + final var topDecls = new ArrayList(); + + while (!cursor.isAtEnd()) { + var pendingAttributes = ReadOnlyList.empty(); + if (cursor.check(PbsTokenKind.LEFT_BRACKET)) { + pendingAttributes = attributeParser.parseAttributeList(); + if (isOrdinaryMode()) { + reportAttributesNotAllowed( + pendingAttributes, + "Attributes are not allowed in ordinary .pbs source modules"); + pendingAttributes = ReadOnlyList.empty(); + } + } + + if (cursor.match(PbsTokenKind.IMPORT)) { + rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "import declarations"); + imports.add(declarationParser.parseImport(cursor.previous())); + continue; + } + + if (cursor.match(PbsTokenKind.FN)) { + rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "top-level functions"); + topDecls.add(declarationParser.parseFunction(cursor.previous())); + continue; + } + + if (cursor.match(PbsTokenKind.DECLARE)) { + topDecls.add(declarationParser.parseDeclareTopDecl(cursor.previous(), pendingAttributes)); + continue; + } + + if (cursor.match(PbsTokenKind.IMPLEMENTS)) { + rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "implements declarations"); + topDecls.add(declarationParser.parseImplementsDeclaration(cursor.previous())); + continue; + } + + if (cursor.match(PbsTokenKind.MOD, PbsTokenKind.PUB)) { + rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "barrel-only visibility modifiers"); + final var token = cursor.previous(); + context.report(token, ParseErrors.E_PARSE_VISIBILITY_IN_SOURCE, + "Visibility modifiers are barrel-only and cannot appear in .pbs declarations"); + topDecls.add(new PbsAst.InvalidDecl("visibility modifier in source", context.span(token.start(), token.end()))); + synchronizeTopLevel(); + continue; + } + + if (cursor.check(PbsTokenKind.EOF)) { + if (!pendingAttributes.isEmpty()) { + reportAttributesNotAllowed(pendingAttributes, "Attributes must precede a valid declaration"); + } + break; + } + + if (!pendingAttributes.isEmpty()) { + reportAttributesNotAllowed(pendingAttributes, "Attributes must precede a valid declaration"); + } + final var token = cursor.peek(); + context.report(token, ParseErrors.E_PARSE_UNEXPECTED_TOKEN, + "Expected top-level declaration ('fn', 'declare', 'implements') or import"); + topDecls.add(new PbsAst.InvalidDecl("unexpected top-level token", context.span(token.start(), token.end()))); + synchronizeTopLevel(); + } + + final var eof = cursor.peek(); + return new PbsAst.File( + ReadOnlyList.wrap(imports), + ReadOnlyList.wrap(topDecls), + context.span(0, eof.end())); + } + + private void rejectAttributesBeforeUnsupportedTopDecl( + final ReadOnlyList attributes, + final String targetSurface) { + if (attributes.isEmpty()) { + return; + } + reportAttributesNotAllowed(attributes, "Attributes are not allowed before " + targetSurface); + } + + private void reportAttributesNotAllowed( + final ReadOnlyList attributes, + final String message) { + for (final var attribute : attributes) { + p.studio.compiler.source.diagnostics.Diagnostics.error(context.diagnostics(), + ParseErrors.E_PARSE_ATTRIBUTES_NOT_ALLOWED.name(), + message, + attribute.span()); + } + } + + private boolean isOrdinaryMode() { + return context.parseMode() == PbsParser.ParseMode.ORDINARY; + } + + private void synchronizeTopLevel() { + while (!cursor.isAtEnd()) { + if (isTopLevelRestartToken(cursor.peek().kind())) { + return; + } + if (cursor.match(PbsTokenKind.SEMICOLON)) { + return; + } + cursor.advance(); + } + } + + private boolean isTopLevelRestartToken(final PbsTokenKind kind) { + return kind == PbsTokenKind.FN + || kind == PbsTokenKind.IMPORT + || kind == PbsTokenKind.DECLARE + || kind == PbsTokenKind.IMPLEMENTS + || kind == PbsTokenKind.LEFT_BRACKET; + } +}