implements PR006
This commit is contained in:
parent
931c5d96a3
commit
f60346e174
@ -1,35 +0,0 @@
|
|||||||
# PR-006 - PBS Barrel and Module Visibility
|
|
||||||
|
|
||||||
## Briefing
|
|
||||||
A spec define `mod.barrel` como fonte unica de visibilidade, mas o frontend atual ignora `.barrel`.
|
|
||||||
Este PR implementa parser/validacao de barrel e aplica regras minimas de visibilidade/exportacao em nivel de modulo.
|
|
||||||
|
|
||||||
## Target
|
|
||||||
- Specs:
|
|
||||||
- `docs/pbs/specs/3. Core Syntax Specification.md` (secoes 5.1, 5.2, 5.3, 6.1)
|
|
||||||
- `docs/pbs/specs/12. Diagnostics Specification.md` (fases syntax/linking)
|
|
||||||
- Codigo:
|
|
||||||
- `prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/services/PBSFrontendPhaseService.java`
|
|
||||||
- novo parser/modelo para `mod.barrel` em `prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/`.
|
|
||||||
|
|
||||||
## Method
|
|
||||||
1. Adicionar parser dedicado para `mod.barrel` com itens e assinaturas de funcao.
|
|
||||||
2. Detectar `missing mod.barrel` por modulo e duplicatas no barrel.
|
|
||||||
3. Validar resolucao barrel -> declaracoes top-level da AST do modulo.
|
|
||||||
4. Garantir que `pub/mod` em `.pbs` continue proibido fora dos contextos permitidos.
|
|
||||||
5. Emitir diagnosticos com atribuicao primaria no arquivo/barrel que causou a falha.
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
- Compilacao de modulo sem `mod.barrel` falha deterministicamente.
|
|
||||||
- Duplicatas em barrel sao detectadas por regra correta (funcao por assinatura; outros por kind+nome).
|
|
||||||
- Cada item de barrel resolve para declaracao existente de modulo.
|
|
||||||
- Itens de barrel invalidos geram erro sem quebrar analise dos demais itens.
|
|
||||||
- Importacao cross-module usa somente simbolos `pub`.
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
- `PbsBarrelParserTest` novo para shape do barrel.
|
|
||||||
- `PbsModuleVisibilityTest` novo cobrindo:
|
|
||||||
- modulo sem barrel;
|
|
||||||
- duplicatas de simbolo/assinatura;
|
|
||||||
- entry nao resolvido;
|
|
||||||
- import de simbolo nao `pub`.
|
|
||||||
@ -22,6 +22,13 @@ public final class PbsFrontendCompiler {
|
|||||||
final DiagnosticSink diagnostics) {
|
final DiagnosticSink diagnostics) {
|
||||||
final var tokens = PbsLexer.lex(source, fileId, diagnostics);
|
final var tokens = PbsLexer.lex(source, fileId, diagnostics);
|
||||||
final var ast = PbsParser.parse(tokens, fileId, diagnostics);
|
final var ast = PbsParser.parse(tokens, fileId, diagnostics);
|
||||||
|
return compileParsedFile(fileId, ast, diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IRBackendFile compileParsedFile(
|
||||||
|
final FileId fileId,
|
||||||
|
final PbsAst.File ast,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
validateFunctionNames(ast, diagnostics);
|
validateFunctionNames(ast, diagnostics);
|
||||||
return new IRBackendFile(fileId, lowerFunctions(fileId, ast));
|
return new IRBackendFile(fileId, lowerFunctions(fileId, ast));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -60,6 +60,11 @@ public final class PbsAst {
|
|||||||
Span span) {
|
Span span) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record BarrelFile(
|
||||||
|
ReadOnlyList<BarrelItem> items,
|
||||||
|
Span span) {
|
||||||
|
}
|
||||||
|
|
||||||
public sealed interface TopDecl permits FunctionDecl,
|
public sealed interface TopDecl permits FunctionDecl,
|
||||||
StructDecl,
|
StructDecl,
|
||||||
ContractDecl,
|
ContractDecl,
|
||||||
@ -564,7 +569,9 @@ public final class PbsAst {
|
|||||||
Visibility visibility,
|
Visibility visibility,
|
||||||
String name,
|
String name,
|
||||||
ReadOnlyList<TypeRef> parameterTypes,
|
ReadOnlyList<TypeRef> parameterTypes,
|
||||||
|
ReturnKind returnKind,
|
||||||
TypeRef returnType,
|
TypeRef returnType,
|
||||||
|
TypeRef resultErrorType,
|
||||||
Span span) implements BarrelItem {
|
Span span) implements BarrelItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
package p.studio.compiler.pbs.linking;
|
||||||
|
|
||||||
|
public enum PbsLinkErrors {
|
||||||
|
E_LINK_MISSING_BARREL,
|
||||||
|
E_LINK_DUPLICATE_BARREL_FILE,
|
||||||
|
E_LINK_DUPLICATE_BARREL_ENTRY,
|
||||||
|
E_LINK_UNRESOLVED_BARREL_ENTRY,
|
||||||
|
E_LINK_AMBIGUOUS_BARREL_ENTRY,
|
||||||
|
E_LINK_IMPORT_SYMBOL_NOT_PUBLIC
|
||||||
|
}
|
||||||
@ -0,0 +1,462 @@
|
|||||||
|
package p.studio.compiler.pbs.linking;
|
||||||
|
|
||||||
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
|
import p.studio.compiler.source.Span;
|
||||||
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
|
import p.studio.compiler.source.diagnostics.RelatedSpan;
|
||||||
|
import p.studio.compiler.source.diagnostics.Severity;
|
||||||
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
import p.studio.compiler.source.identifiers.NameId;
|
||||||
|
import p.studio.compiler.source.tables.NameTable;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public final class PbsModuleVisibilityValidator {
|
||||||
|
|
||||||
|
public void validate(
|
||||||
|
final ReadOnlyList<ModuleUnit> modules,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var nameTable = new NameTable();
|
||||||
|
final var exportsByModule = new HashMap<ModuleRefKey, ModuleExports>();
|
||||||
|
|
||||||
|
for (final var module : modules) {
|
||||||
|
final var exports = validateModule(module, nameTable, diagnostics);
|
||||||
|
exportsByModule.put(toModuleRef(module.coordinates()), exports);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final var module : modules) {
|
||||||
|
validateImports(module, exportsByModule, nameTable, diagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModuleExports validateModule(
|
||||||
|
final ModuleUnit module,
|
||||||
|
final NameTable nameTable,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var exports = new ModuleExports();
|
||||||
|
|
||||||
|
if (module.sourceFiles().isEmpty()) {
|
||||||
|
return exports;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (module.barrelFiles().isEmpty()) {
|
||||||
|
diagnostics.error(
|
||||||
|
PbsLinkErrors.E_LINK_MISSING_BARREL.name(),
|
||||||
|
"Module %s is missing mod.barrel".formatted(displayModule(module.coordinates())),
|
||||||
|
module.sourceFiles().getFirst().ast().span());
|
||||||
|
return exports;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (module.barrelFiles().size() > 1) {
|
||||||
|
final var first = module.barrelFiles().getFirst();
|
||||||
|
for (int i = 1; i < module.barrelFiles().size(); i++) {
|
||||||
|
final var duplicate = module.barrelFiles().get(i);
|
||||||
|
diagnostics.report(
|
||||||
|
Severity.Error,
|
||||||
|
PbsLinkErrors.E_LINK_DUPLICATE_BARREL_FILE.name(),
|
||||||
|
"Module %s has multiple mod.barrel files".formatted(displayModule(module.coordinates())),
|
||||||
|
duplicate.ast().span(),
|
||||||
|
List.of(new RelatedSpan("First mod.barrel is here", first.ast().span())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final var barrel = module.barrelFiles().getFirst();
|
||||||
|
final var declarations = collectDeclarations(module, nameTable);
|
||||||
|
final Set<NonFunctionSymbolKey> seenNonFunctionEntries = new HashSet<>();
|
||||||
|
final Set<FunctionSymbolKey> seenFunctionEntries = new HashSet<>();
|
||||||
|
|
||||||
|
for (final var item : barrel.ast().items()) {
|
||||||
|
if (item instanceof PbsAst.BarrelFunctionItem functionItem) {
|
||||||
|
final var functionKey = functionKey(
|
||||||
|
functionItem.name(),
|
||||||
|
functionItem.parameterTypes().asList(),
|
||||||
|
functionItem.returnKind(),
|
||||||
|
functionItem.returnType(),
|
||||||
|
functionItem.resultErrorType(),
|
||||||
|
nameTable);
|
||||||
|
if (!seenFunctionEntries.add(functionKey)) {
|
||||||
|
diagnostics.error(
|
||||||
|
PbsLinkErrors.E_LINK_DUPLICATE_BARREL_ENTRY.name(),
|
||||||
|
"Duplicate barrel function entry '%s' in module %s".formatted(
|
||||||
|
functionItem.name(),
|
||||||
|
displayModule(module.coordinates())),
|
||||||
|
functionItem.span());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var matches = declarations.functionsBySignature.getOrDefault(functionKey, List.of());
|
||||||
|
if (matches.isEmpty()) {
|
||||||
|
diagnostics.error(
|
||||||
|
PbsLinkErrors.E_LINK_UNRESOLVED_BARREL_ENTRY.name(),
|
||||||
|
"Barrel function entry '%s' in module %s does not resolve to a declaration".formatted(
|
||||||
|
functionItem.name(),
|
||||||
|
displayModule(module.coordinates())),
|
||||||
|
functionItem.span());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (matches.size() > 1) {
|
||||||
|
diagnostics.error(
|
||||||
|
PbsLinkErrors.E_LINK_AMBIGUOUS_BARREL_ENTRY.name(),
|
||||||
|
"Barrel function entry '%s' in module %s resolves ambiguously".formatted(
|
||||||
|
functionItem.name(),
|
||||||
|
displayModule(module.coordinates())),
|
||||||
|
functionItem.span());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (functionItem.visibility() == PbsAst.Visibility.PUB) {
|
||||||
|
exports.publicNameIds.add(functionKey.nameId());
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var symbolKind = nonFunctionKind(item);
|
||||||
|
if (symbolKind == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var symbolKey = new NonFunctionSymbolKey(symbolKind, nameTable.register(nonFunctionName(item)));
|
||||||
|
if (!seenNonFunctionEntries.add(symbolKey)) {
|
||||||
|
diagnostics.error(
|
||||||
|
PbsLinkErrors.E_LINK_DUPLICATE_BARREL_ENTRY.name(),
|
||||||
|
"Duplicate barrel entry '%s' in module %s".formatted(
|
||||||
|
nonFunctionName(item),
|
||||||
|
displayModule(module.coordinates())),
|
||||||
|
item.span());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var matches = declarations.nonFunctionsByKindAndName.getOrDefault(symbolKey, List.of());
|
||||||
|
if (matches.isEmpty()) {
|
||||||
|
diagnostics.error(
|
||||||
|
PbsLinkErrors.E_LINK_UNRESOLVED_BARREL_ENTRY.name(),
|
||||||
|
"Barrel entry '%s' in module %s does not resolve to a declaration".formatted(
|
||||||
|
nonFunctionName(item),
|
||||||
|
displayModule(module.coordinates())),
|
||||||
|
item.span());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (matches.size() > 1) {
|
||||||
|
diagnostics.error(
|
||||||
|
PbsLinkErrors.E_LINK_AMBIGUOUS_BARREL_ENTRY.name(),
|
||||||
|
"Barrel entry '%s' in module %s resolves ambiguously".formatted(
|
||||||
|
nonFunctionName(item),
|
||||||
|
displayModule(module.coordinates())),
|
||||||
|
item.span());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nonFunctionVisibility(item) == PbsAst.Visibility.PUB) {
|
||||||
|
exports.publicNameIds.add(symbolKey.nameId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return exports;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateImports(
|
||||||
|
final ModuleUnit module,
|
||||||
|
final Map<ModuleRefKey, ModuleExports> exportsByModule,
|
||||||
|
final NameTable nameTable,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
for (final var source : module.sourceFiles()) {
|
||||||
|
for (final var importDecl : source.ast().imports()) {
|
||||||
|
if (importDecl.items().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var targetModule = new ModuleRefKey(
|
||||||
|
importDecl.moduleRef().project(),
|
||||||
|
importDecl.moduleRef().pathSegments());
|
||||||
|
final var targetExports = exportsByModule.get(targetModule);
|
||||||
|
if (targetExports == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final var importItem : importDecl.items()) {
|
||||||
|
final var importedNameId = nameTable.register(importItem.name());
|
||||||
|
if (!targetExports.publicNameIds.contains(importedNameId)) {
|
||||||
|
diagnostics.error(
|
||||||
|
PbsLinkErrors.E_LINK_IMPORT_SYMBOL_NOT_PUBLIC.name(),
|
||||||
|
"Symbol '%s' is not public in module %s".formatted(
|
||||||
|
importItem.name(),
|
||||||
|
displayModule(targetModule)),
|
||||||
|
importItem.span());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModuleDeclarations collectDeclarations(
|
||||||
|
final ModuleUnit module,
|
||||||
|
final NameTable nameTable) {
|
||||||
|
final var declarations = new ModuleDeclarations();
|
||||||
|
|
||||||
|
for (final var sourceFile : module.sourceFiles()) {
|
||||||
|
for (final var topDecl : sourceFile.ast().topDecls()) {
|
||||||
|
if (topDecl instanceof PbsAst.FunctionDecl functionDecl) {
|
||||||
|
final var key = functionKey(
|
||||||
|
functionDecl.name(),
|
||||||
|
functionDecl.parameters().stream().map(PbsAst.Parameter::typeRef).toList(),
|
||||||
|
functionDecl.returnKind(),
|
||||||
|
functionDecl.returnType(),
|
||||||
|
functionDecl.resultErrorType(),
|
||||||
|
nameTable);
|
||||||
|
declarations.functionsBySignature
|
||||||
|
.computeIfAbsent(key, ignored -> new ArrayList<>())
|
||||||
|
.add(functionDecl.span());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topDecl instanceof PbsAst.StructDecl structDecl) {
|
||||||
|
registerNonFunctionDeclaration(declarations, NonFunctionKind.STRUCT, structDecl.name(), structDecl.span(), nameTable);
|
||||||
|
} else if (topDecl instanceof PbsAst.ContractDecl contractDecl) {
|
||||||
|
registerNonFunctionDeclaration(declarations, NonFunctionKind.CONTRACT, contractDecl.name(), contractDecl.span(), nameTable);
|
||||||
|
} else if (topDecl instanceof PbsAst.ErrorDecl errorDecl) {
|
||||||
|
registerNonFunctionDeclaration(declarations, NonFunctionKind.ERROR, errorDecl.name(), errorDecl.span(), nameTable);
|
||||||
|
} else if (topDecl instanceof PbsAst.EnumDecl enumDecl) {
|
||||||
|
registerNonFunctionDeclaration(declarations, NonFunctionKind.ENUM, enumDecl.name(), enumDecl.span(), nameTable);
|
||||||
|
} else if (topDecl instanceof PbsAst.ServiceDecl serviceDecl) {
|
||||||
|
registerNonFunctionDeclaration(declarations, NonFunctionKind.SERVICE, serviceDecl.name(), serviceDecl.span(), nameTable);
|
||||||
|
} else if (topDecl instanceof PbsAst.ConstDecl constDecl) {
|
||||||
|
registerNonFunctionDeclaration(declarations, NonFunctionKind.CONST, constDecl.name(), constDecl.span(), nameTable);
|
||||||
|
} else if (topDecl instanceof PbsAst.CallbackDecl callbackDecl) {
|
||||||
|
registerNonFunctionDeclaration(declarations, NonFunctionKind.CALLBACK, callbackDecl.name(), callbackDecl.span(), nameTable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return declarations;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerNonFunctionDeclaration(
|
||||||
|
final ModuleDeclarations declarations,
|
||||||
|
final NonFunctionKind kind,
|
||||||
|
final String name,
|
||||||
|
final Span span,
|
||||||
|
final NameTable nameTable) {
|
||||||
|
final var key = new NonFunctionSymbolKey(kind, nameTable.register(name));
|
||||||
|
declarations.nonFunctionsByKindAndName
|
||||||
|
.computeIfAbsent(key, ignored -> new ArrayList<>())
|
||||||
|
.add(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FunctionSymbolKey functionKey(
|
||||||
|
final String name,
|
||||||
|
final List<PbsAst.TypeRef> parameterTypes,
|
||||||
|
final PbsAst.ReturnKind returnKind,
|
||||||
|
final PbsAst.TypeRef returnType,
|
||||||
|
final PbsAst.TypeRef resultErrorType,
|
||||||
|
final NameTable nameTable) {
|
||||||
|
final var signatureSurface = signatureSurfaceKey(parameterTypes, returnKind, returnType, resultErrorType);
|
||||||
|
return new FunctionSymbolKey(nameTable.register(name), signatureSurface);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String signatureSurfaceKey(
|
||||||
|
final List<PbsAst.TypeRef> parameterTypes,
|
||||||
|
final PbsAst.ReturnKind returnKind,
|
||||||
|
final PbsAst.TypeRef returnType,
|
||||||
|
final PbsAst.TypeRef resultErrorType) {
|
||||||
|
final var builder = new StringBuilder();
|
||||||
|
builder.append('(');
|
||||||
|
for (int i = 0; i < parameterTypes.size(); i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
builder.append(',');
|
||||||
|
}
|
||||||
|
builder.append(typeSurfaceKey(parameterTypes.get(i)));
|
||||||
|
}
|
||||||
|
builder.append(")->");
|
||||||
|
|
||||||
|
switch (returnKind) {
|
||||||
|
case INFERRED_UNIT -> builder.append("infer_unit");
|
||||||
|
case EXPLICIT_UNIT -> builder.append("unit");
|
||||||
|
case PLAIN -> builder.append(typeSurfaceKey(returnType));
|
||||||
|
case RESULT -> builder.append("result<")
|
||||||
|
.append(typeSurfaceKey(resultErrorType))
|
||||||
|
.append('>')
|
||||||
|
.append(typeSurfaceKey(returnType));
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String typeSurfaceKey(final PbsAst.TypeRef typeRef) {
|
||||||
|
if (typeRef == null) {
|
||||||
|
return "unit";
|
||||||
|
}
|
||||||
|
return switch (typeRef.kind()) {
|
||||||
|
case SIMPLE -> "simple:" + typeRef.name();
|
||||||
|
case SELF -> "self";
|
||||||
|
case OPTIONAL -> "optional(" + typeSurfaceKey(typeRef.inner()) + ")";
|
||||||
|
case UNIT -> "unit";
|
||||||
|
case GROUP -> "group(" + typeSurfaceKey(typeRef.inner()) + ")";
|
||||||
|
case NAMED_TUPLE -> "tuple(" + typeRef.fields().stream()
|
||||||
|
.map(field -> typeSurfaceKey(field.typeRef()))
|
||||||
|
.reduce((left, right) -> left + "," + right)
|
||||||
|
.orElse("") + ")";
|
||||||
|
case ERROR -> "error";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private NonFunctionKind nonFunctionKind(final PbsAst.BarrelItem item) {
|
||||||
|
if (item instanceof PbsAst.BarrelStructItem) {
|
||||||
|
return NonFunctionKind.STRUCT;
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelContractItem) {
|
||||||
|
return NonFunctionKind.CONTRACT;
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelHostItem) {
|
||||||
|
return NonFunctionKind.HOST;
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelErrorItem) {
|
||||||
|
return NonFunctionKind.ERROR;
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelEnumItem) {
|
||||||
|
return NonFunctionKind.ENUM;
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelServiceItem) {
|
||||||
|
return NonFunctionKind.SERVICE;
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelConstItem) {
|
||||||
|
return NonFunctionKind.CONST;
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelCallbackItem) {
|
||||||
|
return NonFunctionKind.CALLBACK;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String nonFunctionName(final PbsAst.BarrelItem item) {
|
||||||
|
if (item instanceof PbsAst.BarrelStructItem structItem) {
|
||||||
|
return structItem.name();
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelContractItem contractItem) {
|
||||||
|
return contractItem.name();
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelHostItem hostItem) {
|
||||||
|
return hostItem.name();
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelErrorItem errorItem) {
|
||||||
|
return errorItem.name();
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelEnumItem enumItem) {
|
||||||
|
return enumItem.name();
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelServiceItem serviceItem) {
|
||||||
|
return serviceItem.name();
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelConstItem constItem) {
|
||||||
|
return constItem.name();
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelCallbackItem callbackItem) {
|
||||||
|
return callbackItem.name();
|
||||||
|
}
|
||||||
|
return "<unknown>";
|
||||||
|
}
|
||||||
|
|
||||||
|
private PbsAst.Visibility nonFunctionVisibility(final PbsAst.BarrelItem item) {
|
||||||
|
if (item instanceof PbsAst.BarrelStructItem structItem) {
|
||||||
|
return structItem.visibility();
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelContractItem contractItem) {
|
||||||
|
return contractItem.visibility();
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelHostItem hostItem) {
|
||||||
|
return hostItem.visibility();
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelErrorItem errorItem) {
|
||||||
|
return errorItem.visibility();
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelEnumItem enumItem) {
|
||||||
|
return enumItem.visibility();
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelServiceItem serviceItem) {
|
||||||
|
return serviceItem.visibility();
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelConstItem constItem) {
|
||||||
|
return constItem.visibility();
|
||||||
|
}
|
||||||
|
if (item instanceof PbsAst.BarrelCallbackItem callbackItem) {
|
||||||
|
return callbackItem.visibility();
|
||||||
|
}
|
||||||
|
return PbsAst.Visibility.MOD;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModuleRefKey toModuleRef(final ModuleCoordinates coordinates) {
|
||||||
|
return new ModuleRefKey(coordinates.project(), coordinates.pathSegments());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String displayModule(final ModuleCoordinates coordinates) {
|
||||||
|
return displayModule(new ModuleRefKey(coordinates.project(), coordinates.pathSegments()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String displayModule(final ModuleRefKey moduleRef) {
|
||||||
|
if (moduleRef.pathSegments().isEmpty()) {
|
||||||
|
return "@%s:<root>".formatted(moduleRef.project());
|
||||||
|
}
|
||||||
|
return "@%s:%s".formatted(moduleRef.project(), String.join("/", moduleRef.pathSegments().asList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ModuleCoordinates(
|
||||||
|
String project,
|
||||||
|
ReadOnlyList<String> pathSegments) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record SourceFile(
|
||||||
|
FileId fileId,
|
||||||
|
PbsAst.File ast) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record BarrelFile(
|
||||||
|
FileId fileId,
|
||||||
|
PbsAst.BarrelFile ast) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ModuleUnit(
|
||||||
|
ModuleCoordinates coordinates,
|
||||||
|
ReadOnlyList<SourceFile> sourceFiles,
|
||||||
|
ReadOnlyList<BarrelFile> barrelFiles) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ModuleRefKey(
|
||||||
|
String project,
|
||||||
|
ReadOnlyList<String> pathSegments) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum NonFunctionKind {
|
||||||
|
STRUCT,
|
||||||
|
CONTRACT,
|
||||||
|
HOST,
|
||||||
|
ERROR,
|
||||||
|
ENUM,
|
||||||
|
SERVICE,
|
||||||
|
CONST,
|
||||||
|
CALLBACK
|
||||||
|
}
|
||||||
|
|
||||||
|
private record NonFunctionSymbolKey(
|
||||||
|
NonFunctionKind kind,
|
||||||
|
NameId nameId) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private record FunctionSymbolKey(
|
||||||
|
NameId nameId,
|
||||||
|
String signatureSurface) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ModuleDeclarations {
|
||||||
|
private final Map<NonFunctionSymbolKey, List<Span>> nonFunctionsByKindAndName = new HashMap<>();
|
||||||
|
private final Map<FunctionSymbolKey, List<Span>> functionsBySignature = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ModuleExports {
|
||||||
|
private final Set<NameId> publicNameIds = new HashSet<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,308 @@
|
|||||||
|
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.DiagnosticSink;
|
||||||
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parser for `mod.barrel` files.
|
||||||
|
*/
|
||||||
|
public final class PbsBarrelParser {
|
||||||
|
private final PbsTokenCursor cursor;
|
||||||
|
private final FileId fileId;
|
||||||
|
private final DiagnosticSink diagnostics;
|
||||||
|
|
||||||
|
private PbsBarrelParser(
|
||||||
|
final ReadOnlyList<PbsToken> tokens,
|
||||||
|
final FileId fileId,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
this.cursor = new PbsTokenCursor(tokens);
|
||||||
|
this.fileId = fileId;
|
||||||
|
this.diagnostics = diagnostics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PbsAst.BarrelFile parse(
|
||||||
|
final ReadOnlyList<PbsToken> tokens,
|
||||||
|
final FileId fileId,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
return new PbsBarrelParser(tokens, fileId, diagnostics).parseFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
private PbsAst.BarrelFile parseFile() {
|
||||||
|
final var items = new ArrayList<PbsAst.BarrelItem>();
|
||||||
|
|
||||||
|
while (!cursor.isAtEnd()) {
|
||||||
|
if (cursor.check(PbsTokenKind.EOF)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var visibility = parseVisibility();
|
||||||
|
if (visibility == null) {
|
||||||
|
synchronizeToSemicolon();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var item = parseItem(visibility, cursor.previous());
|
||||||
|
if (item != null) {
|
||||||
|
items.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final var eof = cursor.peek();
|
||||||
|
return new PbsAst.BarrelFile(ReadOnlyList.wrap(items), span(0, eof.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private PbsAst.Visibility parseVisibility() {
|
||||||
|
if (cursor.match(PbsTokenKind.MOD)) {
|
||||||
|
return PbsAst.Visibility.MOD;
|
||||||
|
}
|
||||||
|
if (cursor.match(PbsTokenKind.PUB)) {
|
||||||
|
return PbsAst.Visibility.PUB;
|
||||||
|
}
|
||||||
|
|
||||||
|
report(cursor.peek(), ParseErrors.E_PARSE_UNEXPECTED_TOKEN,
|
||||||
|
"Expected barrel visibility ('mod' or 'pub')");
|
||||||
|
if (!cursor.isAtEnd()) {
|
||||||
|
cursor.advance();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PbsAst.BarrelItem parseItem(final PbsAst.Visibility visibility, final PbsToken visibilityToken) {
|
||||||
|
if (cursor.match(PbsTokenKind.FN)) {
|
||||||
|
return parseFunctionItem(visibility, visibilityToken);
|
||||||
|
}
|
||||||
|
if (cursor.match(PbsTokenKind.STRUCT)) {
|
||||||
|
return parseSimpleItem(visibility, visibilityToken, PbsTokenKind.STRUCT);
|
||||||
|
}
|
||||||
|
if (cursor.match(PbsTokenKind.CONTRACT)) {
|
||||||
|
return parseSimpleItem(visibility, visibilityToken, PbsTokenKind.CONTRACT);
|
||||||
|
}
|
||||||
|
if (cursor.match(PbsTokenKind.HOST)) {
|
||||||
|
return parseSimpleItem(visibility, visibilityToken, PbsTokenKind.HOST);
|
||||||
|
}
|
||||||
|
if (cursor.match(PbsTokenKind.ERROR)) {
|
||||||
|
return parseSimpleItem(visibility, visibilityToken, PbsTokenKind.ERROR);
|
||||||
|
}
|
||||||
|
if (cursor.match(PbsTokenKind.ENUM)) {
|
||||||
|
return parseSimpleItem(visibility, visibilityToken, PbsTokenKind.ENUM);
|
||||||
|
}
|
||||||
|
if (cursor.match(PbsTokenKind.SERVICE)) {
|
||||||
|
return parseSimpleItem(visibility, visibilityToken, PbsTokenKind.SERVICE);
|
||||||
|
}
|
||||||
|
if (cursor.match(PbsTokenKind.CONST)) {
|
||||||
|
return parseSimpleItem(visibility, visibilityToken, PbsTokenKind.CONST);
|
||||||
|
}
|
||||||
|
if (cursor.match(PbsTokenKind.CALLBACK)) {
|
||||||
|
return parseSimpleItem(visibility, visibilityToken, PbsTokenKind.CALLBACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
report(cursor.peek(), ParseErrors.E_PARSE_UNEXPECTED_TOKEN,
|
||||||
|
"Expected barrel declaration kind after visibility");
|
||||||
|
synchronizeToSemicolon();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PbsAst.BarrelItem parseSimpleItem(
|
||||||
|
final PbsAst.Visibility visibility,
|
||||||
|
final PbsToken visibilityToken,
|
||||||
|
final PbsTokenKind kind) {
|
||||||
|
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected declaration name in barrel item");
|
||||||
|
final var semicolon = consume(PbsTokenKind.SEMICOLON, "Expected ';' after barrel item");
|
||||||
|
final var itemSpan = span(visibilityToken.start(), semicolon.end());
|
||||||
|
|
||||||
|
return switch (kind) {
|
||||||
|
case STRUCT -> new PbsAst.BarrelStructItem(visibility, name.lexeme(), itemSpan);
|
||||||
|
case CONTRACT -> new PbsAst.BarrelContractItem(visibility, name.lexeme(), itemSpan);
|
||||||
|
case HOST -> new PbsAst.BarrelHostItem(visibility, name.lexeme(), itemSpan);
|
||||||
|
case ERROR -> new PbsAst.BarrelErrorItem(visibility, name.lexeme(), itemSpan);
|
||||||
|
case ENUM -> new PbsAst.BarrelEnumItem(visibility, name.lexeme(), itemSpan);
|
||||||
|
case SERVICE -> new PbsAst.BarrelServiceItem(visibility, name.lexeme(), itemSpan);
|
||||||
|
case CONST -> new PbsAst.BarrelConstItem(visibility, name.lexeme(), itemSpan);
|
||||||
|
case CALLBACK -> new PbsAst.BarrelCallbackItem(visibility, name.lexeme(), itemSpan);
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private PbsAst.BarrelFunctionItem parseFunctionItem(
|
||||||
|
final PbsAst.Visibility visibility,
|
||||||
|
final PbsToken visibilityToken) {
|
||||||
|
final var name = consume(PbsTokenKind.IDENTIFIER, "Expected function name in barrel item");
|
||||||
|
consume(PbsTokenKind.LEFT_PAREN, "Expected '(' after function name");
|
||||||
|
|
||||||
|
final var parameterTypes = new ArrayList<PbsAst.TypeRef>();
|
||||||
|
if (!cursor.check(PbsTokenKind.RIGHT_PAREN)) {
|
||||||
|
do {
|
||||||
|
consume(PbsTokenKind.IDENTIFIER, "Expected parameter name in barrel function signature");
|
||||||
|
consume(PbsTokenKind.COLON, "Expected ':' after parameter name in barrel function signature");
|
||||||
|
parameterTypes.add(parseTypeRef());
|
||||||
|
} while (cursor.match(PbsTokenKind.COMMA));
|
||||||
|
}
|
||||||
|
final var rightParen = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after barrel function parameters");
|
||||||
|
final var returnSpec = parseCallableReturnSpec(rightParen);
|
||||||
|
|
||||||
|
final var semicolon = consume(PbsTokenKind.SEMICOLON, "Expected ';' after barrel function item");
|
||||||
|
return new PbsAst.BarrelFunctionItem(
|
||||||
|
visibility,
|
||||||
|
name.lexeme(),
|
||||||
|
ReadOnlyList.wrap(parameterTypes),
|
||||||
|
returnSpec.kind(),
|
||||||
|
returnSpec.returnType(),
|
||||||
|
returnSpec.resultErrorType(),
|
||||||
|
span(visibilityToken.start(), semicolon.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParsedReturnSpec parseCallableReturnSpec(final PbsToken anchorToken) {
|
||||||
|
if (cursor.match(PbsTokenKind.ARROW)) {
|
||||||
|
return parseReturnSpecFromMarker();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor.match(PbsTokenKind.COLON)) {
|
||||||
|
report(cursor.previous(), ParseErrors.E_PARSE_INVALID_RETURN_ANNOTATION,
|
||||||
|
"Barrel return annotations must use '->' syntax");
|
||||||
|
return parseReturnSpecFromMarker();
|
||||||
|
}
|
||||||
|
|
||||||
|
final var inferredSpan = span(anchorToken.end(), anchorToken.end());
|
||||||
|
return new ParsedReturnSpec(
|
||||||
|
PbsAst.ReturnKind.INFERRED_UNIT,
|
||||||
|
PbsAst.TypeRef.unit(inferredSpan),
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParsedReturnSpec parseReturnSpecFromMarker() {
|
||||||
|
if (cursor.match(PbsTokenKind.RESULT)) {
|
||||||
|
final var resultToken = cursor.previous();
|
||||||
|
consume(PbsTokenKind.LESS, "Expected '<' after 'result'");
|
||||||
|
final var errorType = parseTypeRef();
|
||||||
|
consume(PbsTokenKind.GREATER, "Expected '>' after result error type");
|
||||||
|
|
||||||
|
final PbsAst.TypeRef payloadType;
|
||||||
|
if (isTypeStart(cursor.peek().kind())) {
|
||||||
|
payloadType = parseTypeRef();
|
||||||
|
} else {
|
||||||
|
payloadType = PbsAst.TypeRef.unit(span(resultToken.end(), resultToken.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ParsedReturnSpec(
|
||||||
|
PbsAst.ReturnKind.RESULT,
|
||||||
|
payloadType,
|
||||||
|
errorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
final var typeRef = parseTypeRef();
|
||||||
|
if (typeRef.kind() == PbsAst.TypeRefKind.UNIT) {
|
||||||
|
return new ParsedReturnSpec(PbsAst.ReturnKind.EXPLICIT_UNIT, typeRef, null);
|
||||||
|
}
|
||||||
|
return new ParsedReturnSpec(PbsAst.ReturnKind.PLAIN, typeRef, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PbsAst.TypeRef parseTypeRef() {
|
||||||
|
if (cursor.match(PbsTokenKind.OPTIONAL)) {
|
||||||
|
final var optionalToken = cursor.previous();
|
||||||
|
final var inner = parseTypeRef();
|
||||||
|
if (inner.kind() == PbsAst.TypeRefKind.UNIT) {
|
||||||
|
report(optionalToken, ParseErrors.E_PARSE_INVALID_TYPE_SURFACE,
|
||||||
|
"'optional void' is not a valid type surface");
|
||||||
|
}
|
||||||
|
return PbsAst.TypeRef.optional(inner, span(optionalToken.start(), inner.span().getEnd()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor.match(PbsTokenKind.VOID)) {
|
||||||
|
final var token = cursor.previous();
|
||||||
|
return PbsAst.TypeRef.unit(span(token.start(), token.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor.match(PbsTokenKind.SELF)) {
|
||||||
|
final var token = cursor.previous();
|
||||||
|
return PbsAst.TypeRef.self(span(token.start(), token.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor.match(PbsTokenKind.IDENTIFIER)) {
|
||||||
|
final var token = cursor.previous();
|
||||||
|
return PbsAst.TypeRef.simple(token.lexeme(), span(token.start(), token.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor.match(PbsTokenKind.LEFT_PAREN)) {
|
||||||
|
final var open = cursor.previous();
|
||||||
|
if (cursor.match(PbsTokenKind.RIGHT_PAREN)) {
|
||||||
|
return PbsAst.TypeRef.unit(span(open.start(), cursor.previous().end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor.check(PbsTokenKind.IDENTIFIER) && cursor.checkNext(PbsTokenKind.COLON)) {
|
||||||
|
final var fields = new ArrayList<PbsAst.NamedTypeField>();
|
||||||
|
do {
|
||||||
|
final var label = consume(PbsTokenKind.IDENTIFIER, "Expected tuple field label");
|
||||||
|
consume(PbsTokenKind.COLON, "Expected ':' after tuple field label");
|
||||||
|
final var type = parseTypeRef();
|
||||||
|
fields.add(new PbsAst.NamedTypeField(label.lexeme(), type, span(label.start(), type.span().getEnd())));
|
||||||
|
} while (cursor.match(PbsTokenKind.COMMA) && !cursor.check(PbsTokenKind.RIGHT_PAREN));
|
||||||
|
|
||||||
|
final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after named tuple type");
|
||||||
|
return PbsAst.TypeRef.namedTuple(ReadOnlyList.wrap(fields), span(open.start(), close.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
final var inner = parseTypeRef();
|
||||||
|
final var close = consume(PbsTokenKind.RIGHT_PAREN, "Expected ')' after grouped type");
|
||||||
|
return PbsAst.TypeRef.group(inner, span(open.start(), close.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
final var token = cursor.peek();
|
||||||
|
report(token, ParseErrors.E_PARSE_EXPECTED_TOKEN, "Expected type surface");
|
||||||
|
if (!cursor.isAtEnd()) {
|
||||||
|
cursor.advance();
|
||||||
|
}
|
||||||
|
return PbsAst.TypeRef.error(span(token.start(), token.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTypeStart(final PbsTokenKind kind) {
|
||||||
|
return switch (kind) {
|
||||||
|
case OPTIONAL, VOID, SELF, IDENTIFIER, LEFT_PAREN -> true;
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void synchronizeToSemicolon() {
|
||||||
|
while (!cursor.isAtEnd()) {
|
||||||
|
if (cursor.match(PbsTokenKind.SEMICOLON)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cursor.check(PbsTokenKind.MOD) || cursor.check(PbsTokenKind.PUB)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cursor.advance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PbsToken consume(final PbsTokenKind kind, final String message) {
|
||||||
|
if (cursor.check(kind)) {
|
||||||
|
return cursor.advance();
|
||||||
|
}
|
||||||
|
final var token = cursor.peek();
|
||||||
|
report(token, ParseErrors.E_PARSE_EXPECTED_TOKEN, message + ", found " + token.kind());
|
||||||
|
if (!cursor.isAtEnd()) {
|
||||||
|
return cursor.advance();
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Span span(final long start, final long end) {
|
||||||
|
return new Span(fileId, start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void report(final PbsToken token, final ParseErrors parseErrors, final String message) {
|
||||||
|
diagnostics.error(parseErrors.name(), message, new Span(fileId, token.start(), token.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ParsedReturnSpec(
|
||||||
|
PbsAst.ReturnKind kind,
|
||||||
|
PbsAst.TypeRef returnType,
|
||||||
|
PbsAst.TypeRef resultErrorType) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,13 +4,28 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import p.studio.compiler.messages.BuildingIssueSink;
|
import p.studio.compiler.messages.BuildingIssueSink;
|
||||||
import p.studio.compiler.messages.FrontendPhaseContext;
|
import p.studio.compiler.messages.FrontendPhaseContext;
|
||||||
import p.studio.compiler.models.IRBackend;
|
import p.studio.compiler.models.IRBackend;
|
||||||
|
import p.studio.compiler.models.ProjectDescriptor;
|
||||||
|
import p.studio.compiler.models.SourceHandle;
|
||||||
import p.studio.compiler.pbs.PbsFrontendCompiler;
|
import p.studio.compiler.pbs.PbsFrontendCompiler;
|
||||||
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
|
import p.studio.compiler.pbs.lexer.PbsLexer;
|
||||||
|
import p.studio.compiler.pbs.linking.PbsModuleVisibilityValidator;
|
||||||
|
import p.studio.compiler.pbs.parser.PbsBarrelParser;
|
||||||
|
import p.studio.compiler.pbs.parser.PbsParser;
|
||||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
import p.studio.utilities.logs.LogAggregator;
|
import p.studio.utilities.logs.LogAggregator;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class PBSFrontendPhaseService implements FrontendPhaseService {
|
public class PBSFrontendPhaseService implements FrontendPhaseService {
|
||||||
private final PbsFrontendCompiler frontendCompiler = new PbsFrontendCompiler();
|
private final PbsFrontendCompiler frontendCompiler = new PbsFrontendCompiler();
|
||||||
|
private final PbsModuleVisibilityValidator moduleVisibilityValidator = new PbsModuleVisibilityValidator();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IRBackend compile(
|
public IRBackend compile(
|
||||||
@ -19,33 +34,116 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
|||||||
final LogAggregator logs,
|
final LogAggregator logs,
|
||||||
final BuildingIssueSink issues) {
|
final BuildingIssueSink issues) {
|
||||||
final var irBackendAggregator = IRBackend.aggregator();
|
final var irBackendAggregator = IRBackend.aggregator();
|
||||||
|
final var parsedSourceFiles = new ArrayList<ParsedSourceFile>();
|
||||||
|
final Map<PbsModuleVisibilityValidator.ModuleCoordinates, MutableModuleUnit> modulesByCoordinates = new LinkedHashMap<>();
|
||||||
|
|
||||||
for (final var pId : ctx.stack.reverseTopologicalOrder) {
|
for (final var pId : ctx.stack.reverseTopologicalOrder) {
|
||||||
|
final var projectDescriptor = ctx.projectTable.get(pId);
|
||||||
final var fileIds = ctx.fileTable.getFiles(pId);
|
final var fileIds = ctx.fileTable.getFiles(pId);
|
||||||
|
|
||||||
for (final var fId : fileIds) {
|
for (final var fId : fileIds) {
|
||||||
final var sourceHandle = ctx.fileTable.get(fId);
|
final var sourceHandle = ctx.fileTable.get(fId);
|
||||||
switch (sourceHandle.getExtension()) {
|
|
||||||
case "pbs": {
|
|
||||||
sourceHandle.readUtf8().ifPresentOrElse(
|
sourceHandle.readUtf8().ifPresentOrElse(
|
||||||
utf8Content -> {
|
utf8Content -> {
|
||||||
final var irBackendFile = frontendCompiler
|
final var coordinates = resolveModuleCoordinates(projectDescriptor, sourceHandle);
|
||||||
.compileFile(fId, utf8Content, diagnostics);
|
final var moduleUnit = modulesByCoordinates.computeIfAbsent(
|
||||||
irBackendAggregator.merge(irBackendFile);
|
coordinates,
|
||||||
|
ignored -> new MutableModuleUnit());
|
||||||
|
switch (sourceHandle.getExtension()) {
|
||||||
|
case "pbs" -> {
|
||||||
|
final var ast = parseSourceFile(fId, utf8Content, diagnostics);
|
||||||
|
moduleUnit.sources.add(new PbsModuleVisibilityValidator.SourceFile(fId, ast));
|
||||||
|
parsedSourceFiles.add(new ParsedSourceFile(fId, ast));
|
||||||
|
}
|
||||||
|
case "barrel" -> {
|
||||||
|
if ("mod.barrel".equals(sourceHandle.getFilename())) {
|
||||||
|
final var barrelAst = parseBarrelFile(fId, utf8Content, diagnostics);
|
||||||
|
moduleUnit.barrels.add(new PbsModuleVisibilityValidator.BarrelFile(fId, barrelAst));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
() -> issues.report(builder -> builder
|
() -> issues.report(builder -> builder
|
||||||
.error(true)
|
.error(true)
|
||||||
.message("Failed to read file content: %s".formatted(sourceHandle.toString()))));
|
.message("Failed to read file content: %s".formatted(sourceHandle.toString()))));
|
||||||
} break;
|
|
||||||
case "barrel":
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final var modules = new ArrayList<PbsModuleVisibilityValidator.ModuleUnit>(modulesByCoordinates.size());
|
||||||
|
for (final var entry : modulesByCoordinates.entrySet()) {
|
||||||
|
final var coordinates = entry.getKey();
|
||||||
|
final var moduleUnit = entry.getValue();
|
||||||
|
modules.add(new PbsModuleVisibilityValidator.ModuleUnit(
|
||||||
|
coordinates,
|
||||||
|
ReadOnlyList.wrap(moduleUnit.sources),
|
||||||
|
ReadOnlyList.wrap(moduleUnit.barrels)));
|
||||||
|
}
|
||||||
|
moduleVisibilityValidator.validate(ReadOnlyList.wrap(modules), diagnostics);
|
||||||
|
|
||||||
|
for (final var parsedSource : parsedSourceFiles) {
|
||||||
|
final var irBackendFile = frontendCompiler.compileParsedFile(parsedSource.fileId(), parsedSource.ast(), diagnostics);
|
||||||
|
irBackendAggregator.merge(irBackendFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
final var irBackend = irBackendAggregator.emit();
|
final var irBackend = irBackendAggregator.emit();
|
||||||
logs.using(log).debug("PBS frontend lowered to IR BE:\n%s".formatted(irBackend));
|
logs.using(log).debug("PBS frontend lowered to IR BE:\n%s".formatted(irBackend));
|
||||||
return irBackend;
|
return irBackend;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PbsAst.File parseSourceFile(
|
||||||
|
final FileId fileId,
|
||||||
|
final String source,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var tokens = PbsLexer.lex(source, fileId, diagnostics);
|
||||||
|
return PbsParser.parse(tokens, fileId, diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PbsAst.BarrelFile parseBarrelFile(
|
||||||
|
final FileId fileId,
|
||||||
|
final String source,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var tokens = PbsLexer.lex(source, fileId, diagnostics);
|
||||||
|
return PbsBarrelParser.parse(tokens, fileId, diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PbsModuleVisibilityValidator.ModuleCoordinates resolveModuleCoordinates(
|
||||||
|
final ProjectDescriptor projectDescriptor,
|
||||||
|
final SourceHandle sourceHandle) {
|
||||||
|
final var sourceRelativePath = sourceRelativePath(projectDescriptor, sourceHandle);
|
||||||
|
final var modulePath = sourceRelativePath.getParent();
|
||||||
|
final var pathSegments = new ArrayList<String>();
|
||||||
|
if (modulePath != null) {
|
||||||
|
for (final var segment : modulePath) {
|
||||||
|
pathSegments.add(segment.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PbsModuleVisibilityValidator.ModuleCoordinates(
|
||||||
|
projectDescriptor.getName(),
|
||||||
|
ReadOnlyList.wrap(pathSegments));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path sourceRelativePath(
|
||||||
|
final ProjectDescriptor projectDescriptor,
|
||||||
|
final SourceHandle sourceHandle) {
|
||||||
|
final var canonPath = sourceHandle.getCanonPath();
|
||||||
|
for (final var sourceRoot : projectDescriptor.getSourceRoots()) {
|
||||||
|
if (canonPath.startsWith(sourceRoot)) {
|
||||||
|
return sourceRoot.relativize(canonPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sourceHandle.getRelativePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class MutableModuleUnit {
|
||||||
|
private final ArrayList<PbsModuleVisibilityValidator.SourceFile> sources = new ArrayList<>();
|
||||||
|
private final ArrayList<PbsModuleVisibilityValidator.BarrelFile> barrels = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ParsedSourceFile(
|
||||||
|
FileId fileId,
|
||||||
|
PbsAst.File ast) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,162 @@
|
|||||||
|
package p.studio.compiler.pbs.linking;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
|
import p.studio.compiler.pbs.lexer.PbsLexer;
|
||||||
|
import p.studio.compiler.pbs.parser.PbsBarrelParser;
|
||||||
|
import p.studio.compiler.pbs.parser.PbsParser;
|
||||||
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class PbsModuleVisibilityTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReportMissingBarrelPerModule() {
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
final var nextFileId = new AtomicInteger(0);
|
||||||
|
final var module = module("core", "math", List.of(
|
||||||
|
"""
|
||||||
|
fn sum(a: int, b: int) -> int { return a + b; }
|
||||||
|
"""
|
||||||
|
), null, nextFileId, diagnostics);
|
||||||
|
|
||||||
|
new PbsModuleVisibilityValidator().validate(ReadOnlyList.wrap(List.of(module)), diagnostics);
|
||||||
|
|
||||||
|
assertTrue(diagnostics.stream().anyMatch(d -> d.getCode().equals(PbsLinkErrors.E_LINK_MISSING_BARREL.name())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReportDuplicateBarrelEntriesByKindAndSignature() {
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
final var nextFileId = new AtomicInteger(0);
|
||||||
|
final var module = module("core", "math", List.of(
|
||||||
|
"""
|
||||||
|
fn foo(x: int) -> int { return x; }
|
||||||
|
declare struct State(v: int);
|
||||||
|
"""
|
||||||
|
), """
|
||||||
|
pub fn foo(a: int) -> int;
|
||||||
|
pub fn foo(b: int) -> int;
|
||||||
|
pub struct State;
|
||||||
|
pub struct State;
|
||||||
|
""", nextFileId, diagnostics);
|
||||||
|
|
||||||
|
new PbsModuleVisibilityValidator().validate(ReadOnlyList.wrap(List.of(module)), diagnostics);
|
||||||
|
|
||||||
|
final long duplicateCount = diagnostics.stream()
|
||||||
|
.filter(d -> d.getCode().equals(PbsLinkErrors.E_LINK_DUPLICATE_BARREL_ENTRY.name()))
|
||||||
|
.count();
|
||||||
|
assertEquals(2, duplicateCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReportUnresolvedBarrelEntries() {
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
final var nextFileId = new AtomicInteger(0);
|
||||||
|
final var module = module("core", "math", List.of(
|
||||||
|
"""
|
||||||
|
fn foo() -> int { return 1; }
|
||||||
|
"""
|
||||||
|
), """
|
||||||
|
pub fn missing() -> int;
|
||||||
|
pub enum MissingEnum;
|
||||||
|
""", nextFileId, diagnostics);
|
||||||
|
|
||||||
|
new PbsModuleVisibilityValidator().validate(ReadOnlyList.wrap(List.of(module)), diagnostics);
|
||||||
|
|
||||||
|
final long unresolvedCount = diagnostics.stream()
|
||||||
|
.filter(d -> d.getCode().equals(PbsLinkErrors.E_LINK_UNRESOLVED_BARREL_ENTRY.name()))
|
||||||
|
.count();
|
||||||
|
assertEquals(2, unresolvedCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectImportOfNonPublicSymbols() {
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
final var nextFileId = new AtomicInteger(0);
|
||||||
|
|
||||||
|
final var moduleMath = module("core", "math", List.of(
|
||||||
|
"""
|
||||||
|
fn secret() -> int { return 1; }
|
||||||
|
fn open() -> int { return 2; }
|
||||||
|
"""
|
||||||
|
), """
|
||||||
|
mod fn secret() -> int;
|
||||||
|
pub fn open() -> int;
|
||||||
|
""", nextFileId, diagnostics);
|
||||||
|
|
||||||
|
final var moduleApp = module("core", "app", List.of(
|
||||||
|
"""
|
||||||
|
import { secret, open } from @core:math;
|
||||||
|
fn use() -> int { return open(); }
|
||||||
|
"""
|
||||||
|
), """
|
||||||
|
pub fn use() -> int;
|
||||||
|
""", nextFileId, diagnostics);
|
||||||
|
|
||||||
|
new PbsModuleVisibilityValidator().validate(ReadOnlyList.wrap(List.of(moduleMath, moduleApp)), diagnostics);
|
||||||
|
|
||||||
|
final var importVisibilityDiagnostics = diagnostics.stream()
|
||||||
|
.filter(d -> d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_NOT_PUBLIC.name()))
|
||||||
|
.toList();
|
||||||
|
assertEquals(1, importVisibilityDiagnostics.size());
|
||||||
|
assertTrue(importVisibilityDiagnostics.getFirst().getMessage().contains("secret"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private PbsModuleVisibilityValidator.ModuleUnit module(
|
||||||
|
final String project,
|
||||||
|
final String modulePath,
|
||||||
|
final List<String> sourceContents,
|
||||||
|
final String barrelContent,
|
||||||
|
final AtomicInteger nextFileId,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
final var sources = new ArrayList<PbsModuleVisibilityValidator.SourceFile>();
|
||||||
|
for (final var sourceContent : sourceContents) {
|
||||||
|
final var fileId = new FileId(nextFileId.getAndIncrement());
|
||||||
|
final var ast = parseSource(sourceContent, fileId, diagnostics);
|
||||||
|
sources.add(new PbsModuleVisibilityValidator.SourceFile(fileId, ast));
|
||||||
|
}
|
||||||
|
|
||||||
|
final var barrels = new ArrayList<PbsModuleVisibilityValidator.BarrelFile>();
|
||||||
|
if (barrelContent != null) {
|
||||||
|
final var fileId = new FileId(nextFileId.getAndIncrement());
|
||||||
|
final var barrelAst = parseBarrel(barrelContent, fileId, diagnostics);
|
||||||
|
barrels.add(new PbsModuleVisibilityValidator.BarrelFile(fileId, barrelAst));
|
||||||
|
}
|
||||||
|
|
||||||
|
final var coordinates = new PbsModuleVisibilityValidator.ModuleCoordinates(project, modulePathSegments(modulePath));
|
||||||
|
return new PbsModuleVisibilityValidator.ModuleUnit(
|
||||||
|
coordinates,
|
||||||
|
ReadOnlyList.wrap(sources),
|
||||||
|
ReadOnlyList.wrap(barrels));
|
||||||
|
}
|
||||||
|
|
||||||
|
private PbsAst.File parseSource(
|
||||||
|
final String source,
|
||||||
|
final FileId fileId,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
return PbsParser.parse(PbsLexer.lex(source, fileId, diagnostics), fileId, diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PbsAst.BarrelFile parseBarrel(
|
||||||
|
final String source,
|
||||||
|
final FileId fileId,
|
||||||
|
final DiagnosticSink diagnostics) {
|
||||||
|
return PbsBarrelParser.parse(PbsLexer.lex(source, fileId, diagnostics), fileId, diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReadOnlyList<String> modulePathSegments(final String modulePath) {
|
||||||
|
if (modulePath == null || modulePath.isBlank()) {
|
||||||
|
return ReadOnlyList.empty();
|
||||||
|
}
|
||||||
|
return ReadOnlyList.wrap(List.of(modulePath.split("/")));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
package p.studio.compiler.pbs.parser;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
|
import p.studio.compiler.pbs.lexer.PbsLexer;
|
||||||
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class PbsBarrelParserTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldParseBarrelShapeWithFunctionReturnSurfaces() {
|
||||||
|
final var source = """
|
||||||
|
pub fn sum(a: int, b: int) -> int;
|
||||||
|
mod fn run(state: optional int) -> result<Err> (value: int, meta: float);
|
||||||
|
pub struct Vec2;
|
||||||
|
pub callback Tick;
|
||||||
|
""";
|
||||||
|
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
final var fileId = new FileId(0);
|
||||||
|
final var tokens = PbsLexer.lex(source, fileId, diagnostics);
|
||||||
|
final var barrel = PbsBarrelParser.parse(tokens, fileId, diagnostics);
|
||||||
|
|
||||||
|
assertTrue(diagnostics.isEmpty(), "Valid barrel should parse without diagnostics");
|
||||||
|
assertEquals(4, barrel.items().size());
|
||||||
|
|
||||||
|
final var sum = assertInstanceOf(PbsAst.BarrelFunctionItem.class, barrel.items().get(0));
|
||||||
|
assertEquals("sum", sum.name());
|
||||||
|
assertEquals(PbsAst.ReturnKind.PLAIN, sum.returnKind());
|
||||||
|
assertEquals("int", sum.returnType().name());
|
||||||
|
assertEquals(2, sum.parameterTypes().size());
|
||||||
|
|
||||||
|
final var run = assertInstanceOf(PbsAst.BarrelFunctionItem.class, barrel.items().get(1));
|
||||||
|
assertEquals(PbsAst.ReturnKind.RESULT, run.returnKind());
|
||||||
|
assertEquals("Err", run.resultErrorType().name());
|
||||||
|
assertEquals(PbsAst.TypeRefKind.NAMED_TUPLE, run.returnType().kind());
|
||||||
|
|
||||||
|
assertInstanceOf(PbsAst.BarrelStructItem.class, barrel.items().get(2));
|
||||||
|
assertInstanceOf(PbsAst.BarrelCallbackItem.class, barrel.items().get(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRecoverFromInvalidBarrelItems() {
|
||||||
|
final var source = """
|
||||||
|
pub fn ok() -> int;
|
||||||
|
pub type Invalid;
|
||||||
|
mod struct Ready;
|
||||||
|
""";
|
||||||
|
|
||||||
|
final var diagnostics = DiagnosticSink.empty();
|
||||||
|
final var fileId = new FileId(0);
|
||||||
|
final var barrel = PbsBarrelParser.parse(PbsLexer.lex(source, fileId, diagnostics), fileId, diagnostics);
|
||||||
|
|
||||||
|
assertTrue(diagnostics.hasErrors(), "Invalid barrel item should report diagnostics");
|
||||||
|
assertEquals(2, barrel.items().size());
|
||||||
|
assertInstanceOf(PbsAst.BarrelFunctionItem.class, barrel.items().get(0));
|
||||||
|
assertInstanceOf(PbsAst.BarrelStructItem.class, barrel.items().get(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user