Major update to code base

- Extended and fixed some issues in the parser
 - Complete rewrite of TypeChecker as a SymbolResolver
 - Created basic structure for a new class TypeChecker
 - Introduces verification passes
 - Updated frontend and all bits depending on it
This commit is contained in:
Sam Vervaeck 2020-05-26 21:01:38 +02:00
parent ae41362367
commit ffb0e2a8c0
17 changed files with 6638 additions and 1484 deletions

View file

@ -2,7 +2,7 @@
TREEGEN_FILES = src/ast-spec.txt lib/bin/bolt-treegen.js lib/treegen/parser.js lib/treegen/index.js lib/treegen/util.js src/treegen/ast-template.js TREEGEN_FILES = src/ast-spec.txt lib/bin/bolt-treegen.js lib/treegen/parser.js lib/treegen/index.js lib/treegen/util.js src/treegen/ast-template.js
all: lib/ast.js all: lib/ast.js
bolt bundle stdlib bolt check stdlib
lib/ast.js: $(TREEGEN_FILES) lib/ast.js: $(TREEGEN_FILES)
@echo "Generating AST definitions ..." @echo "Generating AST definitions ..."

View file

@ -92,7 +92,6 @@ node BoltSourceFile > SourceFile {
node BoltQualName { node BoltQualName {
modulePath: Option<Vec<BoltIdentifier>>, modulePath: Option<Vec<BoltIdentifier>>,
name: BoltSymbol,
} }
node BoltModulePath { node BoltModulePath {
@ -156,11 +155,16 @@ node BoltRecordPattern > BoltPattern {
node BoltExpression; node BoltExpression;
node BoltQuoteExpression > BoltExpression { node BoltQuoteExpression > BoltExpression {
tokens: Vec<Token>, tokens: Vec<Token | BoltExpression>,
}
node BoltTupleExpression > BoltExpression {
elements: Vec<BoltExpression>,
} }
node BoltReferenceExpression > BoltExpression { node BoltReferenceExpression > BoltExpression {
name: BoltQualName, modulePath: Option<BoltModulePath>,
name: BoltSymbol,
} }
node BoltMemberExpression > BoltExpression { node BoltMemberExpression > BoltExpression {

5968
src/ast.d.ts vendored

File diff suppressed because it is too large Load diff

View file

@ -17,7 +17,6 @@ import { Package } from "../common"
import {hasOwnProperty} from "../util" import {hasOwnProperty} from "../util"
import {isString} from "util" import {isString} from "util"
import {DiagnosticPrinter, E_FIELD_NOT_PRESENT, E_FIELD_MUST_BE_STRING, E_FIELD_HAS_INVALID_VERSION_NUMBER} from "../diagnostics" import {DiagnosticPrinter, E_FIELD_NOT_PRESENT, E_FIELD_MUST_BE_STRING, E_FIELD_HAS_INVALID_VERSION_NUMBER} from "../diagnostics"
import {BoltSourceFileModifiers} from "../ast"
//global.print = function (value: any) { //global.print = function (value: any) {
// console.error(require('util').inspect(value, { depth: Infinity, colors: true })) // console.error(require('util').inspect(value, { depth: Infinity, colors: true }))
@ -158,6 +157,18 @@ yargs
) )
.command(
'check [files..]',
'Check the given files/packages for mistakes.',
yargs => yargs,
args => {
const pkgs = loadPackagesAndSourceFiles(toArray(args.files as string[] | string));
const program = new Program(pkgs);
const frontend = new Frontend();
frontend.check(program);
}
)
.command( .command(
'bundle [files..]', 'bundle [files..]',

View file

@ -1,883 +0,0 @@
/**
*
* ```
* mod foo {
* type MyType1 = i32;
* mod bar {
* pub type MyType2 = MyType1;
* }
* }
* ```
*
* ```
* mod foo {
* let x = 1;
* mod bar {
* fn do_something(y) {
* return x + y;
* }
* }
* }
* ```
*
* Note that the `pub`-keyword is not present on `MyType1`.
*/
import {
isSyntax,
Syntax,
SyntaxKind,
BoltReferenceExpression,
BoltDeclaration,
BoltSourceFile,
BoltSyntax,
BoltTypeDeclaration,
BoltExpression,
BoltFunctionDeclaration,
BoltFunctionBodyElement,
kindToString,
BoltStatement,
BoltTypeExpression,
BoltSourceElement,
isBoltStatement,
isBoltDeclaration,
isSourceFile,
BoltReferenceTypeExpression,
isBoltTypeDeclaration,
SourceFile,
BoltModifiers
} from "./ast";
import {warn, FastStringMap, countDigits, assert, verbose} from "./util";
import {
DiagnosticPrinter,
E_TYPES_NOT_ASSIGNABLE,
E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL,
E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL,
E_TYPE_DECLARATION_NOT_FOUND,
E_DECLARATION_NOT_FOUND,
E_INVALID_ARGUMENTS,
E_FILE_NOT_FOUND,
} from "./diagnostics";
import { createAnyType, isOpaqueType, createOpaqueType, Type, createVoidType, createVariantType, isVoidType } from "./types";
import { getReturnStatementsInFunctionBody, Package } from "./common";
import {emit} from "./emitter";
import {Program} from "./program";
import {type} from "os";
// 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;
}
}
function getModulePathToNode(node: BoltSyntax): string[] {
let elements = [];
while (true) {
if (node.kind === SyntaxKind.BoltModule) {
for (const element of node.name) {
elements.unshift(element.text);
}
}
if (node.parentNode === null) {
break;
}
node = node.parentNode;
}
return elements;
}
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);
}
return new SymbolPath(node.modulePath.map(id => id.text), false, name);
case SyntaxKind.BoltModulePath:
return new SymbolPath(
node.elements.slice(0, -1).map(el => el.text),
node.isAbsolute,
node.elements[node.elements.length-1].text
);
default:
throw new Error(`Could not extract a symbol path from the given node.`);
}
}
enum SymbolKind {
Type = 0x1,
Variable = 0x2,
Module = 0x4,
}
function* getAllSymbolKindsInMask(symbolKindMask: SymbolKind) {
for (let i = 1; i <= symbolKindMask; i *= 2) {
if ((symbolKindMask & i) > 0) {
yield i;
}
}
}
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,
private program: Program
) {
}
private symbols = new FastStringMap<string, SymbolInfo>();
public checkSourceFile(node: BoltSourceFile): void {
const self = this;
for (const element of node.elements) {
visitSourceElement(element);
}
function visitExpression(node: BoltExpression) {
switch (node.kind) {
case SyntaxKind.BoltConstantExpression:
break;
case SyntaxKind.BoltReferenceExpression:
{
if (self.resolveReferenceExpression(node) === null) {
self.diagnostics.add({
message: E_DECLARATION_NOT_FOUND,
args: { name: emit(node.name.name) },
severity: 'error',
node: node,
})
}
break;
}
case SyntaxKind.BoltCallExpression:
{
const fnDecls = self.getAllFunctionsInExpression(node.operator);
for (const fnDecl of fnDecls) {
if (fnDecl.params.length > node.operands.length) {
self.diagnostics.add({
message: E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL,
args: { expected: fnDecl.params.length, actual: node.operands.length },
severity: 'error',
node: node,
});
} else if (fnDecl.params.length < node.operands.length) {
self.diagnostics.add({
message: E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL,
args: { expected: fnDecl.params.length, actual: node.operands.length },
severity: 'error',
node: node,
});
} else {
const paramCount = fnDecl.params.length;
for (let i = 0; i < paramCount; i++) {
const arg = node.operands[i];
const param = fnDecl.params[i];
let argType = self.getTypeOfNode(arg);
let paramType = self.getTypeOfNode(param);
if (!self.isTypeAssignableTo(argType, paramType)) {
self.diagnostics.add({
message: E_INVALID_ARGUMENTS,
severity: 'error',
args: { name: fnDecl.name.text },
node: arg,
});
}
}
}
}
break;
}
default:
throw new Error(`Unknown node of type ${kindToString(node.kind)}.`);
}
}
function visitTypeExpressionn(node: BoltTypeExpression) {
switch (node.kind) {
case SyntaxKind.BoltReferenceTypeExpression:
{
if (self.resolveTypeReferenceExpression(node) === null) {
self.diagnostics.add({
message: E_TYPE_DECLARATION_NOT_FOUND,
args: { name: emit(node.path) },
severity: 'error',
node: node,
})
}
break;
}
default:
throw new Error(`Unknown node of type ${kindToString(node.kind)}.`);
}
}
function visitDeclaration(node: BoltDeclaration) {
switch (node.kind) {
case SyntaxKind.BoltRecordDeclaration:
{
if (node.members !== null) {
for (const member of node.members) {
if (member.kind === SyntaxKind.BoltRecordField) {
visitTypeExpressionn(member.type);
}
}
}
break;
}
case SyntaxKind.BoltTypeAliasDeclaration:
{
// TODO
break;
}
case SyntaxKind.BoltTraitDeclaration:
{
// TODO
break;
}
case SyntaxKind.BoltImplDeclaration:
{
// TODO
break;
}
case SyntaxKind.BoltFunctionDeclaration:
{
let fnReturnType: Type = createAnyType();
if (node.returnType !== null) {
fnReturnType = self.getTypeOfNode(node.returnType);
}
if (node.body !== null) {
const returnStmts = getReturnStatementsInFunctionBody(node.body)
const validReturnTypes: Type[] = [];
for (const returnStmt of returnStmts) {
if (returnStmt.value === null) {
if (!isVoidType(fnReturnType)) {
self.diagnostics.add({
message: E_MUST_RETURN_A_VALUE,
node: returnStmt,
severity: 'error',
});
}
} else {
checkExpressionMatchesType(returnStmt.value, fnReturnType);
}
//const returnType = self.getTypeOfNode(returnStmt);
//if (!self.isTypeAssignableTo(fnReturnType, returnType)) {
//self.diagnostics.add({
//severity: 'error',
//node: returnStmt.value !== null ? returnStmt.value : returnStmt,
//args: { left: fnReturnType, right: returnType },
//message: E_TYPES_NOT_ASSIGNABLE,
//});
//} else {
//validReturnTypes.push(returnType);
//}
}
}
// TODO Sort the return types and find the largest types, eliminating types that fall under other types.
// Next, add the resulting types as type hints to `fnReturnType`.
break;
}
default:
throw new Error(`Unknown node of type ${kindToString(node.kind)}.`);
}
}
function checkExpressionMatchesType(node: BoltExpression, expectedType: Type) {
switch (node.kind) {
case SyntaxKind.BoltMatchExpression:
{
for (const matchArm of node.arms) {
checkExpressionMatchesType(matchArm.body, expectedType);
}
break;
}
default:
{
const actualType = self.getTypeOfNode(node);
if (!self.isTypeAssignableTo(expectedType, actualType)) {
self.diagnostics.add({
severity: 'error',
message: E_TYPES_NOT_ASSIGNABLE,
args: { left: expectedType, right: actualType },
node,
});
}
break;
}
}
}
function visitStatement(node: BoltStatement) {
switch (node.kind) {
case SyntaxKind.BoltExpressionStatement:
// TODO check for values that should be unwrapped
visitExpression(node.expression);
break;
case SyntaxKind.BoltReturnStatement:
if (node.value !== null) {
visitExpression(node.value);
}
break;
default:
throw new Error(`Unknown node of type ${kindToString(node.kind)}.`);
}
}
function visitSourceElement(node: BoltSourceElement) {
if (isBoltStatement(node)) {
visitStatement(node);
} else if (isBoltDeclaration(node)) {
visitDeclaration(node);
} else if (node.kind === SyntaxKind.BoltModule) {
for (const element of node.elements) {
visitSourceElement(element);
}
} else if (node.kind !== SyntaxKind.BoltImportDirective) {
throw new Error(`Unknown node of kind ${kindToString(node.kind)}`);
}
}
}
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]);
}
private createType(node: BoltSyntax): Type {
switch (node.kind) {
case SyntaxKind.BoltReferenceTypeExpression:
{
const referenced = this.resolveTypeReferenceExpression(node);
if (referenced === null) {
return createAnyType();
}
return this.getTypeOfNode(referenced);
}
case SyntaxKind.BoltRecordDeclaration:
{
if (node.members === null) {
return createOpaqueType();
}
// TODO
throw new Error(`Not yet implemented.`);
}
case SyntaxKind.BoltParameter:
{
let type: Type = createAnyType();
if (node.type !== null) {
type = this.getTypeOfNode(node.type);
}
return type;
}
case SyntaxKind.BoltReturnStatement:
{
if (node.value === null) {
return createVoidType();
}
return this.getTypeOfNode(node.value)
}
case SyntaxKind.BoltConstantExpression:
{
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:
{
return createVariantType(...node.arms.map(arm => this.getTypeOfNode(arm.body)));
}
default:
throw new Error(`Could not derive type of node ${kindToString(node.kind)}.`);
}
}
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;
}
return false;
}
private getAllFunctionsInExpression(node: BoltExpression): BoltFunctionDeclaration[] {
const self = this;
const results: BoltFunctionDeclaration[] = [];
visitExpression(node);
return results;
function visitExpression(node: BoltExpression) {
switch (node.kind) {
case SyntaxKind.BoltReferenceExpression:
{
const resolved = self.resolveReferenceExpression(node);
if (resolved !== null) {
visitFunctionBodyElement(resolved);
}
break;
}
default:
throw new Error(`Unexpected node type ${kindToString(node.kind)}`);
}
}
function visitFunctionBodyElement(node: BoltFunctionBodyElement) {
switch (node.kind) {
case SyntaxKind.BoltFunctionDeclaration:
results.push(node);
break;
case SyntaxKind.BoltVariableDeclaration:
if (node.value !== null) {
visitExpression(node.value);
}
break;
default:
throw new Error(`Unexpected node type ${kindToString(node.kind)}`);
}
}
}
public registerSourceFile(node: BoltSourceFile): 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): 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) {
// FIXME should be moved to checkNode()
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)) {
addNodeAsSymbol(exportedNode, true);
}
}
}
break;
}
case SyntaxKind.BoltModule:
{
addNodeAsSymbol(node);
// TODO check for duplicates
for (const element of node.elements) {
addAllSymbolsToScope(element, variableScope, typeScope, moduleScope);
}
break;
}
case SyntaxKind.BoltSourceFile:
{
for (const element of node.elements) {
addAllSymbolsToScope(element, variableScope, typeScope, moduleScope);
}
break;
}
case SyntaxKind.BoltRecordDeclaration:
case SyntaxKind.BoltFunctionDeclaration:
addNodeAsSymbol(node);
break;
}
function addNodeAsSymbol(node: BoltSyntax, allowDuplicates = false) {
switch (node.kind) {
case SyntaxKind.BoltModule:
checkAndAddNode(node.name[node.name.length-1].text, node, moduleScope, SymbolKind.Module);
break;
case SyntaxKind.BoltFunctionDeclaration:
checkAndAddNode(emit(node.name), node, variableScope, SymbolKind.Variable);
break;
case SyntaxKind.BoltRecordDeclaration:
checkAndAddNode(node.name.text, node, typeScope, SymbolKind.Type);
break;
}
function checkAndAddNode(symbolName: string, node: BoltSyntax, scope: ScopeInfo, symbolKind: SymbolKind) {
const sym = self.lookupSymbolInScope(symbolName, variableScope, symbolKind)
if (sym !== null) {
if (!allowDuplicates) {
throw new Error(`Symbol '${symbolName}' is already defined.`);
}
if (sym.declarations.indexOf(node) === -1) {
throw new Error(`Different symbols imported under the same name.`);
}
}
self.addSymbolToScope(symbolName, node, scope, symbolKind);
}
}
}
}
private getAllExportedNodes(node: BoltSyntax): BoltSyntax[] {
const nodes: BoltSyntax[] = [];
visit(node);
return nodes;
function visit(node: BoltSyntax) {
switch (node.kind) {
case SyntaxKind.BoltFunctionDeclaration:
case SyntaxKind.BoltRecordDeclaration:
case SyntaxKind.BoltTypeAliasDeclaration:
case SyntaxKind.BoltVariableDeclaration:
if ((node.modifiers & BoltModifiers.IsPublic) > 0) {
nodes.push(node);
}
break;
case SyntaxKind.BoltSourceFile:
for (const element of node.elements) {
visit(element);
}
break;
case SyntaxKind.BoltModule:
if ((node.modifiers & BoltModifiers.IsPublic) > 0) {
nodes.push(node);
}
break;
}
}
}
private resolveReferenceExpression(node: BoltReferenceExpression): BoltDeclaration | null {
const symbolPath = nodeToSymbolPath(node.name)
const scope = this.getScopeForNode(node, SymbolKind.Variable);
return this.resolveSymbolPath(symbolPath, scope, SymbolKind.Variable) as BoltDeclaration;
}
private resolveTypeReferenceExpression(node: BoltReferenceTypeExpression): BoltTypeDeclaration | null {
const symbolPath = nodeToSymbolPath(node.path);
const scope = this.getScopeForNode(node, SymbolKind.Type);
return this.resolveSymbolPath(symbolPath, scope, SymbolKind.Type) as BoltTypeDeclaration;
}
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 {
//let message = `Adding symbol ${name} in scope #${scope.id}`;
//const modulePath = getModulePathToNode(scope.declaration);
//if (modulePath.length > 0) {
// message += ` of module ${modulePath.join('::')} in file ${scope.declaration.span!.file.origPath}`;
//} else {
// message += ` of file ${scope.declaration.span!.file.origPath}`;
//}
//verbose(message);
const sym = { kind: symbolKindMask, declarations: [ node ] } as SymbolInfo;
for (const symbolKind of getAllSymbolKindsInMask(symbolKindMask)) {
const key = `${symbolKind}:${name}:${scope.id}`;
if (this.symbols.has(key)) {
warn(`Warninig: silently skipping introduction of duplicate symbol '${name}'`);
} else {
this.symbols.set(key, 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;
}
// FIXME it should be possible to directly get the scope of a symbol
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;
}
}
}

149
src/checks.ts Normal file
View file

@ -0,0 +1,149 @@
import { BoltImportDirective, Syntax, BoltParameter, BoltModulePath, BoltReferenceExpression, BoltReferenceTypeExpression, BoltSourceFile, BoltCallExpression, BoltReturnKeyword, BoltReturnStatement, SyntaxKind, NodeVisitor } from "./ast";
import { Program } from "./program";
import { DiagnosticPrinter, E_FILE_NOT_FOUND, E_TYPES_NOT_ASSIGNABLE, E_DECLARATION_NOT_FOUND, E_TYPE_DECLARATION_NOT_FOUND, E_MUST_RETURN_A_VALUE } from "./diagnostics";
import { getSymbolPathFromNode } from "./resolver"
import { inject } from "./di";
import { SymbolResolver, ScopeType } from "./resolver";
import { assert } from "./util";
import { emitNode } from "./emitter";
import { TypeChecker, Type } from "./types";
import { getReturnStatementsInFunctionBody } from "./common";
export class CheckInvalidFilePaths extends NodeVisitor {
constructor(
@inject private program: Program,
@inject private diagnostics: DiagnosticPrinter,
) {
super();
}
protected visitBoltImportDirective(node: BoltImportDirective) {
const sourceFile = this.program.resolveToSourceFile(node.file.value, node);
if (sourceFile === null) {
this.diagnostics.add({
severity: 'error',
message: E_FILE_NOT_FOUND,
args: { filename: node.file.value },
node: node.file,
});
}
}
}
export class CheckReference extends NodeVisitor {
constructor(
@inject private diagnostics: DiagnosticPrinter,
@inject private resolver: SymbolResolver
) {
super();
}
private checkBoltModulePath(node: BoltModulePath, symbolKind: ScopeType) {
const scope = this.resolver.getScopeForNode(node, symbolKind);
assert(scope !== null);
const sym = this.resolver.resolveModulePath(node.elements.map(el => el.text), scope!);
if (sym === null) {
this.diagnostics.add({
message: E_DECLARATION_NOT_FOUND,
severity: 'error',
args: { name: emitNode(node) },
node,
});
}
}
protected visitBoltReferenceExpression(node: BoltReferenceExpression) {
if (node.modulePath !== null) {
this.checkBoltModulePath(node.modulePath, ScopeType.Variable);
}
const scope = this.resolver.getScopeSurroundingNode(node, ScopeType.Variable);
assert(scope !== null);
const resolvedSym = this.resolver.resolveSymbolPath(getSymbolPathFromNode(node), scope!);
if (resolvedSym === null) {
this.diagnostics.add({
message: E_DECLARATION_NOT_FOUND,
args: { name: emitNode(node.name) },
severity: 'error',
node: node.name,
})
}
}
protected visitBoltReferenceTypeExpression(node: BoltReferenceTypeExpression) {
const scope = this.resolver.getScopeForNode(node, ScopeType.Type);
assert(scope !== null);
const resolvedSym = this.resolver.resolveSymbolPath(getSymbolPathFromNode(node), scope!);
if (resolvedSym === null) {
this.diagnostics.add({
message: E_TYPE_DECLARATION_NOT_FOUND,
args: { name: emitNode(node.path) },
severity: 'error',
node: node.path,
})
}
}
}
export class CheckTypeAssignments extends NodeVisitor {
constructor(
@inject private diagnostics: DiagnosticPrinter,
@inject private checker: TypeChecker,
) {
super();
}
protected visitBoltReturnStatement(node: BoltReturnStatement) {
const fnDecl = node.getParentOfKind(SyntaxKind.BoltFunctionDeclaration)!;
if (node.value === null) {
if (fnDecl.returnType !== null && this.checker.isVoid(fnDecl.returnType)) {
this.diagnostics.add({
message: E_MUST_RETURN_A_VALUE,
node,
severity: 'error',
});
}
} else {
for (const error of this.checker.getAssignmentErrors(fnDecl.returnType, node.value)) {
this.diagnostics.add({
message: E_MUST_RETURN_A_VALUE,
node: node,
severity: 'error',
});
}
}
}
protected visitBoltParameter(node: BoltParameter) {
if (node.defaultValue !== null) {
for (const error of this.checker.getAssignmentErrors(node.bindings, node.defaultValue)) {
this.diagnostics.add({
severity: 'error',
message: E_TYPES_NOT_ASSIGNABLE,
args: { node: error.node }
});
}
}
}
protected visitBoltCallExpression(node: BoltCallExpression) {
for (const fnDecl of this.checker.getCallableFunctions(node)) {
for (const error of this.checker.getAssignmentErrors(fnDecl, node)) {
this.diagnostics.add({
severity: 'error',
message: E_TYPES_NOT_ASSIGNABLE,
args: { node: error.node },
});
}
}
}
}

View file

@ -10,11 +10,11 @@ import {
isBoltPunctuated, isBoltPunctuated,
SourceFile, SourceFile,
BoltSourceFile, BoltSourceFile,
BoltSourceFileModifiers, isSourceFile,
isSourceFile BoltSyntax,
BoltModifiers
} from "./ast"; } from "./ast";
import { BOLT_SUPPORTED_LANGUAGES } from "./constants" import { BOLT_SUPPORTED_LANGUAGES } from "./constants"
import {emit} from "./emitter";
import {FastStringMap, enumerate, escapeChar, assert} 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";
@ -56,13 +56,6 @@ export class Package {
} }
export function isAutoImported(node: BoltSourceFile): boolean {
//if (node.kind !== SyntaxKind.BoltSourceFile) {
// node = node.getParentOfKind(SyntaxKind.BoltSourceFile)!
//}
return (node.modifiers & BoltSourceFileModifiers.AutoImport) > 0;
}
export function getLanguage(node: Syntax): string { export function getLanguage(node: Syntax): string {
const kindStr = kindToString(node.kind); const kindStr = kindToString(node.kind);
for (const prefix of BOLT_SUPPORTED_LANGUAGES) { for (const prefix of BOLT_SUPPORTED_LANGUAGES) {
@ -148,10 +141,6 @@ export function getReturnStatementsInFunctionBody(body: BoltFunctionBody): BoltR
} }
export function hasPublicModifier(node: BoltDeclaration) {
return (node.modifiers & BoltDeclarationModifiers.Public) > 0;
}
export enum OperatorKind { export enum OperatorKind {
Prefix, Prefix,
InfixL, InfixL,
@ -210,6 +199,37 @@ export class OperatorTable {
} }
export function getModulePathToNode(node: BoltSyntax): string[] {
let elements = [];
while (true) {
if (node.kind === SyntaxKind.BoltModule) {
for (const element of node.name) {
elements.unshift(element.text);
}
}
if (node.parentNode === null) {
break;
}
node = node.parentNode;
}
return elements;
}
export function isExported(node: Syntax) {
switch (node.kind) {
case SyntaxKind.BoltVariableDeclaration:
case SyntaxKind.BoltFunctionDeclaration:
case SyntaxKind.BoltModule:
case SyntaxKind.BoltRecordDeclaration:
case SyntaxKind.BoltTypeAliasDeclaration:
case SyntaxKind.BoltTraitDeclaration:
case SyntaxKind.BoltImplDeclaration:
return (node.modifiers & BoltModifiers.IsPublic) > 0;
default:
throw new Error(`The node ${kindToString(node.kind)} can not be exported.`)
}
}
export function describeKind(kind: SyntaxKind): string { export function describeKind(kind: SyntaxKind): string {
switch (kind) { switch (kind) {
case SyntaxKind.BoltImportKeyword: case SyntaxKind.BoltImportKeyword:

View file

@ -98,7 +98,7 @@ export class Emitter {
/** /**
* A wrapper around `Emitter` for quick emission of AST nodes with sane defaults. * A wrapper around `Emitter` for quick emission of AST nodes with sane defaults.
*/ */
export function emit(node: Syntax) { export function emitNode(node: Syntax) {
const emitter = new Emitter(); const emitter = new Emitter();
return emitter.emit(node); return emitter.emit(node);
} }

View file

@ -1,77 +1,51 @@
import { Syntax, SyntaxKind, Expr, isNode, BoltQualName } from "./ast" import { Syntax, SyntaxKind, BoltQualName, BoltExpression, kindToString, BoltSyntax, isBoltStatement } from "./ast"
import { TypeChecker, Type, RecordType, PrimType, boolType } from "./checker" import { FastStringMap, assert } from "./util"
import { FastStringMap } from "./util" import { emitNode } from "./emitter";
import { Type, TypeChecker, RecordType } from "./types";
export interface Value { export interface Value {
type: Type; readonly type?: Type;
data: ValueData;
} }
export class PrimValue implements Value { class Record {
constructor( private fields: Map<string, Value>;
public type: PrimType,
public value: any
) {
constructor(fields: Iterable<[string, Value]>) {
this.fields = new Map(fields);
}
public clone(): Record {
return new Record(this.fields);
}
public addField(name: string, value: Value): void {
this.fields.set(name, value);
}
public deleteField(name: string): void {
this.fields.delete(name);
}
public clear(): void {
this.fields.clear();
} }
} }
export const TRUE = new PrimValue(boolType, true); type ValueData
export const FALSE = new PrimValue(boolType, false); = string
| undefined
export abstract class RecordValue implements Value { | boolean
| number
abstract type: RecordType; | bigint
| Record
abstract getValueOfField(name: string): Value;
}
export class NativeRecord implements Value {
constructor(
public type: RecordType,
protected fields: FastStringMap<Value>,
) {
}
getValueOfField(name: string): Value {
if (!this.type.hasField(name)) {
throw new Error(`Field '${name}' does not exist on this record.`)
}
return this.fields[name]
}
}
export class RecordWrapper extends RecordValue {
constructor(
public type: RecordType,
protected data: any,
) {
super();
}
getValueOfField(name: string): Value {
if (!this.type.hasField(name)) {
throw new Error(`Field '${name}' does not exist on this record.`)
}
return this.data[name]
}
}
function getDeclarationPath(node: BoltQualName) {
return [...node.modulePath.map(id => id.text), node.name.text];
}
class Environment { class Environment {
private symbols = FastStringMap<string, Value>(); private symbols = new FastStringMap<string, Value>();
constructor(public parentEnv: Environment | null = null) { constructor(public parentEnv: Environment | null = null) {
@ -81,20 +55,22 @@ class Environment {
if (name in this.symbols) { if (name in this.symbols) {
throw new Error(`A variable with the name '${name}' already exists.`); throw new Error(`A variable with the name '${name}' already exists.`);
} }
this.symbols[name] = value; this.symbols.set(name, value);
} }
public updateValue(name: string, newValue: Value) { public updateValue(name: string, newValue: Value) {
if (!(name in this.symbols)) { if (!this.symbols.has(name)) {
throw new Error(`Trying to update a variable '${name}' that has not been declared.`); throw new Error(`Trying to update a variable '${name}' that has not been declared.`);
} }
this.symbols.delete(name);
this.symbols.set(name, newValue);
} }
public lookup(name: string) { public lookup(name: string) {
let curr = this as Environment; let curr = this as Environment;
while (true) { while (true) {
if (name in curr.symbols) { if (this.symbols.has(name)) {
return curr.symbols[name]; return curr.symbols.get(name);
} }
if (curr.parentEnv === null) { if (curr.parentEnv === null) {
break; break;
@ -106,40 +82,78 @@ class Environment {
} }
function mangle(node: BoltSyntax) {
switch (node.kind) {
case SyntaxKind.BoltIdentifier:
return emitNode(node);
default:
throw new Error(`Could not mangle ${kindToString(node.kind)} to a symbol name.`)
}
}
class EvaluationError extends Error {
}
export class Evaluator { export class Evaluator {
constructor(public checker: TypeChecker) { constructor(public checker: TypeChecker) {
} }
match(value: Value, node: Syntax) { private performPatternMatch(value: Value, node: Syntax, env: Environment): boolean {
switch (node.kind) { switch (node.kind) {
case SyntaxKind.RecordPatt: case SyntaxKind.BoltBindPattern:
for (const field of node.fields) { {
if (!this.match((value as RecordValue).getValueOfField(field.name.text), field.pattern)) { env.setValue(node.name.text, value);
return true;
}
case SyntaxKind.BoltRecordPattern:
{
if (!(value.data instanceof Record)) {
throw new EvaluationError(`A deconstructing record pattern received a value that is not a record.`);
}
const record = value.data.clone();
for (const fieldPatt of node.fields) {
if (fieldPatt.isRest) {
if (fieldPatt.name !== null) {
env.setValue(fieldPatt.name.text, { data: fields.clone() });
}
record.clear();
} else {
assert(fieldPatt.name !== null);
let isMatch = true;
if (fieldPatt.pattern !== null) {
isMatch = this.performPatternMatch(value.getFieldValue(fieldPatt.name!.text), fieldPatt.pattern, env);
}
if (!isMatch) {
return false; return false;
} }
record.deleteField(fieldPatt.name!.text);
}
} }
return true; return true;
}
case SyntaxKind.TypePatt: case SyntaxKind.BoltTypePattern:
return value.type === this.checker.getTypeOfNode(node) {
const expectedType = this.checker.getTypeOfNode(node.type);
if (!this.checker.isTypeAssignableTo(expectedType, getTypeOfValue(value))) {
return false;
}
return false;
}
default: default:
throw new Error(`I did not know how to match on pattern ${SyntaxKind[node.kind]}`) throw new Error(`I did not know how to match on pattern ${kindToString(node.kind)}`)
} }
} }
createValue(data: any) {
if (isNode(data)) {
return new RecordWrapper(this.checker.getTypeNamed(`Bolt.AST.${SyntaxKind[data.kind]}`)! as RecordType, data)
}
}
public eval(node: Syntax, env: Environment = new Environment()): Value { public eval(node: Syntax, env: Environment = new Environment()): Value {
switch (node.kind) { switch (node.kind) {
@ -147,32 +161,31 @@ export class Evaluator {
case SyntaxKind.BoltSourceFile: case SyntaxKind.BoltSourceFile:
case SyntaxKind.BoltModule: case SyntaxKind.BoltModule:
for (const element of node.elements) { for (const element of node.elements) {
if (isBoltStatement(element)) {
this.eval(element, env); this.eval(element, env);
} }
break; }
return { data: undefined }
case SyntaxKind.BoltReferenceTypeExpression: case SyntaxKind.BoltReferenceExpression:
// FIXME return env.lookup(mangle(node.name));
return env.lookup(node.name.name.text);
case SyntaxKind.BoltRecordDeclaration:
case SyntaxKind.BoltFunctionDeclaration:
break;
case SyntaxKind.BoltMatchExpression: case SyntaxKind.BoltMatchExpression:
const value = this.eval(node.value, env); const value = this.eval(node.value, env);
for (const [pattern, result] of node.arms) { for (const matchArm of node.arms) {
if (this.match(value, pattern)) { const matchArmEnv = new Environment(env);
return this.eval(result as Expr, env) const isMatch = this.performPatternMatch(value, matchArm.pattern, matchArmEnv);
if (isMatch) {
return this.eval(matchArm.body, env)
} }
} }
return new PrimValue(this.checker.getTypeNamed('Void')!, null); return { data: undefined };
case SyntaxKind.BoltConstantExpression: case SyntaxKind.BoltConstantExpression:
return new PrimValue(this.checker.getTypeOfNode(node), node.value) return node.value;
default: default:
throw new Error(`Could not evaluate node ${SyntaxKind[node.kind]}`) throw new Error(`Could not evaluate node ${kindToString(node.kind)}`)
} }

View file

@ -5,20 +5,20 @@ import { now } from "microtime"
import { EventEmitter } from "events" import { EventEmitter } from "events"
import { Program } from "./program" import { Program } from "./program"
import { TypeChecker } from "./checker" import { emitNode } from "./emitter"
import { Evaluator } from "./evaluator" import { Syntax, BoltSourceFile, SourceFile, NodeVisitor } from "./ast"
import { emit } from "./emitter" import { getFileStem, MapLike } from "./util"
import { Syntax, BoltSourceFile, SourceFile } from "./ast"
import { upsearchSync, FastStringMap, getFileStem, getLanguage } from "./util"
import { Package } from "./package"
import { verbose, memoize } from "./util" import { verbose, memoize } from "./util"
import { Container } from "./di" import { Container, Newable } from "./di"
import ExpandBoltTransform from "./transforms/expand" import ExpandBoltTransform from "./transforms/expand"
import CompileBoltToJSTransform from "./transforms/boltToJS" import CompileBoltToJSTransform from "./transforms/boltToJS"
import ConstFoldTransform from "./transforms/constFold" import ConstFoldTransform from "./transforms/constFold"
import EliminateModulesTransform from "./transforms/eliminateModules"
import { TransformManager } from "./transforms/index" import { TransformManager } from "./transforms/index"
import {DiagnosticPrinter} from "./diagnostics" import {DiagnosticPrinter} from "./diagnostics"
import { TypeChecker } from "./types"
import { checkServerIdentity } from "tls"
import { CheckInvalidFilePaths, CheckTypeAssignments } from "./checks"
import { SymbolResolver, BoltSymbolResolutionStrategy } from "./resolver"
const targetExtensions: MapLike<string> = { const targetExtensions: MapLike<string> = {
'JS': '.mjs', 'JS': '.mjs',
@ -33,7 +33,7 @@ interface TimingInfo {
class Timing extends EventEmitter { class Timing extends EventEmitter {
private runningTasks: FastStringMap<TimingInfo> = Object.create(null); private runningTasks: MapLike<TimingInfo> = Object.create(null);
public start(name: string) { public start(name: string) {
if (this.runningTasks[name] !== undefined) { if (this.runningTasks[name] !== undefined) {
@ -61,53 +61,67 @@ class Timing extends EventEmitter {
export class Frontend { export class Frontend {
public evaluator: Evaluator; //public resolver = new SymbolResolver();
public checker: TypeChecker; //public evaluator = new Evaluator(this.resolver);
public diagnostics: DiagnosticPrinter; public diagnostics: DiagnosticPrinter;
public timing: Timing; public timing: Timing;
private container = new Container();
constructor() { constructor() {
this.diagnostics = new DiagnosticPrinter(); this.diagnostics = new DiagnosticPrinter();
this.timing = new Timing(); this.timing = new Timing();
} }
@memoize //@memoize(filepath => path.resolve(filepath))
public getPackage(filepath: string) { //public getPackage(filepath: string) {
const file = this.getTextFile(filepath) // const file = this.getTextFile(filepath)
const projectFile = upsearchSync('Boltfile', path.dirname(file.fullPath)); // const projectFile = upsearchSync('Boltfile', path.dirname(file.fullPath));
if (projectFile === null) { // if (projectFile === null) {
return null; // return null;
} // }
const projectDir = path.resolve(path.dirname(projectFile)); // const projectDir = path.resolve(path.dirname(projectFile));
return new Package(projectDir); // return new Package(projectDir);
} //}
public check(program: Program) {
const resolver = new SymbolResolver(program, new BoltSymbolResolutionStrategy);
const checker = new TypeChecker(resolver);
const container = new Container();
container.bindSelf(program);
container.bindSelf(resolver);
container.bindSelf(checker);
const checks: Newable<NodeVisitor>[] = [
CheckInvalidFilePaths,
CheckTypeAssignments,
];
const checkers = checks.map(check => container.createInstance(check));
public typeCheck(program: Program) {
const checker = new TypeChecker(this.diagnostics, program);
for (const sourceFile of program.getAllSourceFiles()) { for (const sourceFile of program.getAllSourceFiles()) {
checker.registerSourceFile(sourceFile as BoltSourceFile); resolver.registerSourceFile(sourceFile as BoltSourceFile);
} }
for (const sourceFile of program.getAllSourceFiles()) { for (const sourceFile of program.getAllSourceFiles()) {
checker.checkSourceFile(sourceFile as BoltSourceFile); sourceFile.visit(checkers)
} }
} }
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(); const container = new Container();
const resolver = new SymbolResolver(program, new BoltSymbolResolutionStrategy);
//container.bindSelf(evaluator); for (const sourceFile of program.getAllSourceFiles()) {
container.bindSelf(checker); resolver.registerSourceFile(sourceFile as BoltSourceFile);
}
const transforms = new TransformManager(container);
container.bindSelf(transforms);
container.bindSelf(program);
container.bindSelf(resolver);
switch (target) { switch (target) {
case "JS": case "JS":
const transforms = new TransformManager(this.container);
transforms.register(ExpandBoltTransform); transforms.register(ExpandBoltTransform);
transforms.register(CompileBoltToJSTransform); transforms.register(CompileBoltToJSTransform);
transforms.register(ConstFoldTransform); transforms.register(ConstFoldTransform);
@ -121,7 +135,7 @@ export class Frontend {
for (const sourceFile of program.getAllSourceFiles()) { for (const sourceFile of program.getAllSourceFiles()) {
fs.mkdirp('.bolt-work'); fs.mkdirp('.bolt-work');
fs.writeFileSync(this.mapToTargetFile(sourceFile), emit(sourceFile), 'utf8'); fs.writeFileSync(this.mapToTargetFile(sourceFile), emitNode(sourceFile), 'utf8');
} }
} }

View file

@ -79,6 +79,7 @@ import {
createBoltMemberExpression, createBoltMemberExpression,
createBoltModulePath, createBoltModulePath,
BoltModulePath, BoltModulePath,
isBoltSymbol,
} from "./ast" } from "./ast"
import { parseForeignLanguage } from "./foreign" import { parseForeignLanguage } from "./foreign"
@ -108,16 +109,23 @@ function assertNoTokens(tokens: BoltTokenStream) {
} }
} }
const KIND_EXPRESSION_T0 = [ const KIND_SYMBOL = [
SyntaxKind.BoltStringLiteral,
SyntaxKind.BoltIntegerLiteral,
SyntaxKind.BoltIdentifier, SyntaxKind.BoltIdentifier,
SyntaxKind.BoltOperator, SyntaxKind.BoltOperator,
SyntaxKind.BoltVBar, SyntaxKind.BoltVBar,
SyntaxKind.BoltLtSign,
SyntaxKind.BoltGtSign,
];
const KIND_EXPRESSION_T0 = uniq([
SyntaxKind.BoltStringLiteral,
SyntaxKind.BoltIntegerLiteral,
SyntaxKind.BoltOperator,
SyntaxKind.BoltMatchKeyword, SyntaxKind.BoltMatchKeyword,
SyntaxKind.BoltQuoteKeyword, SyntaxKind.BoltQuoteKeyword,
SyntaxKind.BoltYieldKeyword, SyntaxKind.BoltYieldKeyword,
] ...KIND_SYMBOL,
])
const KIND_STATEMENT_T0 = uniq([ const KIND_STATEMENT_T0 = uniq([
SyntaxKind.BoltReturnKeyword, SyntaxKind.BoltReturnKeyword,
@ -194,7 +202,7 @@ export class Parser {
return (this as any)['parse' + kindToString(kind).substring('Bolt'.length)](tokens); return (this as any)['parse' + kindToString(kind).substring('Bolt'.length)](tokens);
} }
public parseNamespacePath(tokens: BoltTokenStream): BoltModulePath { public parseModulePath(tokens: BoltTokenStream): BoltModulePath {
let isAbsolute = false; let isAbsolute = false;
let elements = []; let elements = [];
@ -381,7 +389,7 @@ export class Parser {
public parseReferenceTypeExpression(tokens: BoltTokenStream): BoltReferenceTypeExpression { public parseReferenceTypeExpression(tokens: BoltTokenStream): BoltReferenceTypeExpression {
const path = this.parseNamespacePath(tokens) const path = this.parseModulePath(tokens)
const t1 = tokens.peek(); const t1 = tokens.peek();
let typeArgs: BoltTypeExpression[] | null = null; let typeArgs: BoltTypeExpression[] | null = null;
@ -431,7 +439,7 @@ export class Parser {
if (!isBoltOperatorLike(t0)) { if (!isBoltOperatorLike(t0)) {
break; break;
} }
let desc0 = this.typeOperatorTable.lookup(emit(t0)); let desc0 = this.typeOperatorTable.lookup(emitNode(t0));
if (desc0 === null || desc0.arity !== 2 || desc0.precedence < minPrecedence) { if (desc0 === null || desc0.arity !== 2 || desc0.precedence < minPrecedence) {
break; break;
} }
@ -442,7 +450,7 @@ export class Parser {
if (!isBoltOperatorLike(t1.kind)) { if (!isBoltOperatorLike(t1.kind)) {
break; break;
} }
const desc1 = this.typeOperatorTable.lookup(emit(t1)) const desc1 = this.typeOperatorTable.lookup(emitNode(t1))
if (desc1 === null || desc1.arity !== 2 || desc1.precedence < desc0.precedence || !isRightAssoc(desc1.kind)) { if (desc1 === null || desc1.arity !== 2 || desc1.precedence < desc0.precedence || !isRightAssoc(desc1.kind)) {
break; break;
} }
@ -512,9 +520,35 @@ export class Parser {
} }
public parseReferenceExpression(tokens: BoltTokenStream): BoltReferenceExpression { public parseReferenceExpression(tokens: BoltTokenStream): BoltReferenceExpression {
const name = this.parseNamespacePath(tokens);
const node = createBoltReferenceExpression(name); const firstToken = tokens.peek();
setOrigNodeRange(node, name, name); let isAbsolute = false;
let elements = [];
while (true) {
const t2 = tokens.peek(2);
if (t2.kind !== SyntaxKind.BoltColonColon) {
break;
}
const t1 = tokens.get();
assertToken(t1, SyntaxKind.BoltIdentifier);
elements.push(t1 as BoltIdentifier)
tokens.get();
}
let path = null;
if (elements.length > 0) {
path = createBoltModulePath(isAbsolute, elements);
setOrigNodeRange(path, elements[0], elements[elements.length-1]);
}
const t3 = tokens.get();
assertToken(t3, SyntaxKind.BoltIdentifier);
// TODO Add support for parsing parenthesized operators
const node = createBoltReferenceExpression(path, t3 as BoltIdentifier);
setOrigNodeRange(node, firstToken, t3);
return node; return node;
} }
@ -930,7 +964,7 @@ export class Parser {
} }
// FIXME should fail to parse absolute paths // FIXME should fail to parse absolute paths
const name = this.parseNamespacePath(tokens); const name = this.parseModulePath(tokens);
const t1 = tokens.get(); const t1 = tokens.get();
if (t1.kind !== SyntaxKind.BoltBraced) { if (t1.kind !== SyntaxKind.BoltBraced) {
@ -1526,7 +1560,7 @@ import { Scanner } from "./scanner"
import { TextFile, TextSpan, TextPos } from "./text" import { TextFile, TextSpan, TextPos } from "./text"
import * as fs from "fs" import * as fs from "fs"
import {JSScanner} from "./foreign/js/scanner"; import {JSScanner} from "./foreign/js/scanner";
import {emit} from "./emitter"; import {emitNode} from "./emitter";
export function parseSourceFile(filepath: string, pkg: Package): BoltSourceFile { export function parseSourceFile(filepath: string, pkg: Package): BoltSourceFile {
const file = new TextFile(filepath); const file = new TextFile(filepath);

497
src/resolver.ts Normal file
View file

@ -0,0 +1,497 @@
import { BoltSyntax, SyntaxKind, Syntax, BoltSourceFile, SourceFile, kindToString } from "./ast";
import { emitNode } from "./emitter";
import { Package, isExported } from "./common";
import { FastStringMap, assert, every } from "./util";
import { Program } from "./program";
const GLOBAL_SCOPE_ID = 'global';
export 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;
}
}
export function getSymbolPathFromNode(node: BoltSyntax): SymbolPath {
switch (node.kind) {
case SyntaxKind.BoltIdentifier:
return new SymbolPath([], false, emitNode(node));
case SyntaxKind.BoltQualName:
const name = emitNode(node.name);
if (node.modulePath === null) {
return new SymbolPath([], false, name);
}
return new SymbolPath(node.modulePath.map(id => id.text), false, name);
case SyntaxKind.BoltModulePath:
return new SymbolPath(
node.elements.slice(0, -1).map(el => el.text),
node.isAbsolute,
node.elements[node.elements.length-1].text
);
default:
throw new Error(`Could not extract a symbol path from the given node.`);
}
}
export enum ScopeType {
Type = 0x1,
Variable = 0x2,
Module = 0x4,
Any = Type | Variable,
}
function* getAllSymbolKinds() {
for (let i = 1; i <= ScopeType.Any; i *= 2) {
yield i;
}
}
interface ScopeSource {
readonly id: string;
}
class PackageScopeSource implements ScopeSource {
constructor(public pkg: Package) {
}
public get id() {
return `pkg:${this.pkg.id}`
}
}
class GlobalScopeSource {
public get id() {
return GLOBAL_SCOPE_ID;
}
}
class NodeScopeSource implements ScopeSource {
constructor(public node: Syntax) {
}
public get id() {
return `node:${this.node.id}`
}
}
interface ResolutionStrategy {
getSymbolName(node: Syntax): string;
getScopeType(node: Syntax): ScopeType;
getNextScopeSources(source: ScopeSource, kind: ScopeType): IterableIterator<ScopeSource>;
}
export class BoltSymbolResolutionStrategy implements ResolutionStrategy {
public hasSymbol(node: Syntax): boolean {
switch (node.kind) {
case SyntaxKind.BoltModule:
case SyntaxKind.BoltBindPattern:
case SyntaxKind.BoltFunctionDeclaration:
case SyntaxKind.BoltTypeAliasDeclaration:
case SyntaxKind.BoltRecordDeclaration:
case SyntaxKind.BoltBindPattern:
case SyntaxKind.BoltTraitDeclaration:
case SyntaxKind.BoltImplDeclaration:
return true;
default:
return false;
}
}
public getSymbolName(node: Syntax): string {
switch (node.kind) {
case SyntaxKind.BoltModule:
return node.name[node.name.length-1].text;
case SyntaxKind.BoltBindPattern:
return node.name.text;
case SyntaxKind.BoltFunctionDeclaration:
return emitNode(node.name);
case SyntaxKind.BoltTypeAliasDeclaration:
return node.name.text;
case SyntaxKind.BoltRecordDeclaration:
return node.name.text;
case SyntaxKind.BoltBindPattern:
return node.name.text;
case SyntaxKind.BoltTraitDeclaration:
return node.name.text;
case SyntaxKind.BoltImplDeclaration:
return node.name.text;
default:
throw new Error(`Could not derive symbol name of node ${kindToString(node.kind)}`)
}
}
public getScopeType(node: Syntax): ScopeType {
switch (node.kind) {
case SyntaxKind.BoltVariableDeclaration:
case SyntaxKind.BoltFunctionDeclaration:
return ScopeType.Variable;
case SyntaxKind.BoltImplDeclaration:
case SyntaxKind.BoltTraitDeclaration:
case SyntaxKind.BoltTypeAliasDeclaration:
case SyntaxKind.BoltRecordDeclaration:
return ScopeType.Type;
case SyntaxKind.BoltModule:
return ScopeType.Module;
default:
throw new Error(`Could not derive scope type of node ${kindToString(node.kind)}.`)
}
}
public introducesNewScope(node: BoltSyntax, kind: ScopeType): boolean {
switch (kind) {
case ScopeType.Variable:
return node.kind === SyntaxKind.BoltSourceFile
|| node.kind === SyntaxKind.BoltModule
|| node.kind === SyntaxKind.BoltFunctionDeclaration
|| node.kind === SyntaxKind.BoltBlockExpression;
case ScopeType.Type:
return node.kind === SyntaxKind.BoltModule
|| node.kind === SyntaxKind.BoltSourceFile
|| node.kind === SyntaxKind.BoltFunctionDeclaration
|| node.kind === SyntaxKind.BoltRecordDeclaration
|| node.kind === SyntaxKind.BoltTraitDeclaration
|| node.kind === SyntaxKind.BoltImplDeclaration;
case ScopeType.Module:
return node.kind === SyntaxKind.BoltModule
|| node.kind === SyntaxKind.BoltRecordDeclaration
|| node.kind === SyntaxKind.BoltSourceFile;
default:
throw new Error(`Invalid scope type detected.`)
}
}
public *getNextScopeSources(source: ScopeSource, kind: ScopeType): IterableIterator<ScopeSource> {
// If we are at a scope that was created by an AST node, we
// search the nearest parent that introduces a new scope of
// the requested kind. If no such scope was found, then we
// return the local package scope.
if (source instanceof NodeScopeSource) {
let currNode = source.node;
while (true) {
if (currNode.kind === SyntaxKind.BoltSourceFile) {
yield new PackageScopeSource(currNode.package);
return;
}
if (this.introducesNewScope(currNode, kind)) {
yield source;
return;
}
assert(currNode.parentNode !== null);
currNode = currNode.parentNode;
}
}
// If we already are at the local package scope level, we go one up
// to the global scope shared by all packages.
if (source instanceof PackageScopeSource) {
yield new GlobalScopeSource();
return;
}
// If we are in the global scope, there is no scope above it.
if (source instanceof GlobalScopeSource) {
return;
}
}
}
class Scope {
private static scopeCache = new FastStringMap<string, Scope>();
private nextScope: Scope | null | undefined;
private symbols = new FastStringMap<string, SymbolInfo>();
constructor(
private resolver: SymbolResolver,
public kind: ScopeType,
public source: ScopeSource,
) {
}
private get globallyUniqueKey() {
return `${this.kind}:${this.source.id}`;
}
public getScope(kind: ScopeType) {
if (Scope.scopeCache.has(this.globallyUniqueKey)) {
return Scope.scopeCache.get(this.globallyUniqueKey);
}
const newScope = new Scope(this.resolver, kind, this.source);
Scope.scopeCache.set(this.globallyUniqueKey, newScope);
return newScope;
}
public *getNextScopes(): IterableIterator<Scope> {
let results = [];
for (const nextSource of this.resolver.strategy.getNextScopeSources(this.source, this.kind)) {
const key = `${this.kind}:${nextSource.id}`;
if (Scope.scopeCache.has(key)) {
yield Scope.scopeCache.get(this.globallyUniqueKey);
} else {
const newScope = new Scope(this.resolver, this.kind, nextSource);
Scope.scopeCache.set(this.globallyUniqueKey, newScope);
yield newScope;
}
}
}
public getLocalSymbol(name: string) {
if (!this.symbols.has(name)) {
return null;
}
return this.symbols.get(name);
}
public getExportedSymbol(name: string) {
if (!this.symbols.has(name)) {
return null;
}
const sym = this.symbols.get(name);
if (!sym.isExported) {
return null;
}
return sym;
}
public getSymbol(name: string): SymbolInfo | null {
const stack: Scope[] = [ this ];
while (stack.length > 0) {
const currScope = stack.pop()!;
const sym = currScope.getLocalSymbol(name);
if (sym !== null) {
return sym;
}
for (const nextScope of currScope.getNextScopes()) {
stack.push(nextScope);
}
}
return null;
}
public addNodeAsSymbol(name: string, node: Syntax) {
if (this.symbols.has(name)) {
const sym = this.symbols.get(name);
if (!sym.declarations.has(node)) {
sym.declarations.add(node);
}
} else {
const sym = {
name,
scope: this,
declarations: new Set([ node ]),
isExported: isExported(node)
} as SymbolInfo;
this.symbols.set(name, sym);
}
}
}
interface SymbolInfo {
name: string;
scope: Scope;
isExported: boolean;
declarations: Set<Syntax>,
}
export class SymbolResolver {
constructor(
private program: Program,
public strategy: BoltSymbolResolutionStrategy
) {
}
private symbols = new FastStringMap<string, SymbolInfo>();
public registerSourceFile(node: SourceFile): void {
for (const childNode of node.preorder()) {
if (this.strategy.hasSymbol(node)) {
const name = this.strategy.getSymbolName(node);
const scope = this.getScopeForNode(node, this.strategy.getScopeType(node));
assert(scope !== null);
scope!.addNodeAsSymbol(name, node);
}
}
for (const importDir of node.findAllChildrenOfKind(SyntaxKind.BoltImportDirective)) {
const sourceFile = this.program.resolveToSourceFile(importDir.file.value, importDir) as BoltSourceFile;
if (sourceFile !== null) {
if (importDir.symbols !== null) {
for (const importSymbol of importDir.symbols) {
switch (importSymbol.kind) {
case SyntaxKind.BoltPlainImportSymbol:
for (const scopeType of getAllSymbolKinds()) {
const scope = this.getScopeForNode(importDir, scopeType);
assert(scope !== null);
const exported = this.resolveSymbolPath(getSymbolPathFromNode(importSymbol), scope!);
if (exported !== null) {
for (const decl of exported.declarations) {
scope!.addNodeAsSymbol(this.strategy.getSymbolName(decl), decl);
}
}
}
}
}
} else {
for (const exportedNode of this.getAllExportedNodes(sourceFile)) {
const scope = this.getScopeForNode(importDir, this.strategy.getScopeType(exportedNode));
assert(scope !== null);
scope!.addNodeAsSymbol(this.strategy.getSymbolName(exportedNode), exportedNode);
}
}
}
}
}
private *getAllExportedNodes(node: SourceFile): IterableIterator<Syntax> {
for (const element of node.elements) {
if (this.strategy.hasSymbol(element)) {
if (isExported(element)) {
yield element;
}
}
}
}
public getScopeSurroundingNode(node: Syntax, kind: ScopeType): Scope | null {
assert(node.parentNode !== null);
return this.getScopeForNode(node.parentNode!, kind);
}
public getScopeForNode(node: Syntax, kind: ScopeType): Scope | null {
let source: ScopeSource = new NodeScopeSource(node);
if (!this.strategy.introducesNewScope(source, kind)) {
const sources = [...this.strategy.getNextScopeSources(source, kind)];
if (sources.length === 0) {
return null;
}
assert(sources.length === 1);
source = sources[0];
}
return new Scope(this, kind, source);
}
public resolveModulePath(path: string[], scope: Scope): Scope | null {
const stack: Scope[] = [ scope ];
// We will keep looping until we are at the topmost module of
// the package corresponding to `node`.
while (true) {
// An empty stack means we've looked everywhere but did not find a scope that contained
// the given module path.
if (stack.length === 0) {
return null;
}
let shouldSearchNextScopes = false;
let currScope = stack.pop()!;
// 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) {
const sym = currScope.getSymbol(name);
if (sym === null) {
shouldSearchNextScopes = true;
break;
}
assert(every(sym.declarations.values(), decl => decl.kind === SyntaxKind.BoltModule));
currScope = sym.scope;
}
// If the previous loop did not fail, we are done.
if (!shouldSearchNextScopes) {
scope = currScope;
break;
}
// We continue the outer loop by getting the parent module, which should be
// equivalent to getting the parent module scope.
for (const nextScope of scope.getNextScopes()) {
stack.push(nextScope);
}
}
return scope;
}
public resolveSymbolPath(path: SymbolPath, scope: Scope): SymbolInfo | null {
if (path.hasParents()) {
if (path.isAbsolute) {
// TODO
} else {
// Perform the acutal module resolution.
const resolvedScope = this.resolveModulePath(path.getParents(), scope);
// Failing to find any module means that we cannot continue, because
// it does not make sense to get the symbol of a non-existent module.
if (resolvedScope === null) {
return null;
}
scope = resolvedScope;
}
}
// Once we've handled any module path that might have been present,
// we resolve the actual symbol using a helper method.
const sym = scope.getExportedSymbol(path.name);
if (sym === null) {
return null;
}
return sym;
}
//public resolveTypeName(name: string, node: Syntax): Type | null {
// const sym = this.findSymbolInScopeOf(name, this.getScopeSurroundingNode(node));
// if (sym === null) {
// return null;
// }
// return this.getTypeOfNode(sym.declarations[0]);
//}
}

View file

@ -1,8 +1,5 @@
import { import { TypeChecker } from "../types"
TypeChecker,
Scope
} from "../checker"
import { import {
Syntax, Syntax,
@ -33,7 +30,6 @@ import {
JSSyntax, JSSyntax,
JSSourceFile, JSSourceFile,
isBoltSourceFile, isBoltSourceFile,
BoltImportDeclaration,
BoltIdentifier, BoltIdentifier,
isBoltDeclaration, isBoltDeclaration,
isBoltStatement, isBoltStatement,
@ -42,11 +38,12 @@ import {
createJSParameter, createJSParameter,
} from "../ast" } from "../ast"
import { hasPublicModifier, setOrigNodeRange } from "../util" import { setOrigNodeRange } from "../common"
import { Program, SourceFile } from "../program" import { Program } from "../program"
import { Transformer, TransformManager } from "./index" import { Transformer, TransformManager } from "./index"
import { assert } from "../util" import { assert } from "../util"
import { inject } from "../di" import { inject } from "../di"
import { isExported } from "../common"
export interface JSCompilerOptions { export interface JSCompilerOptions {
@ -191,7 +188,7 @@ export class BoltToJSTransform implements Transformer {
const params: JSParameter[] = []; const params: JSParameter[] = [];
let body: JSStatement[] = []; let body: JSStatement[] = [];
let modifiers = 0; let modifiers = 0;
if (hasPublicModifier(node)) { if (isExported(node)) {
modifiers |= JSDeclarationModifiers.IsExported;; modifiers |= JSDeclarationModifiers.IsExported;;
} }
let i = 0; let i = 0;

View file

@ -1,13 +1,8 @@
import { SourceFile } from "../program" import { SourceFile } from "../ast"
import { TransformManager } from "../transformers";
export class ConstFoldTransform { export class ConstFoldTransform {
constructor(public transformers: TransformManager) {
}
public isApplicable(node: SourceFile): boolean { public isApplicable(node: SourceFile): boolean {
return true; return true;
} }

View file

@ -8,9 +8,10 @@ import {
BoltPattern, BoltPattern,
isBoltSourceFile, isBoltSourceFile,
BoltMacroCall, BoltMacroCall,
BoltSourceFile,
} from "../ast" } from "../ast"
import { TypeChecker } from "../checker" import { TypeChecker } from "../types"
import { BoltTokenStream, Parser, isModifierKeyword } from "../parser" import { BoltTokenStream, Parser, isModifierKeyword } from "../parser"
import { Evaluator, TRUE, FALSE } from "../evaluator" import { Evaluator, TRUE, FALSE } from "../evaluator"
import { Transformer, TransformManager } from "./index" import { Transformer, TransformManager } from "./index"
@ -30,7 +31,6 @@ export class ExpandBoltTransform implements Transformer {
private toExpand: BoltMacroCall[] = []; private toExpand: BoltMacroCall[] = [];
constructor( constructor(
private transforms: TransformManager,
@inject private evaluator: Evaluator, @inject private evaluator: Evaluator,
@inject private checker: TypeChecker @inject private checker: TypeChecker
) { ) {
@ -57,8 +57,8 @@ export class ExpandBoltTransform implements Transformer {
return isBoltSourceFile(node) return isBoltSourceFile(node)
} }
public transform(node: BoltSyntax) { public transform(node: SourceFile) {
return this.expand(node); return this.expand(node as BoltSourceFile) as BoltSourceFile;
} }
private expand(node: BoltSyntax) { private expand(node: BoltSyntax) {

View file

@ -1,5 +1,7 @@
import { FastStringMap } from "./util"; import { FastStringMap, assert } from "./util";
import { SyntaxKind, Syntax, isBoltTypeExpression, BoltExpression, BoltFunctionDeclaration, BoltFunctionBodyElement, kindToString } from "./ast";
import { getSymbolPathFromNode, ScopeType, SymbolResolver } from "./resolver";
enum TypeKind { enum TypeKind {
OpaqueType, OpaqueType,
@ -8,6 +10,7 @@ enum TypeKind {
FunctionType, FunctionType,
RecordType, RecordType,
VariantType, VariantType,
UnionType,
TupleType, TupleType,
} }
@ -28,34 +31,14 @@ export class OpaqueType extends TypeBase {
kind: TypeKind.OpaqueType = TypeKind.OpaqueType; kind: TypeKind.OpaqueType = TypeKind.OpaqueType;
} }
export function isOpaqueType(value: any): value is OpaqueType {
return value.kind === TypeKind.OpaqueType;
}
export function createOpaqueType(): OpaqueType {
return new OpaqueType();
}
export class AnyType extends TypeBase { export class AnyType extends TypeBase {
kind: TypeKind.AnyType = TypeKind.AnyType; kind: TypeKind.AnyType = TypeKind.AnyType;
} }
export function createAnyType(): AnyType {
return new AnyType();
}
export function isAnyType(value: any): value is AnyType {
return value.kind === TypeKind.AnyType;
}
export class NeverType extends TypeBase { export class NeverType extends TypeBase {
kind: TypeKind.NeverType = TypeKind.NeverType; kind: TypeKind.NeverType = TypeKind.NeverType;
} }
export function isNeverType(value: any): value is NeverType {
return value instanceof NeverType;
}
export class FunctionType extends TypeBase { export class FunctionType extends TypeBase {
kind: TypeKind.FunctionType = TypeKind.FunctionType; kind: TypeKind.FunctionType = TypeKind.FunctionType;
@ -80,10 +63,6 @@ export class FunctionType extends TypeBase {
} }
export function isFunctionType(value: any): value is FunctionType {
return value instanceof FunctionType;
}
export class VariantType extends TypeBase { export class VariantType extends TypeBase {
kind: TypeKind.VariantType = TypeKind.VariantType; kind: TypeKind.VariantType = TypeKind.VariantType;
@ -98,10 +77,6 @@ export class VariantType extends TypeBase {
} }
export function createVariantType(...elementTypes: Type[]): VariantType {
return new VariantType(elementTypes);
}
export function isVariantType(value: any): value is VariantType { export function isVariantType(value: any): value is VariantType {
return value instanceof VariantType; return value instanceof VariantType;
} }
@ -144,18 +119,10 @@ export class TupleType extends TypeBase {
} }
export function createTupleType(...elementTypes: Type[]) {
return new TupleType(elementTypes);
}
export function isTupleType(value: any): value is TupleType { export function isTupleType(value: any): value is TupleType {
return value.kind === TypeKind.TupleType; return value.kind === TypeKind.TupleType;
} }
export function createVoidType() {
return createTupleType();
}
export function isVoidType(value: any) { export function isVoidType(value: any) {
return isTupleType(value) && value.elementTypes.length === 0; return isTupleType(value) && value.elementTypes.length === 0;
} }
@ -190,32 +157,94 @@ export function intersectTypes(a: Type, b: Type): Type {
return new NeverType(); return new NeverType();
} }
export type TypeInfo = never;
export function isTypeAssignable(a: Type, b: Type): boolean { interface AssignmentError {
if (isNeverType(a)) { node: Syntax;
return false;
}
if (isAnyType(b)) {
return true;
}
if (isOpaqueType(a) && isOpaqueType(b)) {
return a === b;
}
if (a.kind !== b.kind) {
return false;
}
if (isFunctionType(a) && isFunctionType(b)) {
if (a.paramTypes.length !== b.paramTypes.length) {
return false;
}
const paramCount = a.getParameterCount();
for (let i = 0; i < paramCount; i++) {
if (!isTypeAssignable(a.getParamTypeAtIndex(i), b.getParamTypeAtIndex(i))) {
return false;
}
}
return true;
}
throw new Error(`Should not get here.`);
} }
export class TypeChecker {
constructor(private resolver: SymbolResolver) {
}
public isVoid(node: Syntax): boolean {
switch (node.kind) {
case SyntaxKind.BoltTupleExpression:
return node.elements.length === 0;
default:
throw new Error(`Could not determine whether the given type resolves to the void type.`)
}
}
public *getAssignmentErrors(left: Syntax, right: Syntax): IterableIterator<AssignmentError> {
// TODO For function bodies, we can do something special.
// Sort the return types and find the largest types, eliminating types that fall under other types.
// Next, add the resulting types as type hints to `fnReturnType`.
}
public getCallableFunctions(node: BoltExpression): BoltFunctionDeclaration[] {
const resolver = this.resolver;
const results: BoltFunctionDeclaration[] = [];
visitExpression(node);
return results;
function visitExpression(node: BoltExpression) {
switch (node.kind) {
case SyntaxKind.BoltMemberExpression:
{
visitExpression(node.expression);
break;
}
case SyntaxKind.BoltQuoteExpression:
{
// TODO visit all unquote expressions
//visitExpression(node.tokens);
break;
}
case SyntaxKind.BoltCallExpression:
{
// TODO
break;
}
case SyntaxKind.BoltReferenceExpression:
{
const scope = resolver.getScopeForNode(node, ScopeType.Variable);
assert(scope !== null);
const resolvedSym = resolver.resolveSymbolPath(getSymbolPathFromNode(node), scope!);
if (resolvedSym !== null) {
for (const decl of resolvedSym.declarations) {
visitFunctionBodyElement(decl as BoltFunctionBodyElement);
}
}
break;
}
default:
throw new Error(`Unexpected node type ${kindToString(node.kind)}`);
}
}
function visitFunctionBodyElement(node: BoltFunctionBodyElement) {
switch (node.kind) {
case SyntaxKind.BoltFunctionDeclaration:
results.push(node);
break;
case SyntaxKind.BoltVariableDeclaration:
if (node.value !== null) {
visitExpression(node.value);
}
break;
default:
throw new Error(`Unexpected node type ${kindToString(node.kind)}`);
}
}
}
}

View file

@ -4,6 +4,24 @@ import * as fs from "fs"
import moment from "moment" import moment from "moment"
import chalk from "chalk" import chalk from "chalk"
export function isPowerOf(x: number, n: number):boolean {
const a = Math.log(x) / Math.log(n);
return Math.pow(a, n) == x;
}
export function every<T>(iterator: Iterator<T>, pred: (value: T) => boolean): boolean {
while (true) {
const { value, done } = iterator.next();
if (done) {
break;
}
if (!pred(value)) {
return false;
}
}
return true;
}
export function assert(test: boolean): void { export function assert(test: boolean): void {
if (!test) { if (!test) {
throw new Error(`Invariant violation: an internal sanity check failed.`); throw new Error(`Invariant violation: an internal sanity check failed.`);
@ -248,7 +266,7 @@ export interface MapLike<T> {
[key: string]: T; [key: string]: T;
} }
export type FormatArg = string | Date | number export type FormatArg = any;
export function format(message: string, data: MapLike<FormatArg>) { export function format(message: string, data: MapLike<FormatArg>) {