implements PR024

This commit is contained in:
bQUARKz 2026-03-06 12:07:38 +00:00
parent 57f1a617ac
commit 0770bb869a
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
9 changed files with 459 additions and 203 deletions

View File

@ -1,55 +0,0 @@
# PR-022 - PBS Source Kind and Module Origin in Frontend Pipeline
## Briefing
O pipeline atual trata todo `.pbs` como fonte ordinaria de projeto.
As specs exigem distincao entre source ordinario e interface-module de SDK/stdlib.
Esta PR introduz classificacao explicita de origem de fonte no frontend para permitir regras diferentes por contexto.
## Motivation
Sem classificacao de origem, o parser e os validadores nao conseguem aplicar corretamente:
- rejeicao de forms reservadas em projeto ordinario,
- admissao de forms reservadas em interface modules,
- e ownership correto de diagnosticos de resolucao/import.
## Target
- `FrontendPhaseContext` e dados de projeto consumidos pelo frontend PBS.
- `PBSFrontendPhaseService` e fluxo de descoberta/roteamento de fontes.
## Scope
- Introduzir `SourceKind`/`ModuleOrigin` no pipeline (`PROJECT`, `SDK_INTERFACE`).
- Preservar comportamento atual para fontes ordinarias.
- Nao alterar ainda parser/semantica de forms reservadas (vem nas PRs seguintes).
## Method
- Adicionar modelagem de origem por unidade de modulo no frontend phase.
- Classificar modulo pelo project space (`@core:*`, `@sdk:*` vs projetos ordinarios), conforme specs de resolucao.
- Propagar `SourceKind` ate o ponto de parsing/validacao para habilitar gating por contexto nas PRs seguintes.
## Acceptance Criteria
- Cada arquivo `.pbs` processado pelo frontend possui `SourceKind` deterministico.
- Modulos ordinarios continuam no caminho atual sem regressao.
- Modulos reservados ficam marcados para fluxo de interface-module.
## Tests
- Testes unitarios para classificacao de origem de modulo por project space.
- Testes de nao regressao em projeto ordinario sem stdlib.
## Non-Goals
- Implementar stdlib loader completo.
- Alterar AST ou parser de forms reservadas.
## Affected Documents
- `docs/pbs/specs/3. Core Syntax Specification.md`
- `docs/pbs/specs/5. Manifest, Stdlib, and SDK Resolution Specification.md`
- `docs/pbs/specs/8. Stdlib Environment Packaging and Loading Specification.md`

View File

@ -1,53 +0,0 @@
# PR-023 - PBS Stdlib Environment Resolver and Interface Loader
## Briefing
Para suportar interface modules reais, o frontend precisa resolver project spaces reservados (`@core:*`, `@sdk:*`) fora da trilha de dependencias ordinarias.
Esta PR introduz o loader/resolver de stdlib environment com fronteiras explicitas.
## Motivation
As specs definem que stdlib e import ordinario possuem resolucoes distintas e sem fallback cruzado.
Sem essa separacao, interface modules nao podem ser montados de forma normativa.
## Target
- Abstracoes de stdlib no frontend/compiler pipeline.
- Fluxo de import para project spaces reservados.
## Scope
- Introduzir `StdlibEnvironment`, `StdlibModuleResolver`, `StdlibModuleSource`, `InterfaceModuleLoader`.
- Integrar roteamento no `PBSFrontendPhaseService` para imports reservados.
- Manter resolucao ordinaria inalterada para `@project:*` nao reservado.
## Method
- Implementar resolucao por linha de stdlib selecionada no root manifest.
- Carregar modulo reservado como fonte PBS real (nao tabela hardcoded).
- Emitir erro deterministico quando modulo reservado nao for encontrado/carregado.
## Acceptance Criteria
- Imports reservados nao passam por resolver ordinario.
- Imports ordinarios nao passam por stdlib resolver.
- Falha de resolucao reservada gera diagnostico deterministico e atribuivel.
## Tests
- Fixture: import reservado resolvido via stdlib resolver.
- Fixture: modulo reservado inexistente com erro deterministico.
- Fixture: tentativa de resolver reservado via trilha ordinaria rejeitada.
## Non-Goals
- Validacao semantica de forms reservadas dentro do modulo carregado.
- Extracao de metadata de builtins/host para lowering.
## Affected Documents
- `docs/pbs/specs/5. Manifest, Stdlib, and SDK Resolution Specification.md`
- `docs/pbs/specs/8. Stdlib Environment Packaging and Loading Specification.md`
- `docs/pbs/specs/12. Diagnostics Specification.md`

View File

@ -1,55 +0,0 @@
# PR-024 - PBS Parser and AST Support for Interface-Module Mode
## Briefing
Hoje o parser rejeita atributos no topo e `declare host`/`declare builtin type` sempre.
Em interface-module mode, essas forms reservadas devem ser parseadas e representadas no AST.
## Motivation
Sem AST/parser para forms reservadas:
- nao ha como validar semantica de SDK/stdlib,
- nao ha como extrair metadata (`Host`, `BuiltinType`, `Slot`, `IntrinsicCall`),
- e nao ha base para linking/lowering dessas surfaces.
## Target
- `PbsAst` (novos nodes de declaracao e metadata de atributo).
- `PbsParser` com `SourceKind`/mode-aware parsing.
## Scope
- Adicionar no AST: `HostDecl`, `BuiltinTypeDecl`, shapes de atributo e anexacao por posicao valida.
- Em modo `PROJECT`: manter rejeicao de forms reservadas e atributos fora de regras.
- Em modo `SDK_INTERFACE`: parsear forms reservadas e attributes permitidas.
## Method
- Tornar parser mode-aware (`ORDINARY`, `INTERFACE_MODULE`).
- Substituir rejeicao hardcoded por gating por mode.
- Preservar spans, recovery e diagnosticos deterministicos.
## Acceptance Criteria
- Modulo ordinario continua rejeitando `declare host`/`declare builtin type` e usos invalidos de atributos.
- Interface module aceita as forms reservadas sintaticamente previstas.
- AST exposto inclui metadata necessaria para fases seguintes.
## Tests
- Parser tests para ordinario vs interface-module com mesmos tokens.
- Tests de recovery e spans nas novas declaracoes.
- Tests negativos para formas reservadas malformadas.
## Non-Goals
- Julgamento semantico completo das attributes reservadas.
- Resolucao de imports reservados (ja coberta em PR anterior).
## Affected Documents
- `docs/pbs/specs/3. Core Syntax Specification.md`
- `docs/pbs/specs/11. AST Specification.md`
- `docs/pbs/specs/12. Diagnostics Specification.md`

View File

@ -65,9 +65,50 @@ public final class PbsAst {
Span span) {
}
public record Attribute(
String name,
ReadOnlyList<AttributeArgument> arguments,
Span span) {
}
public record AttributeArgument(
String name,
AttributeValue value,
Span span) {
}
public sealed interface AttributeValue permits AttributeStringValue,
AttributeIntValue,
AttributeBoolValue,
AttributeErrorValue {
Span span();
}
public record AttributeStringValue(
String value,
Span span) implements AttributeValue {
}
public record AttributeIntValue(
long value,
Span span) implements AttributeValue {
}
public record AttributeBoolValue(
boolean value,
Span span) implements AttributeValue {
}
public record AttributeErrorValue(
String value,
Span span) implements AttributeValue {
}
public sealed interface TopDecl permits FunctionDecl,
StructDecl,
ContractDecl,
HostDecl,
BuiltinTypeDecl,
ServiceDecl,
ErrorDecl,
EnumDecl,
@ -103,6 +144,20 @@ public final class PbsAst {
Span span) implements TopDecl {
}
public record HostDecl(
String name,
ReadOnlyList<FunctionSignature> signatures,
Span span) implements TopDecl {
}
public record BuiltinTypeDecl(
String name,
ReadOnlyList<BuiltinFieldDecl> fields,
ReadOnlyList<FunctionSignature> signatures,
ReadOnlyList<Attribute> attributes,
Span span) implements TopDecl {
}
public record ServiceDecl(
String name,
ReadOnlyList<FunctionDecl> methods,
@ -134,6 +189,7 @@ public final class PbsAst {
String name,
TypeRef explicitType,
Expression initializer,
ReadOnlyList<Attribute> attributes,
Span span) implements TopDecl {
}
@ -212,12 +268,21 @@ public final class PbsAst {
Span span) {
}
public record BuiltinFieldDecl(
String name,
TypeRef typeRef,
boolean isPublic,
ReadOnlyList<Attribute> attributes,
Span span) {
}
public record FunctionSignature(
String name,
ReadOnlyList<Parameter> parameters,
ReturnKind returnKind,
TypeRef returnType,
TypeRef resultErrorType,
ReadOnlyList<Attribute> attributes,
Span span) {
}

View File

@ -23,20 +23,28 @@ import java.util.List;
public final class PbsParser {
private static final int MAX_NAMED_TUPLE_ARITY = 6;
public enum ParseMode {
ORDINARY,
INTERFACE_MODULE
}
private final PbsTokenCursor cursor;
private final PbsExprParser exprParser;
private final FileId fileId;
private final DiagnosticSink diagnostics;
private final ParseMode parseMode;
private int loopDepth;
private PbsParser(
final ReadOnlyList<PbsToken> tokens,
final FileId fileId,
final DiagnosticSink diagnostics) {
final DiagnosticSink diagnostics,
final ParseMode parseMode) {
this.cursor = new PbsTokenCursor(tokens);
this.exprParser = new PbsExprParser(cursor, fileId, diagnostics, this::parseExpressionSurfaceBlock);
this.fileId = fileId;
this.diagnostics = diagnostics;
this.parseMode = parseMode;
}
/**
@ -46,7 +54,15 @@ public final class PbsParser {
final ReadOnlyList<PbsToken> tokens,
final FileId fileId,
final DiagnosticSink diagnostics) {
return new PbsParser(tokens, fileId, diagnostics).parseFile();
return parse(tokens, fileId, diagnostics, ParseMode.ORDINARY);
}
public static PbsAst.File parse(
final ReadOnlyList<PbsToken> tokens,
final FileId fileId,
final DiagnosticSink diagnostics,
final ParseMode parseMode) {
return new PbsParser(tokens, fileId, diagnostics, parseMode).parseFile();
}
/**
@ -57,31 +73,42 @@ public final class PbsParser {
final var topDecls = new ArrayList<PbsAst.TopDecl>();
while (!cursor.isAtEnd()) {
var pendingAttributes = ReadOnlyList.<PbsAst.Attribute>empty();
if (cursor.check(PbsTokenKind.LEFT_BRACKET)) {
parseRejectedAttributeList();
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()));
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");
@ -91,9 +118,15 @@ public final class PbsParser {
}
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");
@ -165,40 +198,59 @@ public final class PbsParser {
/**
* Parses declarations introduced by 'declare'.
*/
private PbsAst.TopDecl parseDeclareTopDecl(final PbsToken declareToken) {
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);
return parseConstDeclaration(declareToken, pendingAttributes);
}
if (cursor.match(PbsTokenKind.HOST)) {
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));
rejectAttributesBeforeUnsupportedTopDecl(pendingAttributes, "host declarations");
if (!isInterfaceMode()) {
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");
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));
if (!isInterfaceMode()) {
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,
@ -208,6 +260,133 @@ public final class PbsParser {
return new PbsAst.InvalidDecl("invalid declare form", span(declareToken.start(), token.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 = 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(), true, 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 = 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(), true, 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 = 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;
}
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(diagnostics,
ParseErrors.E_PARSE_ATTRIBUTES_NOT_ALLOWED.name(),
message,
attribute.span());
}
}
private boolean isInterfaceMode() {
return parseMode == ParseMode.INTERFACE_MODULE;
}
private boolean isOrdinaryMode() {
return parseMode == ParseMode.ORDINARY;
}
private PbsAst.StructDecl parseStructDeclaration(final PbsToken declareToken) {
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected struct name");
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' in struct declaration");
@ -278,21 +457,20 @@ public final class PbsParser {
return fields;
}
private void parseRejectedAttributeList() {
private ReadOnlyList<PbsAst.Attribute> parseAttributeList() {
final var attributes = new ArrayList<PbsAst.Attribute>();
while (cursor.check(PbsTokenKind.LEFT_BRACKET)) {
final var attributeSpan = parseAttribute();
p.studio.compiler.source.diagnostics.Diagnostics.error(diagnostics,
ParseErrors.E_PARSE_ATTRIBUTES_NOT_ALLOWED.name(),
"Attributes are not allowed in ordinary .pbs source modules",
attributeSpan);
attributes.add(parseAttribute());
}
return ReadOnlyList.wrap(attributes);
}
private Span parseAttribute() {
private PbsAst.Attribute parseAttribute() {
final var leftBracket = consume(PbsTokenKind.LEFT_BRACKET, "Expected '[' to start attribute");
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected attribute identifier");
final var arguments = new ArrayList<PbsAst.AttributeArgument>();
if (cursor.match(PbsTokenKind.LEFT_PAREN)) {
parseAttributeArguments();
parseAttributeArguments(arguments);
}
long end = Math.max(leftBracket.end(), name.end());
@ -302,13 +480,16 @@ public final class PbsParser {
report(cursor.peek(), ParseErrors.E_PARSE_EXPECTED_TOKEN, "Expected ']' to close attribute");
end = recoverUntilAttributeCloseOrTopLevel(end);
}
return span(leftBracket.start(), end);
return new PbsAst.Attribute(
name.lexeme(),
ReadOnlyList.wrap(arguments),
span(leftBracket.start(), end));
}
private void parseAttributeArguments() {
private void parseAttributeArguments(final ArrayList<PbsAst.AttributeArgument> arguments) {
if (!cursor.check(PbsTokenKind.RIGHT_PAREN)) {
do {
parseAttributeArgument();
arguments.add(parseAttributeArgument());
} while (cursor.match(PbsTokenKind.COMMA) && !cursor.check(PbsTokenKind.RIGHT_PAREN));
}
@ -319,24 +500,49 @@ public final class PbsParser {
recoverUntilAttributeArgumentClose();
}
private void parseAttributeArgument() {
consume(PbsTokenKind.IDENTIFIER, "Expected attribute argument name");
private PbsAst.AttributeArgument parseAttributeArgument() {
final var argumentStart = cursor.peek();
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected attribute argument name");
consume(PbsTokenKind.EQUAL, "Expected '=' in attribute argument");
parseAttributeValue();
final var value = parseAttributeValue();
return new PbsAst.AttributeArgument(
name.lexeme(),
value,
span(argumentStart.start(), value.span().getEnd()));
}
private void parseAttributeValue() {
if (cursor.match(PbsTokenKind.STRING_LITERAL, PbsTokenKind.INT_LITERAL, PbsTokenKind.TRUE, PbsTokenKind.FALSE)) {
return;
private PbsAst.AttributeValue parseAttributeValue() {
if (cursor.match(PbsTokenKind.STRING_LITERAL)) {
final var token = cursor.previous();
return new PbsAst.AttributeStringValue(token.lexeme(), span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.INT_LITERAL)) {
final var token = cursor.previous();
final var parsedLong = parseLongOrNull(token.lexeme());
if (parsedLong != null) {
return new PbsAst.AttributeIntValue(parsedLong, span(token.start(), token.end()));
}
report(token, ParseErrors.E_PARSE_EXPECTED_TOKEN, "Invalid integer literal in attribute argument");
return new PbsAst.AttributeErrorValue(token.lexeme(), span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.TRUE)) {
final var token = cursor.previous();
return new PbsAst.AttributeBoolValue(true, span(token.start(), token.end()));
}
if (cursor.match(PbsTokenKind.FALSE)) {
final var token = cursor.previous();
return new PbsAst.AttributeBoolValue(false, span(token.start(), token.end()));
}
report(cursor.peek(), ParseErrors.E_PARSE_EXPECTED_TOKEN, "Expected attribute value (string, int, or bool)");
final var token = cursor.peek();
report(token, ParseErrors.E_PARSE_EXPECTED_TOKEN, "Expected attribute value (string, int, or bool)");
if (!cursor.isAtEnd()
&& !cursor.check(PbsTokenKind.COMMA)
&& !cursor.check(PbsTokenKind.RIGHT_PAREN)
&& !cursor.check(PbsTokenKind.RIGHT_BRACKET)) {
cursor.advance();
}
return new PbsAst.AttributeErrorValue("<error>", span(token.start(), token.end()));
}
private long recoverUntilAttributeCloseOrTopLevel(final long fallbackEnd) {
@ -403,13 +609,21 @@ public final class PbsParser {
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(), true));
signatures.add(parseFunctionSignature(cursor.previous(), true, 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()));
@ -530,7 +744,10 @@ public final class PbsParser {
span(fnToken.start(), body.span().getEnd()));
}
private PbsAst.FunctionSignature parseFunctionSignature(final PbsToken fnToken, final boolean requireSemicolon) {
private PbsAst.FunctionSignature parseFunctionSignature(
final PbsToken fnToken,
final boolean requireSemicolon,
final ReadOnlyList<PbsAst.Attribute> attributes) {
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected function name");
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after function name");
final var parameters = parseParametersUntilRightParen();
@ -549,6 +766,7 @@ public final class PbsParser {
returnSpec.kind,
returnSpec.returnType,
returnSpec.resultErrorType,
attributes,
span(fnToken.start(), end));
}
@ -576,7 +794,9 @@ public final class PbsParser {
/**
* Parses `declare const` declaration.
*/
private PbsAst.ConstDecl parseConstDeclaration(final PbsToken declareToken) {
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();
@ -591,6 +811,7 @@ public final class PbsParser {
name.lexeme(),
typeRef,
initializer,
attributes,
span(declareToken.start(), semicolon.end()));
}

View File

@ -49,7 +49,7 @@ public final class InterfaceModuleLoader {
final FileId fileId,
final DiagnosticSink diagnostics) {
final var tokens = PbsLexer.lex(source, fileId, diagnostics);
return PbsParser.parse(tokens, fileId, diagnostics);
return PbsParser.parse(tokens, fileId, diagnostics, PbsParser.ParseMode.INTERFACE_MODULE);
}
private PbsAst.BarrelFile parseBarrel(
@ -60,4 +60,3 @@ public final class InterfaceModuleLoader {
return PbsBarrelParser.parse(tokens, fileId, diagnostics);
}
}

View File

@ -82,7 +82,7 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
case "pbs" -> {
moduleKeyByFile.put(fId, moduleKey);
final var parseErrorBaseline = diagnostics.errorCount();
final var ast = parseSourceFile(fId, utf8Content, diagnostics);
final var ast = parseSourceFile(fId, utf8Content, diagnostics, projectSourceKind);
moduleUnit.sources.add(new PbsModuleVisibilityValidator.SourceFile(fId, ast));
parsedSourceFiles.add(new ParsedSourceFile(fId, ast, moduleKey, projectSourceKind));
if (diagnostics.errorCount() > parseErrorBaseline) {
@ -210,9 +210,13 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
private PbsAst.File parseSourceFile(
final FileId fileId,
final String source,
final DiagnosticSink diagnostics) {
final DiagnosticSink diagnostics,
final SourceKind sourceKind) {
final var tokens = PbsLexer.lex(source, fileId, diagnostics);
return PbsParser.parse(tokens, fileId, diagnostics);
final var parseMode = sourceKind == SourceKind.SDK_INTERFACE
? PbsParser.ParseMode.INTERFACE_MODULE
: PbsParser.ParseMode.ORDINARY;
return PbsParser.parse(tokens, fileId, diagnostics, parseMode);
}
private PbsAst.BarrelFile parseBarrelFile(

View File

@ -216,6 +216,78 @@ class PbsParserTest {
assertInstanceOf(PbsAst.FunctionDecl.class, ast.topDecls().get(2));
}
@Test
void shouldParseReservedDeclarationsInInterfaceModuleMode() {
final var source = """
[BuiltinType(name = "Color", version = 1)]
declare builtin type Color(
[Slot(index = 0)] pub r: int,
[Slot(index = 1)] pub g: int,
[Slot(index = 2)] pub b: int,
) {
[IntrinsicCall(name = "color.pack", version = 1)]
fn pack() -> int;
}
declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)]
fn draw(x: int, y: int, color: Color) -> void;
}
[BuiltinConst(target = "Color", name = "white", version = 1)]
declare const WHITE: Color;
""";
final var diagnostics = DiagnosticSink.empty();
final var fileId = new FileId(0);
final PbsAst.File ast = PbsParser.parse(
PbsLexer.lex(source, fileId, diagnostics),
fileId,
diagnostics,
PbsParser.ParseMode.INTERFACE_MODULE);
assertTrue(diagnostics.isEmpty(), "Parser should accept reserved interface declarations in interface-module mode");
assertEquals(3, ast.topDecls().size());
final var builtinType = assertInstanceOf(PbsAst.BuiltinTypeDecl.class, ast.topDecls().get(0));
assertEquals(1, builtinType.attributes().size());
assertEquals(3, builtinType.fields().size());
assertEquals(1, builtinType.signatures().size());
assertEquals(1, builtinType.fields().getFirst().attributes().size());
assertEquals(1, builtinType.signatures().getFirst().attributes().size());
final var hostDecl = assertInstanceOf(PbsAst.HostDecl.class, ast.topDecls().get(1));
assertEquals(1, hostDecl.signatures().size());
assertEquals(1, hostDecl.signatures().getFirst().attributes().size());
final var constDecl = assertInstanceOf(PbsAst.ConstDecl.class, ast.topDecls().get(2));
assertEquals(1, constDecl.attributes().size());
}
@Test
void shouldRecoverFromMalformedReservedDeclarationsInInterfaceModuleMode() {
final var source = """
declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)]
fn draw(x: int, y: int) -> void;
unexpected_token
}
fn ok() -> int { return 1; }
""";
final var diagnostics = DiagnosticSink.empty();
final var fileId = new FileId(0);
final PbsAst.File ast = PbsParser.parse(
PbsLexer.lex(source, fileId, diagnostics),
fileId,
diagnostics,
PbsParser.ParseMode.INTERFACE_MODULE);
assertTrue(diagnostics.hasErrors(), "Malformed reserved forms must still emit deterministic parse diagnostics");
assertTrue(diagnostics.stream().anyMatch(d -> d.getCode().equals(ParseErrors.E_PARSE_INVALID_DECL_SHAPE.name())));
assertEquals(2, ast.topDecls().size());
assertInstanceOf(PbsAst.HostDecl.class, ast.topDecls().get(0));
assertInstanceOf(PbsAst.FunctionDecl.class, ast.topDecls().get(1));
}
@Test
void shouldRejectFunctionElseFallbackSurface() {
final var source = """

View File

@ -223,6 +223,64 @@ class PBSFrontendPhaseServiceTest {
assertEquals(0, irBackend.getFunctions().size());
}
@Test
void shouldParseReservedDeclarationsWhenSourceKindIsSdkInterface() throws IOException {
final var projectRoot = tempDir.resolve("project-sdk-interface-reserved");
final var sourceRoot = projectRoot.resolve("src");
final var modulePath = sourceRoot.resolve("gfx");
Files.createDirectories(modulePath);
final var sourceFile = modulePath.resolve("source.pbs");
final var modBarrel = modulePath.resolve("mod.barrel");
Files.writeString(sourceFile, """
[BuiltinType(name = "Color", version = 1)]
declare builtin type Color(
[Slot(index = 0)] pub r: int,
[Slot(index = 1)] pub g: int,
[Slot(index = 2)] pub b: int
) {
[IntrinsicCall(name = "color.pack", version = 1)]
fn pack() -> int;
}
declare host Gfx {
[Host(module = "gfx", name = "draw_pixel", version = 1)]
fn draw(x: int, y: int, color: Color) -> void;
}
""");
Files.writeString(modBarrel, "");
final var projectTable = new ProjectTable();
final var fileTable = new FileTable(1);
final var projectId = projectTable.register(ProjectDescriptor.builder()
.rootPath(projectRoot)
.name("gfx-sdk")
.version("1.0.0")
.sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
.sourceKind(SourceKind.SDK_INTERFACE)
.build());
registerFile(projectId, projectRoot, sourceFile, fileTable);
registerFile(projectId, projectRoot, modBarrel, fileTable);
final var ctx = new FrontendPhaseContext(
projectTable,
fileTable,
new BuildStack(ReadOnlyList.wrap(List.of(projectId))));
final var diagnostics = DiagnosticSink.empty();
final var irBackend = new PBSFrontendPhaseService().compile(
ctx,
diagnostics,
LogAggregator.empty(),
BuildingIssueSink.empty());
assertTrue(diagnostics.stream().noneMatch(d ->
d.getCode().equals(PbsLinkErrors.E_LINK_UNRESOLVED_BARREL_ENTRY.name())));
assertTrue(diagnostics.stream().noneMatch(d ->
d.getCode().equals(PbsLinkErrors.E_LINK_MISSING_BARREL.name())));
assertEquals(0, irBackend.getFunctions().size());
}
@Test
void shouldResolveReservedImportsThroughStdlibResolverForSelectedMajor() throws IOException {
final var projectRoot = tempDir.resolve("project-stdlib");