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 {
|
node BoltImportDirective > BoltSourceElement {
|
||||||
modifiers: BoltModifiers,
|
modifiers: BoltModifiers,
|
||||||
file: String,
|
file: BoltStringLiteral,
|
||||||
symbols: Vec<BoltImportSymbol>,
|
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 { 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;
|
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 {
|
export const enum SyntaxKind {
|
||||||
EndOfFile = 2,
|
EndOfFile = 2,
|
||||||
|
@ -158,25 +178,6 @@ export const enum SyntaxKind {
|
||||||
JSSourceFile = 182,
|
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> {
|
export interface EndOfFile extends SyntaxBase<SyntaxKind.EndOfFile> {
|
||||||
kind: SyntaxKind.EndOfFile;
|
kind: SyntaxKind.EndOfFile;
|
||||||
}
|
}
|
||||||
|
@ -824,7 +825,7 @@ export interface BoltPlainImportSymbol extends SyntaxBase<SyntaxKind.BoltPlainIm
|
||||||
export interface BoltImportDirective extends SyntaxBase<SyntaxKind.BoltImportDirective> {
|
export interface BoltImportDirective extends SyntaxBase<SyntaxKind.BoltImportDirective> {
|
||||||
kind: SyntaxKind.BoltImportDirective;
|
kind: SyntaxKind.BoltImportDirective;
|
||||||
modifiers: BoltModifiers;
|
modifiers: BoltModifiers;
|
||||||
file: string;
|
file: BoltStringLiteral;
|
||||||
symbols: BoltImportSymbol[];
|
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 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 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 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 createBoltExportSymbol(span?: TextSpan | null): BoltExportSymbol;
|
||||||
export function createBoltPlainExportSymbol(name: BoltQualName, span?: TextSpan | null): BoltPlainExportSymbol;
|
export function createBoltPlainExportSymbol(name: BoltQualName, span?: TextSpan | null): BoltPlainExportSymbol;
|
||||||
export function createBoltExportDirective(file: string, symbols: BoltExportSymbol[] | null, span?: TextSpan | null): BoltExportDirective;
|
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[]) {
|
function flatMap<T>(array: T[], proc: (element: T) => T[]) {
|
||||||
let out: T[] = []
|
let out: T[] = []
|
||||||
for (const element of array) {
|
for (const element of array) {
|
||||||
|
@ -123,6 +118,7 @@ function loadPackageMetadata(rootDir: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadPackage(rootDir: string): Package {
|
function loadPackage(rootDir: string): Package {
|
||||||
|
rootDir = path.resolve(rootDir);
|
||||||
const data = loadPackageMetadata(rootDir);
|
const data = loadPackageMetadata(rootDir);
|
||||||
const pkg = new Package(rootDir, data.name, data.version, []);
|
const pkg = new Package(rootDir, data.name, data.version, []);
|
||||||
for (const filepath of globSync(path.join(rootDir, '**/*.bolt'))) {
|
for (const filepath of globSync(path.join(rootDir, '**/*.bolt'))) {
|
||||||
|
@ -132,13 +128,14 @@ function loadPackage(rootDir: string): Package {
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadPackagesAndSourceFiles(filenames: string[], cwd = '.'): Package[] {
|
function loadPackagesAndSourceFiles(filenames: string[], cwd = '.'): Package[] {
|
||||||
|
cwd = path.resolve(cwd);
|
||||||
const anonPkg = new Package(cwd, null, null, []);
|
const anonPkg = new Package(cwd, null, null, []);
|
||||||
const pkgs = [ anonPkg ];
|
const pkgs = [ anonPkg ];
|
||||||
for (const filename of filenames) {
|
for (const filename of filenames) {
|
||||||
if (fs.statSync(filename).isDirectory()) {
|
if (fs.statSync(filename).isDirectory()) {
|
||||||
pkgs.push(loadPackage(filename));
|
pkgs.push(loadPackage(filename));
|
||||||
} else {
|
} else {
|
||||||
anonPkg.addSourceFile(parseSourceFile(filename, anonPkg, 0));
|
anonPkg.addSourceFile(parseSourceFile(filename, anonPkg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pkgs;
|
return pkgs;
|
||||||
|
|
533
src/checker.ts
533
src/checker.ts
|
@ -42,9 +42,12 @@ import {
|
||||||
isBoltStatement,
|
isBoltStatement,
|
||||||
isBoltDeclaration,
|
isBoltDeclaration,
|
||||||
isSourceFile,
|
isSourceFile,
|
||||||
BoltReferenceTypeExpression
|
BoltReferenceTypeExpression,
|
||||||
|
isBoltTypeDeclaration,
|
||||||
|
SourceFile,
|
||||||
|
BoltModifiers
|
||||||
} from "./ast";
|
} from "./ast";
|
||||||
import {FastStringMap, memoize, assert, verbose} from "./util";
|
import {FastStringMap, countDigits, assert, verbose} from "./util";
|
||||||
import {
|
import {
|
||||||
DiagnosticPrinter,
|
DiagnosticPrinter,
|
||||||
E_TYPES_NOT_ASSIGNABLE,
|
E_TYPES_NOT_ASSIGNABLE,
|
||||||
|
@ -52,157 +55,90 @@ import {
|
||||||
E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL,
|
E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL,
|
||||||
E_TYPE_DECLARATION_NOT_FOUND,
|
E_TYPE_DECLARATION_NOT_FOUND,
|
||||||
E_DECLARATION_NOT_FOUND,
|
E_DECLARATION_NOT_FOUND,
|
||||||
E_INVALID_ARGUMENTS
|
E_INVALID_ARGUMENTS,
|
||||||
|
E_FILE_NOT_FOUND,
|
||||||
} from "./diagnostics";
|
} from "./diagnostics";
|
||||||
import { createAnyType, isOpaqueType, createOpaqueType, Type, createVoidType, createVariantType, isVoidType } from "./types";
|
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 {emit} from "./emitter";
|
||||||
|
import {Program} from "./program";
|
||||||
|
import {type} from "os";
|
||||||
|
|
||||||
const PACKAGE_SCOPE_ID = 0;
|
// TODO
|
||||||
|
const GLOBAL_SCOPE_ID = 0;
|
||||||
|
|
||||||
|
class SymbolPath {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private parents: string[],
|
||||||
|
public isAbsolute: boolean,
|
||||||
|
public name: string
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasParents(): boolean {
|
||||||
|
return this.parents.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getParents() {
|
||||||
|
return this.parents;
|
||||||
|
}
|
||||||
|
|
||||||
interface SymbolInfo<N extends Syntax> {
|
|
||||||
declarations: N[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function introducesNewScope(kind: SyntaxKind): boolean {
|
function nodeToSymbolPath(node: BoltSyntax): SymbolPath {
|
||||||
return kind === SyntaxKind.BoltSourceFile
|
switch (node.kind) {
|
||||||
|| kind === SyntaxKind.BoltModule
|
case SyntaxKind.BoltIdentifier:
|
||||||
|| kind === SyntaxKind.BoltFunctionDeclaration
|
return new SymbolPath([], false, emit(node));
|
||||||
|| kind === SyntaxKind.BoltBlockExpression;
|
case SyntaxKind.BoltQualName:
|
||||||
|
const name = emit(node.name);
|
||||||
|
if (node.modulePath === null) {
|
||||||
|
return new SymbolPath([], false, name);
|
||||||
|
}
|
||||||
|
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.`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function introducesNewTypeScope(kind: SyntaxKind): boolean {
|
enum SymbolKind {
|
||||||
return kind === SyntaxKind.BoltModule
|
Type = 0x1,
|
||||||
|| kind === SyntaxKind.BoltSourceFile;
|
Variable = 0x2,
|
||||||
|
Module = 0x4,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Scope = unknown;
|
function* getAllSymbolKindsInMask(symbolKindMask: SymbolKind) {
|
||||||
type TypeScope = unknown;
|
const n = countDigits(symbolKindMask, 2);
|
||||||
|
for (let i = 1; i <= n; i++) {
|
||||||
function getScopeId(scope: Scope | TypeScope) {
|
if ((symbolKindMask & i) > 0) {
|
||||||
if (isSyntax(scope)) {
|
yield i;
|
||||||
return scope.id;
|
}
|
||||||
}
|
}
|
||||||
return PACKAGE_SCOPE_ID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSymbol(node: BoltDeclaration): SymbolInfo {
|
export interface ScopeInfo {
|
||||||
return { declarations: [ node ] };
|
id: number;
|
||||||
|
declaration: BoltSyntax | Package;
|
||||||
|
parentScope?: ScopeInfo;
|
||||||
|
kind: SymbolKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
class SymbolResolver<N extends Syntax> {
|
interface SymbolInfo {
|
||||||
|
kind: SymbolKind;
|
||||||
private symbols = new FastStringMap<string, SymbolInfo<N>>();
|
declarations: BoltSyntax[];
|
||||||
|
|
||||||
constructor(private introducesNewScope: (kind: SyntaxKind) => boolean) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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 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;
|
|
||||||
}
|
|
||||||
currNode = currNode.parentNode!;
|
|
||||||
}
|
|
||||||
return currNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private lookupSymbolInScope(name: string, scope: Scope): SymbolInfo<N> | null {
|
|
||||||
const key = `${name}@${getScopeId(scope)}`;
|
|
||||||
if (!this.symbols.has(key)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
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 class TypeChecker {
|
export class TypeChecker {
|
||||||
|
|
||||||
constructor(private diagnostics: DiagnosticPrinter) {
|
constructor(
|
||||||
|
private diagnostics: DiagnosticPrinter,
|
||||||
|
private program: Program
|
||||||
|
) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private varResolver = new SymbolResolver<BoltDeclaration>(introducesNewScope);
|
private symbols = new FastStringMap<string, SymbolInfo>();
|
||||||
private typeResolver = new SymbolResolver<BoltTypeDeclaration>(introducesNewTypeScope);
|
|
||||||
|
|
||||||
public checkSourceFile(node: BoltSourceFile): void {
|
public checkSourceFile(node: BoltSourceFile): void {
|
||||||
|
|
||||||
|
@ -314,14 +250,6 @@ export class TypeChecker {
|
||||||
|
|
||||||
switch (node.kind) {
|
switch (node.kind) {
|
||||||
|
|
||||||
case SyntaxKind.BoltModule:
|
|
||||||
{
|
|
||||||
for (const element of node.elements) {
|
|
||||||
visitSourceElement(element);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case SyntaxKind.BoltRecordDeclaration:
|
case SyntaxKind.BoltRecordDeclaration:
|
||||||
{
|
{
|
||||||
if (node.members !== null) {
|
if (node.members !== null) {
|
||||||
|
@ -460,16 +388,15 @@ export class TypeChecker {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private resolveType(name: string, node: BoltSyntax): Type | null {
|
private resolveTypeName(name: string, node: BoltSyntax): Type | null {
|
||||||
const sym = this.typeResolver.findSymbolInScopeOf(name, this.typeResolver.getScopeSurroundingNode(node))
|
const sym = this.findSymbolInScopeOf(name, this.getScopeSurroundingNode(node));
|
||||||
if (sym === null) {
|
if (sym === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return this.getTypeOfNode(sym.declarations[0]);
|
return this.getTypeOfNode(sym.declarations[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@memoize(node => node.id)
|
private createType(node: BoltSyntax): Type {
|
||||||
private getTypeOfNode(node: BoltSyntax): Type {
|
|
||||||
switch (node.kind) {
|
switch (node.kind) {
|
||||||
case SyntaxKind.BoltReferenceTypeExpression:
|
case SyntaxKind.BoltReferenceTypeExpression:
|
||||||
{
|
{
|
||||||
|
@ -504,18 +431,18 @@ export class TypeChecker {
|
||||||
}
|
}
|
||||||
case SyntaxKind.BoltConstantExpression:
|
case SyntaxKind.BoltConstantExpression:
|
||||||
{
|
{
|
||||||
let type;
|
return node.value.getType();
|
||||||
if (typeof node.value === 'string') {
|
//if (typeof node.value === 'string') {
|
||||||
type = this.resolveType('String', node)!;
|
// type = this.resolveTypeName('String', node)!;
|
||||||
} else if (typeof node.value === 'boolean') {
|
//} else if (typeof node.value === 'boolean') {
|
||||||
type = this.resolveType('bool', node)!;
|
// type = this.resolveTypeName('bool', node)!;
|
||||||
} else if (typeof node.value === 'bigint') {
|
//} else if (typeof node.value === 'bigint') {
|
||||||
type = this.resolveType('i32', node)!;
|
// type = this.resolveTypeName('i32', node)!;
|
||||||
} else {
|
//} else {
|
||||||
throw new Error(`Could not derive type of constant expression.`);
|
// throw new Error(`Could not derive type of constant expression.`);
|
||||||
}
|
//}
|
||||||
assert(type !== null);
|
//assert(type !== null);
|
||||||
return type;
|
//return type;
|
||||||
}
|
}
|
||||||
case SyntaxKind.BoltMatchExpression:
|
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 {
|
private isTypeAssignableTo(left: Type, right: Type): boolean {
|
||||||
if (isOpaqueType(left) && isOpaqueType(right)) {
|
if (isOpaqueType(left) && isOpaqueType(right)) {
|
||||||
return left === right;
|
return left === right;
|
||||||
|
@ -574,43 +510,316 @@ export class TypeChecker {
|
||||||
}
|
}
|
||||||
|
|
||||||
public registerSourceFile(node: BoltSourceFile): void {
|
public registerSourceFile(node: BoltSourceFile): void {
|
||||||
this.addAllSymbolsInNode(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
private addAllSymbolsInNode(node: BoltSyntax): void {
|
const self = this;
|
||||||
|
|
||||||
|
addAllSymbolsToScope(
|
||||||
|
node,
|
||||||
|
this.getScopeForNode(node, SymbolKind.Variable),
|
||||||
|
this.getScopeForNode(node, SymbolKind.Type),
|
||||||
|
this.getScopeForNode(node, SymbolKind.Module)
|
||||||
|
);
|
||||||
|
|
||||||
|
function addAllSymbolsToScope(node: BoltSyntax, variableScope: ScopeInfo, typeScope: ScopeInfo, moduleScope: ScopeInfo, allowDuplicates = false): void {
|
||||||
|
|
||||||
switch (node.kind) {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
case SyntaxKind.BoltSourceFile:
|
case SyntaxKind.BoltSourceFile:
|
||||||
case SyntaxKind.BoltModule:
|
case SyntaxKind.BoltModule:
|
||||||
{
|
{
|
||||||
for (const element of node.elements) {
|
for (const element of node.elements) {
|
||||||
this.addAllSymbolsInNode(element);
|
addAllSymbolsToScope(element, variableScope, typeScope, moduleScope);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case SyntaxKind.BoltFunctionDeclaration:
|
case SyntaxKind.BoltFunctionDeclaration:
|
||||||
{
|
{
|
||||||
this.varResolver.addSymbol(emit(node.name), node);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case SyntaxKind.BoltRecordDeclaration:
|
case SyntaxKind.BoltRecordDeclaration:
|
||||||
{
|
{
|
||||||
this.typeResolver.addSymbol(node.name.text, node);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
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 {
|
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,
|
isBoltPunctuated,
|
||||||
SourceFile,
|
SourceFile,
|
||||||
BoltSourceFile,
|
BoltSourceFile,
|
||||||
BoltSourceFileModifiers
|
BoltSourceFileModifiers,
|
||||||
|
isSourceFile
|
||||||
} from "./ast";
|
} from "./ast";
|
||||||
import { BOLT_SUPPORTED_LANGUAGES } from "./constants"
|
import { BOLT_SUPPORTED_LANGUAGES } from "./constants"
|
||||||
import {emit} from "./emitter";
|
import {emit} from "./emitter";
|
||||||
import {FastStringMap, enumerate, escapeChar} from "./util";
|
import {FastStringMap, enumerate, escapeChar, assert} from "./util";
|
||||||
import {TextSpan, TextPos, TextFile} from "./text";
|
import {TextSpan, TextPos, TextFile} from "./text";
|
||||||
import {Scanner} from "./scanner";
|
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 {
|
export class Package {
|
||||||
|
|
||||||
|
public id = nextPackageId++;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public rootDir: string,
|
public rootDir: string,
|
||||||
public name: string | null,
|
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 chalk from "chalk"
|
||||||
import {Syntax} from "./ast";
|
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_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_MUST_BE_STRING = "Field '{name}' must be a string."
|
||||||
export const E_FIELD_NOT_PRESENT = "Field '{name}' is not present."
|
export const E_FIELD_NOT_PRESENT = "Field '{name}' is not present."
|
||||||
|
@ -22,13 +23,6 @@ export interface Diagnostic {
|
||||||
node?: Syntax;
|
node?: Syntax;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function countDigits(num: number) {
|
|
||||||
if (num === 0) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return Math.ceil(Math.log10(num+1))
|
|
||||||
}
|
|
||||||
|
|
||||||
function firstIndexOfNonEmpty(str: string) {
|
function firstIndexOfNonEmpty(str: string) {
|
||||||
let j = 0;
|
let j = 0;
|
||||||
for (; j < str.length; j++) {
|
for (; j < str.length; j++) {
|
||||||
|
|
|
@ -70,11 +70,7 @@ export class Frontend {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.diagnostics = new DiagnosticPrinter();
|
this.diagnostics = new DiagnosticPrinter();
|
||||||
this.checker = new TypeChecker(this.diagnostics);
|
|
||||||
this.evaluator = new Evaluator(this.checker);
|
|
||||||
this.timing = new Timing();
|
this.timing = new Timing();
|
||||||
this.container.bindSelf(this.evaluator);
|
|
||||||
this.container.bindSelf(this.checker);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@memoize
|
@memoize
|
||||||
|
@ -89,16 +85,25 @@ export class Frontend {
|
||||||
}
|
}
|
||||||
|
|
||||||
public typeCheck(program: Program) {
|
public typeCheck(program: Program) {
|
||||||
|
const checker = new TypeChecker(this.diagnostics, program);
|
||||||
for (const sourceFile of program.getAllSourceFiles()) {
|
for (const sourceFile of program.getAllSourceFiles()) {
|
||||||
this.checker.registerSourceFile(sourceFile as BoltSourceFile);
|
checker.registerSourceFile(sourceFile as BoltSourceFile);
|
||||||
}
|
}
|
||||||
for (const sourceFile of program.getAllSourceFiles()) {
|
for (const sourceFile of program.getAllSourceFiles()) {
|
||||||
this.checker.checkSourceFile(sourceFile as BoltSourceFile);
|
checker.checkSourceFile(sourceFile as BoltSourceFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public compile(program: Program, target: string) {
|
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) {
|
switch (target) {
|
||||||
|
|
||||||
case "JS":
|
case "JS":
|
||||||
|
|
|
@ -337,15 +337,14 @@ export class Parser {
|
||||||
|
|
||||||
const t1 = tokens.get();
|
const t1 = tokens.get();
|
||||||
assertToken(t1, SyntaxKind.BoltStringLiteral);
|
assertToken(t1, SyntaxKind.BoltStringLiteral);
|
||||||
const filename = (t1 as BoltStringLiteral).value;
|
|
||||||
|
|
||||||
const symbols: BoltImportSymbol[] = [];
|
const symbols: BoltImportSymbol[] = null;
|
||||||
const t2 = tokens.get();
|
const t2 = tokens.get();
|
||||||
if (t2.kind === SyntaxKind.BoltParenthesized) {
|
if (t2.kind === SyntaxKind.BoltParenthesized) {
|
||||||
// TODO implement grammar and parsing logic for symbols
|
// 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);
|
setOrigNodeRange(node, t0, t1);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,70 @@
|
||||||
|
|
||||||
import { Package } from "./common"
|
import * as path from "path"
|
||||||
import { SourceFile } from "./ast"
|
import { Package, getPackage } from "./common"
|
||||||
import { FastStringMap } from "./util";
|
import { SourceFile, Syntax } from "./ast"
|
||||||
|
import { FastStringMap, assert, isInsideDirectory, stripExtensions } from "./util";
|
||||||
|
|
||||||
export class Program {
|
export class Program {
|
||||||
|
|
||||||
private transformed = new FastStringMap<string, SourceFile>();
|
private packagesByName = new FastStringMap<string, Package>();
|
||||||
|
|
||||||
|
private sourceFilesByFilePath = new FastStringMap<string, SourceFile>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
pkgs: Package[]
|
pkgs: Package[]
|
||||||
) {
|
) {
|
||||||
for (const pkg of pkgs) {
|
for (const pkg of pkgs) {
|
||||||
for (const sourceFile of pkg.sourceFiles) {
|
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() {
|
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 {
|
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.`);
|
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.sourceFilesByFilePath.delete(oldSourceFile.span!.file.fullPath);
|
||||||
this.transformed.set(newSourceFile.span!.file.fullPath, newSourceFile);
|
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
|
// Write corresponding TypeScript declarations
|
||||||
|
|
||||||
|
dtsFile.write(fs.readFileSync(path.join(PACKAGE_ROOT, 'src', 'treegen', 'ast.dts.template'), 'utf8'));
|
||||||
// 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(`\nexport const enum SyntaxKind {\n`);
|
dtsFile.write(`\nexport const enum SyntaxKind {\n`);
|
||||||
for (const decl of leafNodes) {
|
for (const decl of leafNodes) {
|
||||||
|
@ -111,27 +106,6 @@ export function generateAST(decls: Declaration[]) {
|
||||||
}
|
}
|
||||||
dtsFile.write(`}\n\n`);
|
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) {
|
for (const decl of decls) {
|
||||||
if (decl.type === 'NodeDeclaration') {
|
if (decl.type === 'NodeDeclaration') {
|
||||||
if (isLeafNode(decl.name)) {
|
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 interface JsonObject { [key: string]: Json }
|
||||||
export type Json = null | string | boolean | number | JsonArray | JsonObject;
|
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 {
|
export function isString(value: any): boolean {
|
||||||
return typeof value === 'string';
|
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);
|
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[] {
|
export function uniq<T>(elements: T[]): T[] {
|
||||||
const out: T[] = [];
|
const out: T[] = [];
|
||||||
const visited = new Set<T>();
|
const visited = new Set<T>();
|
||||||
|
|
Loading…
Reference in a new issue