Improve symbol resolution algorithms in type checker
This commit is contained in:
parent
17d91ef36c
commit
e0566233c0
12 changed files with 535 additions and 287 deletions
|
@ -277,7 +277,7 @@ node BoltPlainImportSymbol > BoltImportSymbol {
|
|||
|
||||
node BoltImportDirective > BoltSourceElement {
|
||||
modifiers: BoltModifiers,
|
||||
file: String,
|
||||
file: BoltStringLiteral,
|
||||
symbols: Vec<BoltImportSymbol>,
|
||||
}
|
||||
|
||||
|
|
43
src/ast.d.ts
vendored
43
src/ast.d.ts
vendored
|
@ -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<K extends SyntaxKind> {
|
||||
id: number;
|
||||
_scope?: ScopeInfo;
|
||||
kind: K;
|
||||
parentNode: ParentTypesOf<K> | null;
|
||||
span: TextSpan | null;
|
||||
getChildNodes(): IterableIterator<ChildTypesOf<K>>,
|
||||
findAllChildrenOfKind<K1 extends SyntaxKind>(kind: K1): IterableIterator<ResolveSyntaxKind<K1>>;
|
||||
}
|
||||
|
||||
export type ResolveSyntaxKind<K extends SyntaxKind> = Extract<Syntax, { kind: K }>;
|
||||
|
||||
|
||||
|
||||
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<K extends SyntaxKind> {
|
||||
id: number;
|
||||
kind: K;
|
||||
parentNode: ParentTypesOf<K> | null;
|
||||
span: TextSpan | null;
|
||||
getChildNodes(): IterableIterator<ChildTypesOf<K>>,
|
||||
findAllChildrenOfKind<K1 extends SyntaxKind>(kind: K1): IterableIterator<ResolveSyntaxKind<K1>>;
|
||||
}
|
||||
|
||||
export type ResolveSyntaxKind<K extends SyntaxKind> = Extract<Syntax, { kind: K }>;
|
||||
|
||||
export interface EndOfFile extends SyntaxBase<SyntaxKind.EndOfFile> {
|
||||
kind: SyntaxKind.EndOfFile;
|
||||
}
|
||||
|
@ -824,7 +825,7 @@ export interface BoltPlainImportSymbol extends SyntaxBase<SyntaxKind.BoltPlainIm
|
|||
export interface BoltImportDirective extends SyntaxBase<SyntaxKind.BoltImportDirective> {
|
||||
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;
|
||||
|
|
|
@ -36,11 +36,6 @@ function pushAll<T>(array: T[], elements: T[]) {
|
|||
}
|
||||
}
|
||||
|
||||
function stripExtension(filepath: string) {
|
||||
const i = filepath.lastIndexOf('.');
|
||||
return i !== -1 ? filepath.substring(0, i) : filepath
|
||||
}
|
||||
|
||||
function flatMap<T>(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;
|
||||
|
|
549
src/checker.ts
549
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<N extends Syntax> {
|
||||
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<N extends Syntax> {
|
||||
|
||||
private symbols = new FastStringMap<string, SymbolInfo<N>>();
|
||||
|
||||
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<N> | 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<N> | 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<BoltDeclaration>(introducesNewScope);
|
||||
private typeResolver = new SymbolResolver<BoltTypeDeclaration>(introducesNewTypeScope);
|
||||
private symbols = new FastStringMap<string, SymbolInfo>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<string, SourceFile>();
|
||||
private packagesByName = new FastStringMap<string, Package>();
|
||||
|
||||
private sourceFilesByFilePath = new FastStringMap<string, SourceFile>();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
25
src/treegen/ast.dts.template
Normal file
25
src/treegen/ast.dts.template
Normal file
|
@ -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<K extends SyntaxKind> {
|
||||
id: number;
|
||||
_scope?: ScopeInfo;
|
||||
kind: K;
|
||||
parentNode: ParentTypesOf<K> | null;
|
||||
span: TextSpan | null;
|
||||
getChildNodes(): IterableIterator<ChildTypesOf<K>>,
|
||||
findAllChildrenOfKind<K1 extends SyntaxKind>(kind: K1): IterableIterator<ResolveSyntaxKind<K1>>;
|
||||
}
|
||||
|
||||
export type ResolveSyntaxKind<K extends SyntaxKind> = Extract<Syntax, { kind: K }>;
|
||||
|
||||
|
|
@ -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<K extends SyntaxKind> {
|
||||
id: number;
|
||||
kind: K;
|
||||
parentNode: ParentTypesOf<K> | null;
|
||||
span: TextSpan | null;
|
||||
getChildNodes(): IterableIterator<ChildTypesOf<K>>,
|
||||
findAllChildrenOfKind<K1 extends SyntaxKind>(kind: K1): IterableIterator<ResolveSyntaxKind<K1>>;
|
||||
}
|
||||
|
||||
export type ResolveSyntaxKind<K extends SyntaxKind> = Extract<Syntax, { kind: K }>;
|
||||
|
||||
`);
|
||||
|
||||
for (const decl of decls) {
|
||||
if (decl.type === 'NodeDeclaration') {
|
||||
if (isLeafNode(decl.name)) {
|
||||
|
|
19
src/util.ts
19
src/util.ts
|
@ -14,6 +14,18 @@ export interface JsonArray extends Array<Json> { };
|
|||
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<T extends object, K extends PropertyKey>(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<T>(elements: T[]): T[] {
|
||||
const out: T[] = [];
|
||||
const visited = new Set<T>();
|
||||
|
|
Loading…
Reference in a new issue