implements PR-10.3

This commit is contained in:
bQUARKz 2026-03-10 09:46:56 +00:00
parent 6b85dc33e4
commit 416c4e7a11
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
3 changed files with 860 additions and 574 deletions

View File

@ -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<PbsAst.ImportItem>();
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<String>();
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<PbsAst.Attribute> 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<PbsAst.FunctionDecl>();
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<PbsAst.FunctionSignature>();
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
var attributes = ReadOnlyList.<PbsAst.Attribute>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<PbsAst.Attribute> 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<PbsAst.FunctionSignature>();
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
var attributes = ReadOnlyList.<PbsAst.Attribute>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<PbsAst.BuiltinFieldDecl> parseBuiltinTypeFields() {
final var fields = new ArrayList<PbsAst.BuiltinFieldDecl>();
if (cursor.check(PbsTokenKind.RIGHT_PAREN)) {
return fields;
}
do {
final var start = cursor.peek();
var attributes = ReadOnlyList.<PbsAst.Attribute>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<PbsAst.FunctionDecl>();
final var ctors = new ArrayList<PbsAst.CtorDecl>();
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<PbsAst.StructField> parseStructFields() {
final var fields = new ArrayList<PbsAst.StructField>();
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<PbsAst.FunctionDecl>();
final var ctors = new ArrayList<PbsAst.CtorDecl>();
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<PbsAst.FunctionSignature>();
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
var attributes = ReadOnlyList.<PbsAst.Attribute>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<PbsAst.FunctionDecl>();
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<String>();
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<PbsAst.EnumCase>();
final var caseLabels = new HashMap<String, Span>();
final var explicitCaseIds = new HashMap<Long, Span>();
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<PbsAst.Attribute> 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<PbsAst.Attribute> 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<PbsAst.Attribute> attributes,
final String targetSurface) {
if (attributes.isEmpty()) {
return;
}
reportAttributesNotAllowed(attributes, "Attributes are not allowed before " + targetSurface);
}
private void reportAttributesNotAllowed(
final ReadOnlyList<PbsAst.Attribute> 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<PbsAst.FunctionDecl> methods,
ReadOnlyList<PbsAst.CtorDecl> ctors,
long end) {
}
}

View File

@ -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<PbsToken> 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<PbsAst.ImportDecl>();
final var topDecls = new ArrayList<PbsAst.TopDecl>();
while (!cursor.isAtEnd()) {
var pendingAttributes = ReadOnlyList.<PbsAst.Attribute>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<PbsAst.ImportItem>();
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<String>();
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<PbsAst.Attribute> 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<PbsAst.FunctionSignature>();
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
var attributes = ReadOnlyList.<PbsAst.Attribute>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<PbsAst.Attribute> 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<PbsAst.FunctionSignature>();
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
var attributes = ReadOnlyList.<PbsAst.Attribute>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<PbsAst.BuiltinFieldDecl> parseBuiltinTypeFields() {
final var fields = new ArrayList<PbsAst.BuiltinFieldDecl>();
if (cursor.check(PbsTokenKind.RIGHT_PAREN)) {
return fields;
}
do {
final var start = cursor.peek();
var attributes = ReadOnlyList.<PbsAst.Attribute>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<PbsAst.Attribute> attributes,
final String targetSurface) {
if (attributes.isEmpty()) {
return;
}
reportAttributesNotAllowed(
attributes,
"Attributes are not allowed before " + targetSurface);
throw new UnsupportedOperationException();
}
private void reportAttributesNotAllowed(
final ReadOnlyList<PbsAst.Attribute> 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<PbsAst.FunctionDecl>();
final var ctors = new ArrayList<PbsAst.CtorDecl>();
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<PbsAst.StructField> parseStructFields() {
final var fields = new ArrayList<PbsAst.StructField>();
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<PbsAst.Attribute> parseAttributeList() {
@ -461,234 +149,55 @@ public final class PbsParser {
}
private StructBodyParse parseStructBodyAndConsumeRightBrace(final PbsToken leftBrace) {
final var methods = new ArrayList<PbsAst.FunctionDecl>();
final var ctors = new ArrayList<PbsAst.CtorDecl>();
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<PbsAst.FunctionSignature>();
while (!cursor.check(PbsTokenKind.RIGHT_BRACE) && !cursor.isAtEnd()) {
var attributes = ReadOnlyList.<PbsAst.Attribute>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<PbsAst.FunctionDecl>();
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<String>();
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<PbsAst.EnumCase>();
final var caseLabels = new HashMap<String, Span>();
final var explicitCaseIds = new HashMap<Long, Span>();
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<PbsAst.Attribute> 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<PbsAst.Attribute> 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<PbsAst.FunctionDecl>();
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<PbsAst.Parameter> parseParametersUntilRightParen() {

View File

@ -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<PbsAst.ImportDecl>();
final var topDecls = new ArrayList<PbsAst.TopDecl>();
while (!cursor.isAtEnd()) {
var pendingAttributes = ReadOnlyList.<PbsAst.Attribute>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<PbsAst.Attribute> attributes,
final String targetSurface) {
if (attributes.isEmpty()) {
return;
}
reportAttributesNotAllowed(attributes, "Attributes are not allowed before " + targetSurface);
}
private void reportAttributesNotAllowed(
final ReadOnlyList<PbsAst.Attribute> 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;
}
}