Skip to content

Commit

Permalink
Support object imports
Browse files Browse the repository at this point in the history
  • Loading branch information
drew-y committed Sep 11, 2024
1 parent 394bb69 commit 5660aee
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 34 deletions.
2 changes: 1 addition & 1 deletion src/semantics/regular-macros.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const expandModuleMacros = (module: VoidModule): VoidModule => {
const initUse = (list: List) => {
const path = list.listAt(1);
const entities = resolveModulePath(path, expandModuleMacros);
entities.forEach((e) => list.parent?.registerEntity(e));
entities.forEach((e) => list.parent?.registerEntity(e.e, e.alias));

return new Use({
...list.metadata,
Expand Down
2 changes: 1 addition & 1 deletion src/semantics/resolution/get-call-fn.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Call, Expr, Fn, Parameter } from "../../syntax-objects/index.js";
import { Call, Expr, Fn } from "../../syntax-objects/index.js";
import { getExprType } from "./get-expr-type.js";
import { typesAreEquivalent } from "./types-are-equivalent.js";
import { resolveFnTypes } from "./resolve-fn-type.js";
Expand Down
112 changes: 90 additions & 22 deletions src/semantics/resolution/resolve-use.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Call } from "../../syntax-objects/call.js";
import { Expr } from "../../syntax-objects/expr.js";
import { Identifier } from "../../syntax-objects/identifier.js";
import { List } from "../../syntax-objects/list.js";
import { VoidModule } from "../../syntax-objects/module.js";
import { NamedEntity } from "../../syntax-objects/named-entity.js";
import { Use } from "../../syntax-objects/use.js";
import { Use, UseEntities } from "../../syntax-objects/use.js";
import { resolveModuleTypes, resolveTypes } from "./resolve-types.js";

export type ModulePass = (mod: VoidModule) => VoidModule;
Expand All @@ -13,40 +12,36 @@ export const resolveUse = (use: Use, runPass?: ModulePass) => {
const path = use.path;

const entities = resolveModulePath(path, runPass);
entities.forEach((e) => use.parentModule?.registerEntity(e));
entities.forEach((e) => use.parentModule?.registerEntity(e.e, e.alias));
use.entities = entities;
return use;
};

export const resolveModulePath = (
path: Expr,
runPass?: ModulePass
): NamedEntity[] => {
): { e: NamedEntity; alias?: string }[] => {
if (path.isIdentifier()) {
const entity = path.resolve();
return entity ? [entity] : [];
const e = path.resolve();
return e ? [{ e }] : [];
}

if (!path.isCall() && !path.isList()) {
throw new Error("Invalid path statement");
}

if (path.calls("object")) {
const imports = path.argsArray();
}

if (!path.calls("::")) {
throw new Error(`Invalid use statement ${path}`);
}

const [left, right] = path.argsArray();

const [resolvedModule] =
const resolvedModule =
left?.isList() || left?.isCall()
? resolveModulePath(left, runPass)
? resolveModulePath(left, runPass)[0]?.e
: left?.isIdentifier()
? [left.resolveModule(left)]
: [];
? left.resolveModule(left)
: undefined;

if (
!resolvedModule ||
Expand All @@ -58,22 +53,86 @@ export const resolveModulePath = (

const module = runPass ? runPass(resolvedModule) : resolvedModule;

if ((right.isCall() || right.isList()) && right.calls("object")) {
return resolveObjectPath(right, module);
}

if (!right?.isIdentifier()) {
if (resolvedModule.phase < 3) return []; // Ignore unresolved entities in macro phase
throw new Error(`Invalid use statement, expected identifier, got ${right}`);
}

if (right?.is("all")) {
return module.getAllExports();
return module.getAllExports().map((e) => ({ e }));
}

const entity = module.resolveExport(right);
if (!entity.length) {
const entities = module.resolveExport(right);
if (!entities.length) {
if (resolvedModule.phase < 3) return []; // Ignore unresolved entities in macro phase
throw new Error(
`Invalid use statement, entity ${right} is not exported at ${right.location}`
);
}

return entity;
return entities.map((e) => ({ e }));
};

const resolveObjectPath = (path: Call | List, module: VoidModule) => {
const entities: UseEntities = [];

const imports = path.argsArray();
for (const imp of imports) {
if ((imp.isCall() || imp.isList()) && imp.calls("as")) {
const args = imp.argsArray();
const entityId = args.at(0);
const alias = args.at(1);
if (!entityId?.isIdentifier() || !alias?.isIdentifier()) {
if (module.phase < 3) continue; // Ignore unresolved entities in macro phase
throw new Error(
`Invalid use statement, expected identifiers, got ${imp}`
);
}

if (entityId.is("self")) {
entities.push({ e: module, alias: alias?.value });
continue;
}

const resolvedEntities = module.resolveExport(entityId);
if (!resolvedEntities.length) {
if (module.phase < 3) continue; // Ignore unresolved entities in macro phase
throw new Error(
`Invalid use statement, entity ${entityId} is not exported at ${entityId.location}`
);
}

entities.push(
...resolvedEntities.map((e) => ({ e, alias: alias?.value }))
);
continue;
}

if (!imp.isIdentifier()) {
throw new Error(`Invalid use statement, expected identifier, got ${imp}`);
}

if (imp.is("self")) {
entities.push({ e: module });
continue;
}

const resolvedEntities = module.resolveExport(imp);
if (!resolvedEntities.length) {
if (module.phase < 3) continue; // Ignore unresolved entities in macro phase
throw new Error(
`Invalid use statement, entity ${imp} is not exported at ${imp.location}`
);
}

entities.push(...resolvedEntities.map((e) => ({ e })));
}

return entities;
};

export const resolveExport = (call: Call) => {
Expand All @@ -88,12 +147,17 @@ export const resolveExport = (call: Call) => {

export const registerExports = (
exportExpr: Expr,
entities: (Expr | NamedEntity)[],
entities: (Expr | NamedEntity | { e: NamedEntity; alias?: string })[],
pass?: ModulePass
) => {
entities.forEach((e) => {
if ("e" in e) {
registerExport(exportExpr, e.e, e.alias);
return;
}

if (e.isUse()) {
e.entities.forEach((e) => registerExport(exportExpr, e));
e.entities.forEach((e) => registerExport(exportExpr, e.e, e.alias));
return;
}

Expand All @@ -109,6 +173,10 @@ export const registerExports = (
});
};

const registerExport = (exportExpr: Expr, entity: NamedEntity) => {
exportExpr.parentModule?.registerExport(entity);
const registerExport = (
exportExpr: Expr,
entity: NamedEntity,
alias?: string
) => {
exportExpr.parentModule?.registerExport(entity, alias);
};
6 changes: 3 additions & 3 deletions src/syntax-objects/lexical-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ export class LexicalContext {
private readonly fnsById: Map<string, Fn> = new Map();
private readonly entities: Map<string, NamedEntity> = new Map();

registerEntity(entity: NamedEntity) {
const idStr = getIdStr(entity.name);
registerEntity(entity: NamedEntity, alias?: string) {
const idStr = alias ?? getIdStr(entity.name);
if (entity.isFn()) {
if (this.fnsById.get(entity.id)) return; // Already registered
if (!alias && this.fnsById.get(entity.id)) return; // Already registered
const fns = this.fns.get(idStr) ?? [];
fns.push(entity);
this.fns.set(idStr, fns);
Expand Down
4 changes: 2 additions & 2 deletions src/syntax-objects/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ export class VoidModule extends ScopedNamedEntity {
this.isIndex = opts.isIndex ?? false;
}

registerExport(entity: NamedEntity) {
this.exports.registerEntity(entity);
registerExport(entity: NamedEntity, alias?: string) {
this.exports.registerEntity(entity, alias);
}

resolveExport(name: Id): NamedEntity[] {
Expand Down
6 changes: 3 additions & 3 deletions src/syntax-objects/syntax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ export abstract class Syntax {
return this.lexicon.getAllEntities();
}

registerEntity(v: NamedEntity): void {
if (!this.isScopedEntity()) return this.parent?.registerEntity(v);
this.lexicon.registerEntity(v);
registerEntity(v: NamedEntity, alias?: string): void {
if (!this.isScopedEntity()) return this.parent?.registerEntity(v, alias);
this.lexicon.registerEntity(v, alias);
}

/** Will resolve a sibling module, or a direct ancestor */
Expand Down
6 changes: 4 additions & 2 deletions src/syntax-objects/use.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ import { List } from "./list.js";
import { NamedEntity } from "./named-entity.js";
import { Syntax, SyntaxMetadata } from "./syntax.js";

export type UseEntities = { e: NamedEntity; alias?: string }[];

/** Defines a declared namespace for external function imports */
export class Use extends Syntax {
readonly syntaxType = "use";
entities: NamedEntity[];
entities: UseEntities;
path: List | Identifier;

constructor(
opts: SyntaxMetadata & {
entities: NamedEntity[];
entities: UseEntities;
path: List | Identifier;
}
) {
Expand Down

0 comments on commit 5660aee

Please sign in to comment.