From e0566233c0db423e31abd9c46339bd82cbe9d3f7 Mon Sep 17 00:00:00 2001 From: Sam Vervaeck Date: Mon, 25 May 2020 15:52:11 +0200 Subject: [PATCH] Improve symbol resolution algorithms in type checker --- src/ast-spec.txt | 2 +- src/ast.d.ts | 43 +-- src/bin/bolt.ts | 9 +- src/checker.ts | 549 ++++++++++++++++++++++++----------- src/common.ts | 59 ++-- src/diagnostics.ts | 10 +- src/frontend.ts | 17 +- src/parser.ts | 5 +- src/program.ts | 56 +++- src/treegen/ast.dts.template | 25 ++ src/treegen/index.ts | 28 +- src/util.ts | 19 ++ 12 files changed, 535 insertions(+), 287 deletions(-) create mode 100644 src/treegen/ast.dts.template diff --git a/src/ast-spec.txt b/src/ast-spec.txt index a8a46662f..469fbb927 100644 --- a/src/ast-spec.txt +++ b/src/ast-spec.txt @@ -277,7 +277,7 @@ node BoltPlainImportSymbol > BoltImportSymbol { node BoltImportDirective > BoltSourceElement { modifiers: BoltModifiers, - file: String, + file: BoltStringLiteral, symbols: Vec, } diff --git a/src/ast.d.ts b/src/ast.d.ts index 3948a2abf..04ad38347 100644 --- a/src/ast.d.ts +++ b/src/ast.d.ts @@ -1,8 +1,28 @@ +import { Type } from "./types" +import { ScopeInfo } from "./checker" import { Package } from "./common" +import { TextSpan } from "./text" + +export function setParents(node: Syntax): void; + +export type SyntaxRange = [Syntax, Syntax]; export function isSyntax(value: any): value is Syntax; +interface SyntaxBase { + id: number; + _scope?: ScopeInfo; + kind: K; + parentNode: ParentTypesOf | null; + span: TextSpan | null; + getChildNodes(): IterableIterator>, + findAllChildrenOfKind(kind: K1): IterableIterator>; +} + +export type ResolveSyntaxKind = Extract; + + export const enum SyntaxKind { EndOfFile = 2, @@ -158,25 +178,6 @@ export const enum SyntaxKind { JSSourceFile = 182, } - - -import { TextSpan } from "./text" - -export function setParents(node: Syntax): void; - -export type SyntaxRange = [Syntax, Syntax]; - -interface SyntaxBase { - id: number; - kind: K; - parentNode: ParentTypesOf | null; - span: TextSpan | null; - getChildNodes(): IterableIterator>, - findAllChildrenOfKind(kind: K1): IterableIterator>; -} - -export type ResolveSyntaxKind = Extract; - export interface EndOfFile extends SyntaxBase { kind: SyntaxKind.EndOfFile; } @@ -824,7 +825,7 @@ export interface BoltPlainImportSymbol extends SyntaxBase { kind: SyntaxKind.BoltImportDirective; modifiers: BoltModifiers; - file: string; + file: BoltStringLiteral; symbols: BoltImportSymbol[]; } @@ -1705,7 +1706,7 @@ export function createBoltModule(modifiers: BoltModifiers, name: BoltQualName, e export function createBoltFunctionDeclaration(modifiers: BoltModifiers, target: string, name: BoltSymbol, params: BoltParameter[], returnType: BoltTypeExpression | null, typeParams: BoltTypeParameter[] | null, body: BoltFunctionBodyElement[], span?: TextSpan | null): BoltFunctionDeclaration; export function createBoltVariableDeclaration(modifiers: BoltModifiers, bindings: BoltPattern, type: BoltTypeExpression | null, value: BoltExpression | null, span?: TextSpan | null): BoltVariableDeclaration; export function createBoltPlainImportSymbol(name: BoltQualName, span?: TextSpan | null): BoltPlainImportSymbol; -export function createBoltImportDirective(modifiers: BoltModifiers, file: string, symbols: BoltImportSymbol[], span?: TextSpan | null): BoltImportDirective; +export function createBoltImportDirective(modifiers: BoltModifiers, file: BoltStringLiteral, symbols: BoltImportSymbol[], span?: TextSpan | null): BoltImportDirective; export function createBoltExportSymbol(span?: TextSpan | null): BoltExportSymbol; export function createBoltPlainExportSymbol(name: BoltQualName, span?: TextSpan | null): BoltPlainExportSymbol; export function createBoltExportDirective(file: string, symbols: BoltExportSymbol[] | null, span?: TextSpan | null): BoltExportDirective; diff --git a/src/bin/bolt.ts b/src/bin/bolt.ts index fe680f4ce..ac8a147b8 100644 --- a/src/bin/bolt.ts +++ b/src/bin/bolt.ts @@ -36,11 +36,6 @@ function pushAll(array: T[], elements: T[]) { } } -function stripExtension(filepath: string) { - const i = filepath.lastIndexOf('.'); - return i !== -1 ? filepath.substring(0, i) : filepath -} - function flatMap(array: T[], proc: (element: T) => T[]) { let out: T[] = [] for (const element of array) { @@ -123,6 +118,7 @@ function loadPackageMetadata(rootDir: string) { } function loadPackage(rootDir: string): Package { + rootDir = path.resolve(rootDir); const data = loadPackageMetadata(rootDir); const pkg = new Package(rootDir, data.name, data.version, []); for (const filepath of globSync(path.join(rootDir, '**/*.bolt'))) { @@ -132,13 +128,14 @@ function loadPackage(rootDir: string): Package { } function loadPackagesAndSourceFiles(filenames: string[], cwd = '.'): Package[] { + cwd = path.resolve(cwd); const anonPkg = new Package(cwd, null, null, []); const pkgs = [ anonPkg ]; for (const filename of filenames) { if (fs.statSync(filename).isDirectory()) { pkgs.push(loadPackage(filename)); } else { - anonPkg.addSourceFile(parseSourceFile(filename, anonPkg, 0)); + anonPkg.addSourceFile(parseSourceFile(filename, anonPkg)); } } return pkgs; diff --git a/src/checker.ts b/src/checker.ts index bf02f80da..3362da583 100644 --- a/src/checker.ts +++ b/src/checker.ts @@ -42,9 +42,12 @@ import { isBoltStatement, isBoltDeclaration, isSourceFile, - BoltReferenceTypeExpression + BoltReferenceTypeExpression, + isBoltTypeDeclaration, + SourceFile, + BoltModifiers } from "./ast"; -import {FastStringMap, memoize, assert, verbose} from "./util"; +import {FastStringMap, countDigits, assert, verbose} from "./util"; import { DiagnosticPrinter, E_TYPES_NOT_ASSIGNABLE, @@ -52,157 +55,90 @@ import { E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL, E_TYPE_DECLARATION_NOT_FOUND, E_DECLARATION_NOT_FOUND, - E_INVALID_ARGUMENTS + E_INVALID_ARGUMENTS, + E_FILE_NOT_FOUND, } from "./diagnostics"; import { createAnyType, isOpaqueType, createOpaqueType, Type, createVoidType, createVariantType, isVoidType } from "./types"; -import { getReturnStatementsInFunctionBody, isAutoImported, toDeclarationPath, createDeclarationPath, hasRelativeModulePath, hasAbsoluteModulePath, DeclarationPath, getModulePath, getSymbolNameOfDeclarationPath } from "./common"; +import { getReturnStatementsInFunctionBody, Package } from "./common"; import {emit} from "./emitter"; +import {Program} from "./program"; +import {type} from "os"; -const PACKAGE_SCOPE_ID = 0; +// TODO +const GLOBAL_SCOPE_ID = 0; -interface SymbolInfo { - declarations: N[]; -} +class SymbolPath { -function introducesNewScope(kind: SyntaxKind): boolean { - return kind === SyntaxKind.BoltSourceFile - || kind === SyntaxKind.BoltModule - || kind === SyntaxKind.BoltFunctionDeclaration - || kind === SyntaxKind.BoltBlockExpression; -} - -function introducesNewTypeScope(kind: SyntaxKind): boolean { - return kind === SyntaxKind.BoltModule - || kind === SyntaxKind.BoltSourceFile; -} - -type Scope = unknown; -type TypeScope = unknown; - -function getScopeId(scope: Scope | TypeScope) { - if (isSyntax(scope)) { - return scope.id; - } - return PACKAGE_SCOPE_ID; -} - -function createSymbol(node: BoltDeclaration): SymbolInfo { - return { declarations: [ node ] }; -} - -class SymbolResolver { - - private symbols = new FastStringMap>(); - - constructor(private introducesNewScope: (kind: SyntaxKind) => boolean) { + constructor( + private parents: string[], + public isAbsolute: boolean, + public name: string + ) { } - public addSymbol(name: string, node: N): void { - const scope = this.getScopeSurroundingNode(node) - verbose(`Adding symbol ${name} in scope #${getScopeId(scope)}`); - const sym = { declarations: [ node ] }; - this.symbols.set(`${name}@${getScopeId(scope)}`, sym); + public hasParents(): boolean { + return this.parents.length > 0; } - public getParentScope(scope: Scope): Scope | null { - if (!isSyntax(scope)) { - // Scope is the global package scope - return null; - } - if (scope.kind === SyntaxKind.BoltSourceFile) { - return scope.package; - } - return this.getScopeForNode(scope.parentNode!) + public getParents() { + return this.parents; } - public getScopeSurroundingNode(node: Syntax): Scope { - assert(node.parentNode !== null); - return this.getScopeForNode(node.parentNode!); - } +} - public getScopeForNode(node: Syntax): Scope { - let currNode = node; - while (!this.introducesNewScope(currNode.kind)) { - if (currNode.kind === SyntaxKind.BoltSourceFile) { - return currNode.package; +function nodeToSymbolPath(node: BoltSyntax): SymbolPath { + switch (node.kind) { + case SyntaxKind.BoltIdentifier: + return new SymbolPath([], false, emit(node)); + case SyntaxKind.BoltQualName: + const name = emit(node.name); + if (node.modulePath === null) { + return new SymbolPath([], false, name); } - currNode = currNode.parentNode!; - } - return currNode; + return new SymbolPath(node.modulePath.map(id => id.text), false, name); + default: + throw new Error(`Could not extract a symbol path from the given node.`); } +} - private lookupSymbolInScope(name: string, scope: Scope): SymbolInfo | null { - const key = `${name}@${getScopeId(scope)}`; - if (!this.symbols.has(key)) { - return null; +enum SymbolKind { + Type = 0x1, + Variable = 0x2, + Module = 0x4, +} + +function* getAllSymbolKindsInMask(symbolKindMask: SymbolKind) { + const n = countDigits(symbolKindMask, 2); + for (let i = 1; i <= n; i++) { + if ((symbolKindMask & i) > 0) { + yield i; } - return this.symbols.get(key); } +} - public findSymbolInScopeOf(name: string, scope: Scope): SymbolInfo | null { - while (true) { - const sym = this.lookupSymbolInScope(name, scope); - if (sym !== null) { - return sym; - } - const parentScope = this.getParentScope(scope); - if (parentScope === null) { - break; - } - scope = parentScope; - } - return null; - } - - public resolve(path: DeclarationPath, node: BoltSyntax): BoltSyntax | null { - let scope = this.getScopeSurroundingNode(node); - if (hasAbsoluteModulePath(path)) { - // TODO - } else if (hasRelativeModulePath(path)) { - while (true) { - let shouldSearchParentScopes = false; - let currScope = scope; - for (const name of getModulePath(path)) { - const sym = this.lookupSymbolInScope(name, currScope); - if (sym === null) { - shouldSearchParentScopes = true; - break; - } - if (sym.declarations[0].kind !== SyntaxKind.BoltModule) { - shouldSearchParentScopes = true; - break; - } - currScope = this.getScopeForNode(sym.declarations[0]); - } - if (!shouldSearchParentScopes) { - scope = currScope; - break; - } - const parentScope = this.getParentScope(scope); - if (parentScope === null) { - return null; - } - scope = parentScope; - } - } - const sym = this.findSymbolInScopeOf(getSymbolNameOfDeclarationPath(path), scope); - if (sym === null) { - return null; - } - return sym.declarations[0]!; - } +export interface ScopeInfo { + id: number; + declaration: BoltSyntax | Package; + parentScope?: ScopeInfo; + kind: SymbolKind, +} +interface SymbolInfo { + kind: SymbolKind; + declarations: BoltSyntax[]; } export class TypeChecker { - constructor(private diagnostics: DiagnosticPrinter) { + constructor( + private diagnostics: DiagnosticPrinter, + private program: Program + ) { } - private varResolver = new SymbolResolver(introducesNewScope); - private typeResolver = new SymbolResolver(introducesNewTypeScope); + private symbols = new FastStringMap(); public checkSourceFile(node: BoltSourceFile): void { @@ -314,14 +250,6 @@ export class TypeChecker { switch (node.kind) { - case SyntaxKind.BoltModule: - { - for (const element of node.elements) { - visitSourceElement(element); - } - break; - } - case SyntaxKind.BoltRecordDeclaration: { if (node.members !== null) { @@ -460,16 +388,15 @@ export class TypeChecker { } - private resolveType(name: string, node: BoltSyntax): Type | null { - const sym = this.typeResolver.findSymbolInScopeOf(name, this.typeResolver.getScopeSurroundingNode(node)) + private resolveTypeName(name: string, node: BoltSyntax): Type | null { + const sym = this.findSymbolInScopeOf(name, this.getScopeSurroundingNode(node)); if (sym === null) { return null; } return this.getTypeOfNode(sym.declarations[0]); } - @memoize(node => node.id) - private getTypeOfNode(node: BoltSyntax): Type { + private createType(node: BoltSyntax): Type { switch (node.kind) { case SyntaxKind.BoltReferenceTypeExpression: { @@ -504,18 +431,18 @@ export class TypeChecker { } case SyntaxKind.BoltConstantExpression: { - let type; - if (typeof node.value === 'string') { - type = this.resolveType('String', node)!; - } else if (typeof node.value === 'boolean') { - type = this.resolveType('bool', node)!; - } else if (typeof node.value === 'bigint') { - type = this.resolveType('i32', node)!; - } else { - throw new Error(`Could not derive type of constant expression.`); - } - assert(type !== null); - return type; + return node.value.getType(); + //if (typeof node.value === 'string') { + // type = this.resolveTypeName('String', node)!; + //} else if (typeof node.value === 'boolean') { + // type = this.resolveTypeName('bool', node)!; + //} else if (typeof node.value === 'bigint') { + // type = this.resolveTypeName('i32', node)!; + //} else { + // throw new Error(`Could not derive type of constant expression.`); + //} + //assert(type !== null); + //return type; } case SyntaxKind.BoltMatchExpression: { @@ -526,6 +453,15 @@ export class TypeChecker { } } + private getTypeOfNode(node: BoltSyntax): Type { + if (node._type !== undefined) { + return node._type; + } + const type = this.createType(node); + node._type = type; + return type; + } + private isTypeAssignableTo(left: Type, right: Type): boolean { if (isOpaqueType(left) && isOpaqueType(right)) { return left === right; @@ -574,43 +510,316 @@ export class TypeChecker { } public registerSourceFile(node: BoltSourceFile): void { - this.addAllSymbolsInNode(node); - } - private addAllSymbolsInNode(node: BoltSyntax): void { + const self = this; - switch (node.kind) { + addAllSymbolsToScope( + node, + this.getScopeForNode(node, SymbolKind.Variable), + this.getScopeForNode(node, SymbolKind.Type), + this.getScopeForNode(node, SymbolKind.Module) + ); - case SyntaxKind.BoltSourceFile: - case SyntaxKind.BoltModule: - { - for (const element of node.elements) { - this.addAllSymbolsInNode(element); + function addAllSymbolsToScope(node: BoltSyntax, variableScope: ScopeInfo, typeScope: ScopeInfo, moduleScope: ScopeInfo, allowDuplicates = false): void { + + switch (node.kind) { + + case SyntaxKind.BoltImportDirective: + { + if (node.symbols !== null) { + for (const importSymbol of node.symbols) { + // TODO + } + } else { + const sourceFile = self.program.resolveToSourceFile(node.file.value, node) as BoltSourceFile; + if (sourceFile === null) { + self.diagnostics.add({ + severity: 'error', + message: E_FILE_NOT_FOUND, + args: { filename: node.file.value }, + node: node.file, + }); + } else { + for (const exportedNode of self.getAllExportedNodes(sourceFile)) { + addAllSymbolsToScope(exportedNode, variableScope, typeScope, moduleScope, true); + } + } + } + break; } - break; - } - case SyntaxKind.BoltFunctionDeclaration: - { - this.varResolver.addSymbol(emit(node.name), node); - break; - } + case SyntaxKind.BoltSourceFile: + case SyntaxKind.BoltModule: + { + for (const element of node.elements) { + addAllSymbolsToScope(element, variableScope, typeScope, moduleScope); + } + break; + } + + case SyntaxKind.BoltFunctionDeclaration: + { + const symbolName = emit(node.name); + const sym = self.lookupSymbolInScope(symbolName, variableScope, SymbolKind.Variable) + if (sym !== null) { + if (!allowDuplicates) { + throw new Error(`Symbol '${name}' is already defined.`); + } + if (sym.declarations.indexOf(node) === -1) { + throw new Error(`Different symbols imported under the same name.`); + } + } else { + self.addSymbolToScope(symbolName, node, variableScope, SymbolKind.Variable); + } + break; + } + + case SyntaxKind.BoltRecordDeclaration: + { + const symbolName = emit(node.name); + const sym = self.lookupSymbolInScope(symbolName, typeScope, SymbolKind.Type) + if (sym !== null) { + if (!allowDuplicates) { + throw new Error(`Symbol '${name}' is already defined.`); + } + if (sym.declarations.indexOf(node) === -1) { + throw new Error(`Different symbols imported under the same name.`); + } + } else { + self.addSymbolToScope(node.name.text, node, typeScope, SymbolKind.Type); + } + break; + } - case SyntaxKind.BoltRecordDeclaration: - { - this.typeResolver.addSymbol(node.name.text, node); } } } + private getAllExportedNodes(node: BoltSyntax): BoltSyntax[] { + + const nodes: BoltSyntax[] = []; + visit(node); + return nodes; + + function visit(node: BoltSyntax) { + if (isBoltDeclaration(node) || isBoltTypeDeclaration(node)) { + if ((node.modifiers & BoltModifiers.IsPublic) > 0) { + nodes.push(node); + } + } + switch (node.kind) { + case SyntaxKind.BoltFunctionDeclaration: + case SyntaxKind.BoltRecordDeclaration: + case SyntaxKind.BoltTypeAliasDeclaration: + nodes.push(node); + break; + case SyntaxKind.BoltModule: + case SyntaxKind.BoltSourceFile: + for (const element of node.elements) { + visit(element); + } + break; + } + } + + } + private resolveReferenceExpression(node: BoltReferenceExpression): BoltDeclaration | null { - return this.varResolver.resolve(createDeclarationPath(node.name), node); + const symbolPath = nodeToSymbolPath(node.name) + return this.resolveSymbolPath(symbolPath, node, SymbolKind.Variable); } private resolveTypeReferenceExpression(node: BoltReferenceTypeExpression): BoltTypeDeclaration | null { - return this.typeResolver.resolve(createDeclarationPath(node.name), node); + const symbolPath = nodeToSymbolPath(node.name); + const scope = this.getScopeForNode(node, SymbolKind.Type); + return this.resolveSymbolPath(symbolPath, scope, SymbolKind.Type); + } + + public addSymbol(name: string, node: BoltSyntax, kind: SymbolKind): void { + const scope = this.getScopeSurroundingNode(node, kind); + this.addSymbolToScope(name, node, scope, kind) + } + + public addSymbolToScope(name: string, node: BoltSyntax, scope: ScopeInfo, symbolKindMask: SymbolKind): void { + verbose(`Adding symbol ${name} in scope #${scope.id}`); + const sym = { kind: symbolKindMask, declarations: [ node ] } as SymbolInfo; + for (const symbolKind of getAllSymbolKindsInMask(symbolKindMask)) { + this.symbols.set(`${symbolKind}:${name}:${scope.id}`, sym); + } + } + + public getParentScope(scope: ScopeInfo, kind: SymbolKind): ScopeInfo | null { + + // We might have already calculcated this scope's parent scope before;; + if (scope.parentScope !== undefined) { + return scope.parentScope; + } + + if (isSyntax(scope.declaration)) { + + // Edge case where there are no parent nodes left to traverse + if (scope.declaration.kind === SyntaxKind.BoltSourceFile) { + const pkg = (scope.declaration as BoltSourceFile).package; + return { + id: pkg.id, + declaration: pkg, + } as ScopeInfo; + } + + return this.getScopeForNode(scope.declaration.parentNode!, kind) + } + + // If the declaration was not an AST node, it can only be a package + return null; + } + + public getScopeSurroundingNode(node: Syntax, kind: SymbolKind): ScopeInfo { + assert(node.parentNode !== null); + return this.getScopeForNode(node.parentNode!, kind); + } + + public getScopeForNode(node: BoltSyntax, kind: SymbolKind): ScopeInfo { + + let currNode = node; + + while (true) { + + // We might have created a scope for this node before, + // or saved the relevant scope for efficiency. + if (node._scope !== undefined) { + return node._scope; + } + + // When we've reached a node that introduces a new scope according + // to the rules of the SymbolKind, we may continue. + if (this.introducesNewScope(currNode.kind, kind)) { + break; + } + + assert(currNode.parentNode !== null); + currNode = currNode.parentNode!; + } + + return { + id: currNode.id, + declaration: currNode, + } as ScopeInfo; + } + + private lookupSymbolInScope(name: string, scope: ScopeInfo, symbolKindMask: SymbolKind): SymbolInfo | null { + for (const symbolKind of getAllSymbolKindsInMask(symbolKindMask)) { + const key = `${symbolKind}:${name}:${scope.id}`; + if (this.symbols.has(key)) { + return this.symbols.get(key); + } + } + return null; + } + + private findSymbolInScopeOf(name: string, scope: ScopeInfo, kind: SymbolKind): SymbolInfo | null { + while (true) { + + // Attempt to look up the symbol in the scope that was either passed to this + // method or one of its parents. If we found one, we're done. + const sym = this.lookupSymbolInScope(name, scope, kind); + if (sym !== null) { + return sym; + } + + const parentScope = this.getParentScope(scope, kind); + + // Failing to find a parent scope means that none of the enclosing + // scopes had the given variable. If this is the case, jump to the + // error handling logic. + if (parentScope === null) { + break; + } + + scope = parentScope; + } + + return null; + } + + public resolveSymbolPath(path: SymbolPath, scope: ScopeInfo, kind: SymbolKind): BoltSyntax | null { + + if (path.hasParents()) { + + if (path.isAbsolute) { + + // TODO + + } else { + + // We will keep looping until we are at the topmost module of + // the package corresponding to `node`. + while (true) { + + let shouldSearchParentScopes = false; + let currScope = scope; + + // Go through each of the parent names in normal order, resolving to the module + // that declared the name, and mark when we failed to look up the inner module. + for (const name of path.getParents()) { + const sym = this.lookupSymbolInScope(name, currScope, SymbolKind.Module); + if (sym === null) { + shouldSearchParentScopes = true; + break; + } + if (sym.declarations[0].kind !== SyntaxKind.BoltModule) { + shouldSearchParentScopes = true; + break; + } + currScope = this.getScopeForNode(sym.declarations[0], SymbolKind.Module); + } + + // If the previous loop did not fail, we are done. + if (!shouldSearchParentScopes) { + scope = currScope; + break; + } + + // We continue the outer loop by getting the parent module, which should be + // equivalent to getting the parent module scope. + const parentScope = this.getParentScope(scope, SymbolKind.Module); + if (parentScope === null) { + return null; + } + scope = parentScope; + } + + } + + } + + // Once we've handled any module path that might have been present, + // we resolve the actual symbol using a helper method. + + const sym = this.findSymbolInScopeOf(path.name, scope, kind); + + if (sym === null) { + return null; + } + + return sym.declarations[0]!; + } + + private introducesNewScope(nodeKind: SyntaxKind, symbolKind: SymbolKind) { + switch (symbolKind) { + case SymbolKind.Variable: + return nodeKind === SyntaxKind.BoltSourceFile + || nodeKind === SyntaxKind.BoltModule + || nodeKind === SyntaxKind.BoltFunctionDeclaration + || nodeKind === SyntaxKind.BoltBlockExpression; + case SymbolKind.Type: + return nodeKind === SyntaxKind.BoltModule + || nodeKind === SyntaxKind.BoltSourceFile; + case SymbolKind.Module: + return nodeKind === SyntaxKind.BoltModule + || nodeKind === SyntaxKind.BoltSourceFile; + } } } diff --git a/src/common.ts b/src/common.ts index 549bced46..623ec85e2 100644 --- a/src/common.ts +++ b/src/common.ts @@ -10,16 +10,37 @@ import { isBoltPunctuated, SourceFile, BoltSourceFile, - BoltSourceFileModifiers + BoltSourceFileModifiers, + isSourceFile } from "./ast"; import { BOLT_SUPPORTED_LANGUAGES } from "./constants" import {emit} from "./emitter"; -import {FastStringMap, enumerate, escapeChar} from "./util"; +import {FastStringMap, enumerate, escapeChar, assert} from "./util"; import {TextSpan, TextPos, TextFile} from "./text"; import {Scanner} from "./scanner"; +export function getSourceFile(node: Syntax) { + while (true) { + if (isSourceFile(node)) { + return node + } + assert(node.parentNode !== null); + node = node.parentNode; + } +} + +export function getPackage(node: Syntax) { + const sourceFile = getSourceFile(node); + assert(sourceFile.kind === SyntaxKind.BoltSourceFile); + return (sourceFile as BoltSourceFile).package; +} + +let nextPackageId = 1; + export class Package { + public id = nextPackageId++; + constructor( public rootDir: string, public name: string | null, @@ -328,37 +349,3 @@ export function describeKind(kind: SyntaxKind): string { } } -export type DeclarationPath = unknown; - -type DeclarationPathInfo = { - modulePath: string[], - isAbsolute: boolean, - name: string -}; - -export function getModulePath(path: DeclarationPath): string[] { - return (path as DeclarationPathInfo).modulePath; -} - -export function hasAbsoluteModulePath(path: DeclarationPath): boolean { - return (path as DeclarationPathInfo).modulePath.length > 0 - && (path as DeclarationPathInfo).isAbsolute; -} - -export function hasRelativeModulePath(path: DeclarationPath): boolean { - return (path as DeclarationPathInfo).modulePath.length > 0 - && !(path as DeclarationPathInfo).isAbsolute; -} - -export function getSymbolNameOfDeclarationPath(path: DeclarationPath): string { - return (path as DeclarationPathInfo).name; -} - -export function createDeclarationPath(node: BoltQualName): DeclarationPath { - const name = emit(node.name); - if (node.modulePath === null) { - return { modulePath: [], isAbsolute: false, name }; - } - return { modulePath: node.modulePath.map(id => id.text), isAbsolute: false, name }; -} - diff --git a/src/diagnostics.ts b/src/diagnostics.ts index ceeee21fc..660d6a842 100644 --- a/src/diagnostics.ts +++ b/src/diagnostics.ts @@ -1,8 +1,9 @@ import chalk from "chalk" import {Syntax} from "./ast"; -import {format, MapLike, FormatArg} from "./util"; +import {format, MapLike, FormatArg, countDigits} from "./util"; +export const E_FILE_NOT_FOUND = "A file named {filename} was not found."; export const E_FIELD_HAS_INVALID_VERSION_NUMBER = "Field '{name}' contains an invalid version nunmber." export const E_FIELD_MUST_BE_STRING = "Field '{name}' must be a string." export const E_FIELD_NOT_PRESENT = "Field '{name}' is not present." @@ -22,13 +23,6 @@ export interface Diagnostic { node?: Syntax; } -export function countDigits(num: number) { - if (num === 0) { - return 1 - } - return Math.ceil(Math.log10(num+1)) -} - function firstIndexOfNonEmpty(str: string) { let j = 0; for (; j < str.length; j++) { diff --git a/src/frontend.ts b/src/frontend.ts index b5a5009f1..7954bd9cc 100644 --- a/src/frontend.ts +++ b/src/frontend.ts @@ -70,11 +70,7 @@ export class Frontend { constructor() { this.diagnostics = new DiagnosticPrinter(); - this.checker = new TypeChecker(this.diagnostics); - this.evaluator = new Evaluator(this.checker); this.timing = new Timing(); - this.container.bindSelf(this.evaluator); - this.container.bindSelf(this.checker); } @memoize @@ -89,16 +85,25 @@ export class Frontend { } public typeCheck(program: Program) { + const checker = new TypeChecker(this.diagnostics, program); for (const sourceFile of program.getAllSourceFiles()) { - this.checker.registerSourceFile(sourceFile as BoltSourceFile); + checker.registerSourceFile(sourceFile as BoltSourceFile); } for (const sourceFile of program.getAllSourceFiles()) { - this.checker.checkSourceFile(sourceFile as BoltSourceFile); + checker.checkSourceFile(sourceFile as BoltSourceFile); } } public compile(program: Program, target: string) { + // FIXME type checker should be shared across multple different method invocations + const checker = new TypeChecker(this.diagnostics, program); + + const container = new Container(); + + //container.bindSelf(evaluator); + container.bindSelf(checker); + switch (target) { case "JS": diff --git a/src/parser.ts b/src/parser.ts index 222260672..e0ff16454 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -337,15 +337,14 @@ export class Parser { const t1 = tokens.get(); assertToken(t1, SyntaxKind.BoltStringLiteral); - const filename = (t1 as BoltStringLiteral).value; - const symbols: BoltImportSymbol[] = []; + const symbols: BoltImportSymbol[] = null; const t2 = tokens.get(); if (t2.kind === SyntaxKind.BoltParenthesized) { // TODO implement grammar and parsing logic for symbols } - const node = createBoltImportDirective(modifiers, filename, symbols); + const node = createBoltImportDirective(modifiers, t1 as BoltStringLiteral, symbols); setOrigNodeRange(node, t0, t1); return node; } diff --git a/src/program.ts b/src/program.ts index df1d2b19d..d88157983 100644 --- a/src/program.ts +++ b/src/program.ts @@ -1,32 +1,70 @@ -import { Package } from "./common" -import { SourceFile } from "./ast" -import { FastStringMap } from "./util"; +import * as path from "path" +import { Package, getPackage } from "./common" +import { SourceFile, Syntax } from "./ast" +import { FastStringMap, assert, isInsideDirectory, stripExtensions } from "./util"; export class Program { - private transformed = new FastStringMap(); + private packagesByName = new FastStringMap(); + + private sourceFilesByFilePath = new FastStringMap(); constructor( pkgs: Package[] ) { for (const pkg of pkgs) { for (const sourceFile of pkg.sourceFiles) { - this.transformed.set(sourceFile.span!.file.fullPath, sourceFile); + this.sourceFilesByFilePath.set(stripExtensions(sourceFile.span!.file.fullPath), sourceFile); } } } public getAllSourceFiles() { - return this.transformed.values(); + return this.sourceFilesByFilePath.values(); + } + + public getSourceFile(filepath: string): SourceFile | null { + assert(path.isAbsolute(filepath)); + if (!this.sourceFilesByFilePath.has(filepath)) { + return null; + } + return this.sourceFilesByFilePath.get(filepath); + } + + public getPackageNamed(name: string): Package { + return this.packagesByName.get(name); + } + + public resolveToSourceFile(importPath: string, fromNode: Syntax): SourceFile | null { + let resolvedFilePath: string; + if (importPath.startsWith('.')) { + const pkg = getPackage(fromNode); + resolvedFilePath = path.join(pkg.rootDir, importPath.substring(2)); + assert(isInsideDirectory(resolvedFilePath, pkg.rootDir)); + } else { + const elements = importPath.split('/'); + const pkg = this.getPackageNamed(elements[0]); + let filename: string; + if (elements.length === 1) { + filename = 'lib'; + } else { + assert(elements.length > 0); + assert(!elements.slice(1).some(element => element.startsWith('.'))); + filename = elements.slice(1).join(path.sep); + } + resolvedFilePath = path.join(pkg.rootDir, filename) + assert(isInsideDirectory(resolvedFilePath, pkg.rootDir)); + } + return this.getSourceFile(resolvedFilePath); } public updateSourceFile(oldSourceFile: SourceFile, newSourceFile: SourceFile): void { - if (!this.transformed.has(oldSourceFile.span!.file.fullPath)) { + if (!this.sourceFilesByFilePath.has(oldSourceFile.span!.file.fullPath)) { throw new Error(`Could not update ${oldSourceFile.span!.file.origPath} because it was not found in this program.`); } - this.transformed.delete(oldSourceFile.span!.file.fullPath); - this.transformed.set(newSourceFile.span!.file.fullPath, newSourceFile); + this.sourceFilesByFilePath.delete(oldSourceFile.span!.file.fullPath); + this.sourceFilesByFilePath.set(newSourceFile.span!.file.fullPath, newSourceFile); } } diff --git a/src/treegen/ast.dts.template b/src/treegen/ast.dts.template new file mode 100644 index 000000000..39774c3ed --- /dev/null +++ b/src/treegen/ast.dts.template @@ -0,0 +1,25 @@ + +import { Type } from "./types" +import { ScopeInfo } from "./checker" +import { Package } from "./common" +import { TextSpan } from "./text" + +export function setParents(node: Syntax): void; + +export type SyntaxRange = [Syntax, Syntax]; + +export function isSyntax(value: any): value is Syntax; + +interface SyntaxBase { + id: number; + _scope?: ScopeInfo; + kind: K; + parentNode: ParentTypesOf | null; + span: TextSpan | null; + getChildNodes(): IterableIterator>, + findAllChildrenOfKind(kind: K1): IterableIterator>; +} + +export type ResolveSyntaxKind = Extract; + + diff --git a/src/treegen/index.ts b/src/treegen/index.ts index d1b8d6d12..23b13cedf 100644 --- a/src/treegen/index.ts +++ b/src/treegen/index.ts @@ -98,12 +98,7 @@ export function generateAST(decls: Declaration[]) { // Write corresponding TypeScript declarations - - // FIXME These imports are specific to our project and should somehow - // form part of the user specification. - dtsFile.write('\nimport { Package } from "./common"\n\n'); - - dtsFile.write('export function isSyntax(value: any): value is Syntax;\n\n'); + dtsFile.write(fs.readFileSync(path.join(PACKAGE_ROOT, 'src', 'treegen', 'ast.dts.template'), 'utf8')); dtsFile.write(`\nexport const enum SyntaxKind {\n`); for (const decl of leafNodes) { @@ -111,27 +106,6 @@ export function generateAST(decls: Declaration[]) { } dtsFile.write(`}\n\n`); - dtsFile.write(` - -import { TextSpan } from "./text" - -export function setParents(node: Syntax): void; - -export type SyntaxRange = [Syntax, Syntax]; - -interface SyntaxBase { - id: number; - kind: K; - parentNode: ParentTypesOf | null; - span: TextSpan | null; - getChildNodes(): IterableIterator>, - findAllChildrenOfKind(kind: K1): IterableIterator>; -} - -export type ResolveSyntaxKind = Extract; - -`); - for (const decl of decls) { if (decl.type === 'NodeDeclaration') { if (isLeafNode(decl.name)) { diff --git a/src/util.ts b/src/util.ts index 9ebef12e8..8534ba97a 100644 --- a/src/util.ts +++ b/src/util.ts @@ -14,6 +14,18 @@ export interface JsonArray extends Array { }; export interface JsonObject { [key: string]: Json } export type Json = null | string | boolean | number | JsonArray | JsonObject; +export function isInsideDirectory(filepath: string, rootDir: string): boolean { + const relPath = path.relative(rootDir, filepath) + return !relPath.startsWith('..'); +} + +export function stripExtensions(filepath: string) { + const i = filepath.indexOf('.') + return i !== -1 + ? filepath.substring(0, i) + : filepath; +} + export function isString(value: any): boolean { return typeof value === 'string'; } @@ -22,6 +34,13 @@ export function hasOwnProperty(obj: T, return Object.prototype.hasOwnProperty.call(obj, key); } +export function countDigits(x: number, base: number = 10) { + if (x === 0) { + return 1 + } + return Math.ceil(Math.log(x+1) / Math.log(base)) +} + export function uniq(elements: T[]): T[] { const out: T[] = []; const visited = new Set();