implements PR023

This commit is contained in:
bQUARKz 2026-03-06 11:45:47 +00:00
parent 213c6a2f76
commit 57f1a617ac
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
13 changed files with 383 additions and 15 deletions

View File

@ -0,0 +1,22 @@
package p.studio.compiler.pbs.stdlib;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.Optional;
public final class EmptyStdlibEnvironmentResolver implements StdlibEnvironmentResolver {
private static final StdlibEnvironment EMPTY = new StdlibEnvironment() {
@Override
public Optional<StdlibModuleSource> resolveModule(
final String project,
final ReadOnlyList<String> pathSegments) {
return Optional.empty();
}
};
@Override
public StdlibEnvironment resolve(final int stdlibVersion) {
return EMPTY;
}
}

View File

@ -0,0 +1,63 @@
package p.studio.compiler.pbs.stdlib;
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.identifiers.FileId;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
public final class InterfaceModuleLoader {
private final AtomicInteger nextSyntheticFileId;
public InterfaceModuleLoader() {
this(1_000_000);
}
public InterfaceModuleLoader(final int firstSyntheticFileId) {
this.nextSyntheticFileId = new AtomicInteger(firstSyntheticFileId);
}
public PbsModuleVisibilityValidator.ModuleUnit load(
final StdlibModuleSource moduleSource,
final DiagnosticSink diagnostics) {
final var sources = new ArrayList<PbsModuleVisibilityValidator.SourceFile>(moduleSource.sourceFiles().size());
for (final var sourceFile : moduleSource.sourceFiles()) {
final var fileId = new FileId(nextSyntheticFileId.getAndIncrement());
final var sourceAst = parseSource(sourceFile.source(), fileId, diagnostics);
sources.add(new PbsModuleVisibilityValidator.SourceFile(fileId, sourceAst));
}
final var barrels = new ArrayList<PbsModuleVisibilityValidator.BarrelFile>(1);
final var barrelFileId = new FileId(nextSyntheticFileId.getAndIncrement());
final var barrelAst = parseBarrel(moduleSource.barrelSource(), barrelFileId, diagnostics);
barrels.add(new PbsModuleVisibilityValidator.BarrelFile(barrelFileId, barrelAst));
return new PbsModuleVisibilityValidator.ModuleUnit(
new PbsModuleVisibilityValidator.ModuleCoordinates(moduleSource.project(), moduleSource.pathSegments()),
ReadOnlyList.wrap(sources),
ReadOnlyList.wrap(barrels));
}
private PbsAst.File parseSource(
final String source,
final FileId fileId,
final DiagnosticSink diagnostics) {
final var tokens = PbsLexer.lex(source, fileId, diagnostics);
return PbsParser.parse(tokens, fileId, diagnostics);
}
private PbsAst.BarrelFile parseBarrel(
final String source,
final FileId fileId,
final DiagnosticSink diagnostics) {
final var tokens = PbsLexer.lex(source, fileId, diagnostics);
return PbsBarrelParser.parse(tokens, fileId, diagnostics);
}
}

View File

@ -0,0 +1,10 @@
package p.studio.compiler.pbs.stdlib;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.Optional;
public interface StdlibEnvironment {
Optional<StdlibModuleSource> resolveModule(String project, ReadOnlyList<String> pathSegments);
}

View File

@ -0,0 +1,6 @@
package p.studio.compiler.pbs.stdlib;
public interface StdlibEnvironmentResolver {
StdlibEnvironment resolve(int stdlibVersion);
}

View File

@ -0,0 +1,29 @@
package p.studio.compiler.pbs.stdlib;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.Objects;
public record StdlibModuleSource(
String project,
ReadOnlyList<String> pathSegments,
ReadOnlyList<SourceFile> sourceFiles,
String barrelSource) {
public StdlibModuleSource {
project = Objects.requireNonNull(project, "project");
pathSegments = pathSegments == null ? ReadOnlyList.empty() : pathSegments;
sourceFiles = sourceFiles == null ? ReadOnlyList.empty() : sourceFiles;
barrelSource = Objects.requireNonNull(barrelSource, "barrelSource");
}
public record SourceFile(
String logicalPath,
String source) {
public SourceFile {
logicalPath = Objects.requireNonNull(logicalPath, "logicalPath");
source = Objects.requireNonNull(source, "source");
}
}
}

View File

@ -14,6 +14,9 @@ import p.studio.compiler.pbs.linking.PbsLinkErrors;
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.pbs.stdlib.EmptyStdlibEnvironmentResolver;
import p.studio.compiler.pbs.stdlib.InterfaceModuleLoader;
import p.studio.compiler.pbs.stdlib.StdlibEnvironmentResolver;
import p.studio.compiler.source.Span;
import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.compiler.source.diagnostics.DiagnosticPhase;
@ -23,6 +26,7 @@ import p.studio.utilities.logs.LogAggregator;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@ -34,6 +38,19 @@ import java.util.Set;
public class PBSFrontendPhaseService implements FrontendPhaseService {
private final PbsFrontendCompiler frontendCompiler = new PbsFrontendCompiler();
private final PbsModuleVisibilityValidator moduleVisibilityValidator = new PbsModuleVisibilityValidator();
private final StdlibEnvironmentResolver stdlibEnvironmentResolver;
private final InterfaceModuleLoader interfaceModuleLoader;
public PBSFrontendPhaseService() {
this(new EmptyStdlibEnvironmentResolver(), new InterfaceModuleLoader());
}
PBSFrontendPhaseService(
final StdlibEnvironmentResolver stdlibEnvironmentResolver,
final InterfaceModuleLoader interfaceModuleLoader) {
this.stdlibEnvironmentResolver = stdlibEnvironmentResolver;
this.interfaceModuleLoader = interfaceModuleLoader;
}
@Override
public IRBackend compile(
@ -99,6 +116,8 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
}
}
loadReservedStdlibModules(modulesByCoordinates, diagnostics, ctx.stdlibVersion());
final var modules = new ArrayList<PbsModuleVisibilityValidator.ModuleUnit>(modulesByCoordinates.size());
for (final var entry : modulesByCoordinates.entrySet()) {
final var coordinates = entry.getKey();
@ -127,6 +146,67 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
return irBackend;
}
private void loadReservedStdlibModules(
final Map<PbsModuleVisibilityValidator.ModuleCoordinates, MutableModuleUnit> modulesByCoordinates,
final DiagnosticSink diagnostics,
final int stdlibVersion) {
final var stdlibEnvironment = stdlibEnvironmentResolver.resolve(stdlibVersion);
final var pending = new ArrayDeque<PbsModuleVisibilityValidator.ModuleCoordinates>();
final var resolved = new HashSet<String>();
enqueueReservedImportsFromKnownModules(modulesByCoordinates, pending);
while (!pending.isEmpty()) {
final var target = pending.removeFirst();
final var targetKey = moduleKey(target);
if (!resolved.add(targetKey)) {
continue;
}
if (modulesByCoordinates.containsKey(target)) {
continue;
}
final var moduleSource = stdlibEnvironment.resolveModule(target.project(), target.pathSegments());
if (moduleSource.isEmpty()) {
continue;
}
final var loadedModule = interfaceModuleLoader.load(moduleSource.get(), diagnostics);
final var moduleData = new MutableModuleUnit();
moduleData.sources.addAll(loadedModule.sourceFiles().asList());
moduleData.barrels.addAll(loadedModule.barrelFiles().asList());
modulesByCoordinates.put(loadedModule.coordinates(), moduleData);
enqueueReservedImportsFromSourceFiles(loadedModule.sourceFiles().asList(), pending);
}
}
private void enqueueReservedImportsFromKnownModules(
final Map<PbsModuleVisibilityValidator.ModuleCoordinates, MutableModuleUnit> modulesByCoordinates,
final ArrayDeque<PbsModuleVisibilityValidator.ModuleCoordinates> pending) {
for (final var moduleUnit : modulesByCoordinates.values()) {
enqueueReservedImportsFromSourceFiles(moduleUnit.sources, pending);
}
}
private void enqueueReservedImportsFromSourceFiles(
final Iterable<PbsModuleVisibilityValidator.SourceFile> sourceFiles,
final ArrayDeque<PbsModuleVisibilityValidator.ModuleCoordinates> pending) {
for (final var source : sourceFiles) {
for (final var importDecl : source.ast().imports()) {
final var moduleRef = importDecl.moduleRef();
if (!isReservedProjectSpace(moduleRef.project())) {
continue;
}
pending.addLast(new PbsModuleVisibilityValidator.ModuleCoordinates(
moduleRef.project(),
moduleRef.pathSegments()));
}
}
}
private boolean isReservedProjectSpace(final String projectName) {
return "core".equals(projectName) || "sdk".equals(projectName);
}
private PbsAst.File parseSourceFile(
final FileId fileId,
final String source,

View File

@ -8,6 +8,10 @@ import p.studio.compiler.models.BuildStack;
import p.studio.compiler.models.ProjectDescriptor;
import p.studio.compiler.models.SourceHandle;
import p.studio.compiler.models.SourceKind;
import p.studio.compiler.pbs.stdlib.InterfaceModuleLoader;
import p.studio.compiler.pbs.stdlib.StdlibEnvironment;
import p.studio.compiler.pbs.stdlib.StdlibEnvironmentResolver;
import p.studio.compiler.pbs.stdlib.StdlibModuleSource;
import p.studio.compiler.pbs.linking.PbsLinkErrors;
import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.compiler.source.identifiers.ProjectId;
@ -21,7 +25,10 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -216,6 +223,111 @@ class PBSFrontendPhaseServiceTest {
assertEquals(0, irBackend.getFunctions().size());
}
@Test
void shouldResolveReservedImportsThroughStdlibResolverForSelectedMajor() throws IOException {
final var projectRoot = tempDir.resolve("project-stdlib");
final var sourceRoot = projectRoot.resolve("src");
final var modulePath = sourceRoot.resolve("app");
Files.createDirectories(modulePath);
final var sourceFile = modulePath.resolve("source.pbs");
final var modBarrel = modulePath.resolve("mod.barrel");
Files.writeString(sourceFile, """
import { draw } from @sdk:gfx;
fn use() -> int { return 1; }
""");
Files.writeString(modBarrel, "pub fn use() -> int;");
final var projectTable = new ProjectTable();
final var fileTable = new FileTable(1);
final var projectId = projectTable.register(ProjectDescriptor.builder()
.rootPath(projectRoot)
.name("app")
.version("1.0.0")
.sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
.build());
registerFile(projectId, projectRoot, sourceFile, fileTable);
registerFile(projectId, projectRoot, modBarrel, fileTable);
final var stdlibModule = new StdlibModuleSource(
"sdk",
ReadOnlyList.wrap(List.of("gfx")),
ReadOnlyList.wrap(List.of(new StdlibModuleSource.SourceFile("gfx.pbs", "fn draw() -> int { return 1; }"))),
"pub fn draw() -> int;");
final var frontendService = new PBSFrontendPhaseService(
resolverForMajor(7, stdlibModule),
new InterfaceModuleLoader(2_000_000));
final var ctx = new FrontendPhaseContext(
projectTable,
fileTable,
new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
7);
final var diagnostics = DiagnosticSink.empty();
final var irBackend = frontendService.compile(
ctx,
diagnostics,
LogAggregator.empty(),
BuildingIssueSink.empty());
assertTrue(diagnostics.stream().noneMatch(d ->
d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_MODULE_NOT_FOUND.name())));
assertTrue(diagnostics.stream().noneMatch(d ->
d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_SYMBOL_UNRESOLVED.name())));
assertEquals(1, irBackend.getFunctions().size());
assertEquals("use", irBackend.getFunctions().getFirst().name());
}
@Test
void shouldReportReservedImportModuleNotFoundWhenStdlibResolverMissesModule() throws IOException {
final var projectRoot = tempDir.resolve("project-stdlib-missing");
final var sourceRoot = projectRoot.resolve("src");
final var modulePath = sourceRoot.resolve("app");
Files.createDirectories(modulePath);
final var sourceFile = modulePath.resolve("source.pbs");
final var modBarrel = modulePath.resolve("mod.barrel");
Files.writeString(sourceFile, """
import { draw } from @sdk:gfx;
fn use() -> int { return 1; }
""");
Files.writeString(modBarrel, "pub fn use() -> int;");
final var projectTable = new ProjectTable();
final var fileTable = new FileTable(1);
final var projectId = projectTable.register(ProjectDescriptor.builder()
.rootPath(projectRoot)
.name("app")
.version("1.0.0")
.sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
.build());
registerFile(projectId, projectRoot, sourceFile, fileTable);
registerFile(projectId, projectRoot, modBarrel, fileTable);
final var frontendService = new PBSFrontendPhaseService(
resolverForMajor(7),
new InterfaceModuleLoader(2_100_000));
final var ctx = new FrontendPhaseContext(
projectTable,
fileTable,
new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
7);
final var diagnostics = DiagnosticSink.empty();
final var irBackend = frontendService.compile(
ctx,
diagnostics,
LogAggregator.empty(),
BuildingIssueSink.empty());
assertTrue(diagnostics.stream().anyMatch(d ->
d.getCode().equals(PbsLinkErrors.E_LINK_IMPORT_MODULE_NOT_FOUND.name())));
assertEquals(0, irBackend.getFunctions().size());
}
private void registerFile(
final ProjectId projectId,
final Path projectRoot,
@ -231,4 +343,32 @@ class PBSFrontendPhaseServiceTest {
SourceProviderFactory.filesystem()));
}
private StdlibEnvironmentResolver resolverForMajor(
final int acceptedStdlib,
final StdlibModuleSource... modules) {
final var byModuleKey = new HashMap<String, StdlibModuleSource>();
for (final var module : modules) {
byModuleKey.put(moduleKey(module.project(), module.pathSegments()), module);
}
return stdlib -> {
if (stdlib != acceptedStdlib) {
return (project, pathSegments) -> Optional.empty();
}
return new StdlibEnvironment() {
@Override
public Optional<StdlibModuleSource> resolveModule(
final String project,
final ReadOnlyList<String> pathSegments) {
return Optional.ofNullable(byModuleKey.get(moduleKey(project, pathSegments)));
}
};
};
}
private String moduleKey(
final String project,
final ReadOnlyList<String> pathSegments) {
return project + ":" + String.join("/", pathSegments.asList());
}
}

View File

@ -23,7 +23,11 @@ public class FrontendPhasePipelineStage implements PipelineStage {
}
final var projectTable = ctx.resolvedWorkspace.graph().projectTable();
final var fileTable = ctx.fileTable;
final var frontendPhaseContext = new FrontendPhaseContext(projectTable, fileTable, ctx.resolvedWorkspace.stack());
final var frontendPhaseContext = new FrontendPhaseContext(
projectTable,
fileTable,
ctx.resolvedWorkspace.stack(),
ctx.resolvedWorkspace.stdlib());
final var diagnostics = DiagnosticSink.empty();
final var issues = BuildingIssueSink.empty();
ctx.irBackend = service.get().compile(frontendPhaseContext, diagnostics, logs, issues);

View File

@ -47,7 +47,7 @@ public final class DependencyContext {
throw new BuildException("dependenciesByProjectId: internal error: rootProjectId ProjectId not set");
}
if (rootStdlib == null) {
throw new BuildException("dependenciesByProjectId: internal error: rootStdlibMajor not set");
throw new BuildException("dependenciesByProjectId: internal error: rootStdlib not set");
}
final var dependenciesByProject = buildDependenciesByProject();
final var workspaceGraph = new WorkspaceGraph(projectTable, dependenciesByProject);

View File

@ -5,7 +5,7 @@ import p.studio.compiler.source.identifiers.ProjectId;
public record ResolvedWorkspace(
ProjectId mainProjectId,
FrontendSpec frontendSpec,
int stdlibMajor,
int stdlib,
WorkspaceGraph graph,
BuildStack stack) {
public ProjectDescriptor mainProject() {

View File

@ -45,7 +45,7 @@ public final class PrometeuManifestUtils {
.error(true)
.message("[DEPS]: manifest missing 'version': " + manifestPathCanon));
}
final var stdlibMajorMaybe = parseStdlibMajor(dto.stdlib(), manifestPathCanon, issuesLocal);
final var stdlibMaybe = parseStdlib(dto.stdlib(), manifestPathCanon, issuesLocal);
final var language = StringUtils.isBlank(dto.language())
? FrontendRegistryService.getDefaultFrontendSpec().getLanguageId()
: dto.language();
@ -54,34 +54,34 @@ public final class PrometeuManifestUtils {
issues.merge(issuesLocal);
return Optional.empty();
}
return Optional.of(new PrometeuManifest(dto.name(), dto.version(), language, stdlibMajorMaybe.getAsInt(), ReadOnlyList.wrap(dependencies)));
return Optional.of(new PrometeuManifest(dto.name(), dto.version(), language, stdlibMaybe.getAsInt(), ReadOnlyList.wrap(dependencies)));
}
private static OptionalInt parseStdlibMajor(
final String stdlib,
private static OptionalInt parseStdlib(
final String stdlibStr,
final Path manifestPathCanon,
final BuildingIssueSink issues) {
if (StringUtils.isBlank(stdlib)) {
if (StringUtils.isBlank(stdlibStr)) {
issues.report(builder -> builder
.error(true)
.message("[DEPS]: manifest missing 'stdlib': " + manifestPathCanon));
return OptionalInt.empty();
}
final int stdlibMajor;
final int stdlib;
try {
stdlibMajor = Integer.parseInt(stdlib);
stdlib = Integer.parseInt(stdlibStr);
} catch (NumberFormatException e) {
issues.report(builder -> builder
.error(true)
.message("[DEPS]: invalid 'stdlib' major version \"" + stdlib + "\": " + manifestPathCanon));
.message("[DEPS]: invalid 'stdlib' major version \"" + stdlibStr + "\": " + manifestPathCanon));
return OptionalInt.empty();
}
if (stdlibMajor <= 0 || !stdlib.equals(Integer.toString(stdlibMajor))) {
if (stdlib <= 0 || !stdlibStr.equals(Integer.toString(stdlib))) {
issues.report(builder -> builder
.error(true)
.message("[DEPS]: invalid 'stdlib' major version \"" + stdlib + "\": " + manifestPathCanon));
.message("[DEPS]: invalid 'stdlib' major version \"" + stdlibStr + "\": " + manifestPathCanon));
return OptionalInt.empty();
}
return OptionalInt.of(stdlibMajor);
return OptionalInt.of(stdlib);
}
}

View File

@ -16,7 +16,7 @@ class PrometeuManifestUtilsTest {
Path tempDir;
@Test
void buildManifestParsesStdlibMajor() throws IOException {
void buildManifestParsesStdlib() throws IOException {
final var manifestPath = writeManifest("""
{
"name": "main",

View File

@ -10,17 +10,31 @@ public class FrontendPhaseContext {
public final ProjectTableReader projectTable;
public final FileTableReader fileTable;
public final BuildStack stack;
private final int stdlibVersion;
public FrontendPhaseContext(
final ProjectTableReader projectTable,
final FileTableReader fileTable,
final BuildStack stack) {
this(projectTable, fileTable, stack, 1);
}
public FrontendPhaseContext(
final ProjectTableReader projectTable,
final FileTableReader fileTable,
final BuildStack stack,
final int stdlibVersion) {
this.projectTable = projectTable;
this.fileTable = fileTable;
this.stack = stack;
this.stdlibVersion = stdlibVersion;
}
public SourceKind sourceKind(final ProjectId projectId) {
return projectTable.sourceKind(projectId);
}
public int stdlibVersion() {
return stdlibVersion;
}
}