From 5660aee6584370b93bbd871fbd76a624fb5e6736 Mon Sep 17 00:00:00 2001 From: Drew Youngwerth Date: Wed, 11 Sep 2024 01:37:48 -0700 Subject: [PATCH] Support object imports --- src/semantics/regular-macros.ts | 2 +- src/semantics/resolution/get-call-fn.ts | 2 +- src/semantics/resolution/resolve-use.ts | 112 +++++++++++++++++++----- src/syntax-objects/lexical-context.ts | 6 +- src/syntax-objects/module.ts | 4 +- src/syntax-objects/syntax.ts | 6 +- src/syntax-objects/use.ts | 6 +- 7 files changed, 104 insertions(+), 34 deletions(-) diff --git a/src/semantics/regular-macros.ts b/src/semantics/regular-macros.ts index d3f8ea2..53b67cd 100644 --- a/src/semantics/regular-macros.ts +++ b/src/semantics/regular-macros.ts @@ -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, diff --git a/src/semantics/resolution/get-call-fn.ts b/src/semantics/resolution/get-call-fn.ts index 3b39e3b..04a4ba4 100644 --- a/src/semantics/resolution/get-call-fn.ts +++ b/src/semantics/resolution/get-call-fn.ts @@ -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"; diff --git a/src/semantics/resolution/resolve-use.ts b/src/semantics/resolution/resolve-use.ts index 238bd8d..b38a443 100644 --- a/src/semantics/resolution/resolve-use.ts +++ b/src/semantics/resolution/resolve-use.ts @@ -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; @@ -13,7 +12,7 @@ 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; }; @@ -21,32 +20,28 @@ export const resolveUse = (use: Use, runPass?: ModulePass) => { 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 || @@ -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) => { @@ -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; } @@ -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); }; diff --git a/src/syntax-objects/lexical-context.ts b/src/syntax-objects/lexical-context.ts index dbc055f..ecb7eb0 100644 --- a/src/syntax-objects/lexical-context.ts +++ b/src/syntax-objects/lexical-context.ts @@ -8,10 +8,10 @@ export class LexicalContext { private readonly fnsById: Map = new Map(); private readonly entities: Map = 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); diff --git a/src/syntax-objects/module.ts b/src/syntax-objects/module.ts index 2438166..e6ccd26 100644 --- a/src/syntax-objects/module.ts +++ b/src/syntax-objects/module.ts @@ -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[] { diff --git a/src/syntax-objects/syntax.ts b/src/syntax-objects/syntax.ts index a0efbe3..07580c9 100644 --- a/src/syntax-objects/syntax.ts +++ b/src/syntax-objects/syntax.ts @@ -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 */ diff --git a/src/syntax-objects/use.ts b/src/syntax-objects/use.ts index 6e31af7..04b2e4e 100644 --- a/src/syntax-objects/use.ts +++ b/src/syntax-objects/use.ts @@ -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; } ) {