implements PR024
This commit is contained in:
parent
57f1a617ac
commit
0770bb869a
@ -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`
|
||||
@ -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`
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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) {
|
||||
}
|
||||
|
||||
|
||||
@ -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()));
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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 = """
|
||||
|
||||
@ -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");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user