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:
parent
ae41362367
commit
ffb0e2a8c0
17 changed files with 6638 additions and 1484 deletions
2
Makefile
2
Makefile
|
@ -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
|
||||
|
||||
all: lib/ast.js
|
||||
bolt bundle stdlib
|
||||
bolt check stdlib
|
||||
|
||||
lib/ast.js: $(TREEGEN_FILES)
|
||||
@echo "Generating AST definitions ..."
|
||||
|
|
|
@ -92,7 +92,6 @@ node BoltSourceFile > SourceFile {
|
|||
|
||||
node BoltQualName {
|
||||
modulePath: Option<Vec<BoltIdentifier>>,
|
||||
name: BoltSymbol,
|
||||
}
|
||||
|
||||
node BoltModulePath {
|
||||
|
@ -156,11 +155,16 @@ node BoltRecordPattern > BoltPattern {
|
|||
node BoltExpression;
|
||||
|
||||
node BoltQuoteExpression > BoltExpression {
|
||||
tokens: Vec<Token>,
|
||||
tokens: Vec<Token | BoltExpression>,
|
||||
}
|
||||
|
||||
node BoltTupleExpression > BoltExpression {
|
||||
elements: Vec<BoltExpression>,
|
||||
}
|
||||
|
||||
node BoltReferenceExpression > BoltExpression {
|
||||
name: BoltQualName,
|
||||
modulePath: Option<BoltModulePath>,
|
||||
name: BoltSymbol,
|
||||
}
|
||||
|
||||
node BoltMemberExpression > BoltExpression {
|
||||
|
|
5968
src/ast.d.ts
vendored
5968
src/ast.d.ts
vendored
File diff suppressed because it is too large
Load diff
|
@ -17,7 +17,6 @@ import { Package } from "../common"
|
|||
import {hasOwnProperty} 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 {BoltSourceFileModifiers} from "../ast"
|
||||
|
||||
//global.print = function (value: any) {
|
||||
// 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(
|
||||
|
||||
'bundle [files..]',
|
||||
|
|
883
src/checker.ts
883
src/checker.ts
|
@ -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
149
src/checks.ts
Normal 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 },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -10,11 +10,11 @@ import {
|
|||
isBoltPunctuated,
|
||||
SourceFile,
|
||||
BoltSourceFile,
|
||||
BoltSourceFileModifiers,
|
||||
isSourceFile
|
||||
isSourceFile,
|
||||
BoltSyntax,
|
||||
BoltModifiers
|
||||
} from "./ast";
|
||||
import { BOLT_SUPPORTED_LANGUAGES } from "./constants"
|
||||
import {emit} from "./emitter";
|
||||
import {FastStringMap, enumerate, escapeChar, assert} from "./util";
|
||||
import {TextSpan, TextPos, TextFile} from "./text";
|
||||
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 {
|
||||
const kindStr = kindToString(node.kind);
|
||||
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 {
|
||||
Prefix,
|
||||
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 {
|
||||
switch (kind) {
|
||||
case SyntaxKind.BoltImportKeyword:
|
||||
|
|
|
@ -98,7 +98,7 @@ export class Emitter {
|
|||
/**
|
||||
* 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();
|
||||
return emitter.emit(node);
|
||||
}
|
||||
|
|
195
src/evaluator.ts
195
src/evaluator.ts
|
@ -1,77 +1,51 @@
|
|||
|
||||
import { Syntax, SyntaxKind, Expr, isNode, BoltQualName } from "./ast"
|
||||
import { TypeChecker, Type, RecordType, PrimType, boolType } from "./checker"
|
||||
import { FastStringMap } from "./util"
|
||||
import { Syntax, SyntaxKind, BoltQualName, BoltExpression, kindToString, BoltSyntax, isBoltStatement } from "./ast"
|
||||
import { FastStringMap, assert } from "./util"
|
||||
import { emitNode } from "./emitter";
|
||||
import { Type, TypeChecker, RecordType } from "./types";
|
||||
|
||||
export interface Value {
|
||||
type: Type;
|
||||
readonly type?: Type;
|
||||
data: ValueData;
|
||||
}
|
||||
|
||||
export class PrimValue implements Value {
|
||||
class Record {
|
||||
|
||||
constructor(
|
||||
public type: PrimType,
|
||||
public value: any
|
||||
) {
|
||||
private fields: Map<string, Value>;
|
||||
|
||||
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);
|
||||
export const FALSE = new PrimValue(boolType, false);
|
||||
|
||||
export abstract class RecordValue implements Value {
|
||||
|
||||
abstract type: RecordType;
|
||||
|
||||
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];
|
||||
}
|
||||
type ValueData
|
||||
= string
|
||||
| undefined
|
||||
| boolean
|
||||
| number
|
||||
| bigint
|
||||
| Record
|
||||
|
||||
class Environment {
|
||||
|
||||
private symbols = FastStringMap<string, Value>();
|
||||
private symbols = new FastStringMap<string, Value>();
|
||||
|
||||
constructor(public parentEnv: Environment | null = null) {
|
||||
|
||||
|
@ -81,20 +55,22 @@ class Environment {
|
|||
if (name in this.symbols) {
|
||||
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) {
|
||||
if (!(name in this.symbols)) {
|
||||
if (!this.symbols.has(name)) {
|
||||
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) {
|
||||
let curr = this as Environment;
|
||||
while (true) {
|
||||
if (name in curr.symbols) {
|
||||
return curr.symbols[name];
|
||||
if (this.symbols.has(name)) {
|
||||
return curr.symbols.get(name);
|
||||
}
|
||||
if (curr.parentEnv === null) {
|
||||
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 {
|
||||
|
||||
constructor(public checker: TypeChecker) {
|
||||
|
||||
}
|
||||
|
||||
match(value: Value, node: Syntax) {
|
||||
private performPatternMatch(value: Value, node: Syntax, env: Environment): boolean {
|
||||
|
||||
switch (node.kind) {
|
||||
|
||||
case SyntaxKind.RecordPatt:
|
||||
for (const field of node.fields) {
|
||||
if (!this.match((value as RecordValue).getValueOfField(field.name.text), field.pattern)) {
|
||||
case SyntaxKind.BoltBindPattern:
|
||||
{
|
||||
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;
|
||||
}
|
||||
record.deleteField(fieldPatt.name!.text);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case SyntaxKind.TypePatt:
|
||||
return value.type === this.checker.getTypeOfNode(node)
|
||||
case SyntaxKind.BoltTypePattern:
|
||||
{
|
||||
const expectedType = this.checker.getTypeOfNode(node.type);
|
||||
if (!this.checker.isTypeAssignableTo(expectedType, getTypeOfValue(value))) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
switch (node.kind) {
|
||||
|
@ -147,32 +161,31 @@ export class Evaluator {
|
|||
case SyntaxKind.BoltSourceFile:
|
||||
case SyntaxKind.BoltModule:
|
||||
for (const element of node.elements) {
|
||||
if (isBoltStatement(element)) {
|
||||
this.eval(element, env);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return { data: undefined }
|
||||
|
||||
case SyntaxKind.BoltReferenceTypeExpression:
|
||||
// FIXME
|
||||
return env.lookup(node.name.name.text);
|
||||
|
||||
case SyntaxKind.BoltRecordDeclaration:
|
||||
case SyntaxKind.BoltFunctionDeclaration:
|
||||
break;
|
||||
case SyntaxKind.BoltReferenceExpression:
|
||||
return env.lookup(mangle(node.name));
|
||||
|
||||
case SyntaxKind.BoltMatchExpression:
|
||||
const value = this.eval(node.value, env);
|
||||
for (const [pattern, result] of node.arms) {
|
||||
if (this.match(value, pattern)) {
|
||||
return this.eval(result as Expr, env)
|
||||
for (const matchArm of node.arms) {
|
||||
const matchArmEnv = new Environment(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:
|
||||
return new PrimValue(this.checker.getTypeOfNode(node), node.value)
|
||||
return node.value;
|
||||
|
||||
default:
|
||||
throw new Error(`Could not evaluate node ${SyntaxKind[node.kind]}`)
|
||||
throw new Error(`Could not evaluate node ${kindToString(node.kind)}`)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -5,20 +5,20 @@ import { now } from "microtime"
|
|||
import { EventEmitter } from "events"
|
||||
|
||||
import { Program } from "./program"
|
||||
import { TypeChecker } from "./checker"
|
||||
import { Evaluator } from "./evaluator"
|
||||
import { emit } from "./emitter"
|
||||
import { Syntax, BoltSourceFile, SourceFile } from "./ast"
|
||||
import { upsearchSync, FastStringMap, getFileStem, getLanguage } from "./util"
|
||||
import { Package } from "./package"
|
||||
import { emitNode } from "./emitter"
|
||||
import { Syntax, BoltSourceFile, SourceFile, NodeVisitor } from "./ast"
|
||||
import { getFileStem, MapLike } from "./util"
|
||||
import { verbose, memoize } from "./util"
|
||||
import { Container } from "./di"
|
||||
import { Container, Newable } from "./di"
|
||||
import ExpandBoltTransform from "./transforms/expand"
|
||||
import CompileBoltToJSTransform from "./transforms/boltToJS"
|
||||
import ConstFoldTransform from "./transforms/constFold"
|
||||
import EliminateModulesTransform from "./transforms/eliminateModules"
|
||||
import { TransformManager } from "./transforms/index"
|
||||
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> = {
|
||||
'JS': '.mjs',
|
||||
|
@ -33,7 +33,7 @@ interface TimingInfo {
|
|||
|
||||
class Timing extends EventEmitter {
|
||||
|
||||
private runningTasks: FastStringMap<TimingInfo> = Object.create(null);
|
||||
private runningTasks: MapLike<TimingInfo> = Object.create(null);
|
||||
|
||||
public start(name: string) {
|
||||
if (this.runningTasks[name] !== undefined) {
|
||||
|
@ -61,53 +61,67 @@ class Timing extends EventEmitter {
|
|||
|
||||
export class Frontend {
|
||||
|
||||
public evaluator: Evaluator;
|
||||
public checker: TypeChecker;
|
||||
//public resolver = new SymbolResolver();
|
||||
//public evaluator = new Evaluator(this.resolver);
|
||||
public diagnostics: DiagnosticPrinter;
|
||||
public timing: Timing;
|
||||
|
||||
private container = new Container();
|
||||
|
||||
constructor() {
|
||||
this.diagnostics = new DiagnosticPrinter();
|
||||
this.timing = new Timing();
|
||||
}
|
||||
|
||||
@memoize
|
||||
public getPackage(filepath: string) {
|
||||
const file = this.getTextFile(filepath)
|
||||
const projectFile = upsearchSync('Boltfile', path.dirname(file.fullPath));
|
||||
if (projectFile === null) {
|
||||
return null;
|
||||
}
|
||||
const projectDir = path.resolve(path.dirname(projectFile));
|
||||
return new Package(projectDir);
|
||||
}
|
||||
//@memoize(filepath => path.resolve(filepath))
|
||||
//public getPackage(filepath: string) {
|
||||
// const file = this.getTextFile(filepath)
|
||||
// const projectFile = upsearchSync('Boltfile', path.dirname(file.fullPath));
|
||||
// if (projectFile === null) {
|
||||
// return null;
|
||||
// }
|
||||
// const projectDir = path.resolve(path.dirname(projectFile));
|
||||
// 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()) {
|
||||
checker.registerSourceFile(sourceFile as BoltSourceFile);
|
||||
resolver.registerSourceFile(sourceFile as BoltSourceFile);
|
||||
}
|
||||
for (const sourceFile of program.getAllSourceFiles()) {
|
||||
checker.checkSourceFile(sourceFile as BoltSourceFile);
|
||||
sourceFile.visit(checkers)
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
const resolver = new SymbolResolver(program, new BoltSymbolResolutionStrategy);
|
||||
for (const sourceFile of program.getAllSourceFiles()) {
|
||||
resolver.registerSourceFile(sourceFile as BoltSourceFile);
|
||||
}
|
||||
const transforms = new TransformManager(container);
|
||||
container.bindSelf(transforms);
|
||||
container.bindSelf(program);
|
||||
container.bindSelf(resolver);
|
||||
|
||||
switch (target) {
|
||||
|
||||
case "JS":
|
||||
const transforms = new TransformManager(this.container);
|
||||
transforms.register(ExpandBoltTransform);
|
||||
transforms.register(CompileBoltToJSTransform);
|
||||
transforms.register(ConstFoldTransform);
|
||||
|
@ -121,7 +135,7 @@ export class Frontend {
|
|||
|
||||
for (const sourceFile of program.getAllSourceFiles()) {
|
||||
fs.mkdirp('.bolt-work');
|
||||
fs.writeFileSync(this.mapToTargetFile(sourceFile), emit(sourceFile), 'utf8');
|
||||
fs.writeFileSync(this.mapToTargetFile(sourceFile), emitNode(sourceFile), 'utf8');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -79,6 +79,7 @@ import {
|
|||
createBoltMemberExpression,
|
||||
createBoltModulePath,
|
||||
BoltModulePath,
|
||||
isBoltSymbol,
|
||||
} from "./ast"
|
||||
|
||||
import { parseForeignLanguage } from "./foreign"
|
||||
|
@ -108,16 +109,23 @@ function assertNoTokens(tokens: BoltTokenStream) {
|
|||
}
|
||||
}
|
||||
|
||||
const KIND_EXPRESSION_T0 = [
|
||||
SyntaxKind.BoltStringLiteral,
|
||||
SyntaxKind.BoltIntegerLiteral,
|
||||
const KIND_SYMBOL = [
|
||||
SyntaxKind.BoltIdentifier,
|
||||
SyntaxKind.BoltOperator,
|
||||
SyntaxKind.BoltVBar,
|
||||
SyntaxKind.BoltLtSign,
|
||||
SyntaxKind.BoltGtSign,
|
||||
];
|
||||
|
||||
const KIND_EXPRESSION_T0 = uniq([
|
||||
SyntaxKind.BoltStringLiteral,
|
||||
SyntaxKind.BoltIntegerLiteral,
|
||||
SyntaxKind.BoltOperator,
|
||||
SyntaxKind.BoltMatchKeyword,
|
||||
SyntaxKind.BoltQuoteKeyword,
|
||||
SyntaxKind.BoltYieldKeyword,
|
||||
]
|
||||
...KIND_SYMBOL,
|
||||
])
|
||||
|
||||
const KIND_STATEMENT_T0 = uniq([
|
||||
SyntaxKind.BoltReturnKeyword,
|
||||
|
@ -194,7 +202,7 @@ export class Parser {
|
|||
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 elements = [];
|
||||
|
@ -381,7 +389,7 @@ export class Parser {
|
|||
|
||||
public parseReferenceTypeExpression(tokens: BoltTokenStream): BoltReferenceTypeExpression {
|
||||
|
||||
const path = this.parseNamespacePath(tokens)
|
||||
const path = this.parseModulePath(tokens)
|
||||
const t1 = tokens.peek();
|
||||
|
||||
let typeArgs: BoltTypeExpression[] | null = null;
|
||||
|
@ -431,7 +439,7 @@ export class Parser {
|
|||
if (!isBoltOperatorLike(t0)) {
|
||||
break;
|
||||
}
|
||||
let desc0 = this.typeOperatorTable.lookup(emit(t0));
|
||||
let desc0 = this.typeOperatorTable.lookup(emitNode(t0));
|
||||
if (desc0 === null || desc0.arity !== 2 || desc0.precedence < minPrecedence) {
|
||||
break;
|
||||
}
|
||||
|
@ -442,7 +450,7 @@ export class Parser {
|
|||
if (!isBoltOperatorLike(t1.kind)) {
|
||||
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)) {
|
||||
break;
|
||||
}
|
||||
|
@ -512,9 +520,35 @@ export class Parser {
|
|||
}
|
||||
|
||||
public parseReferenceExpression(tokens: BoltTokenStream): BoltReferenceExpression {
|
||||
const name = this.parseNamespacePath(tokens);
|
||||
const node = createBoltReferenceExpression(name);
|
||||
setOrigNodeRange(node, name, name);
|
||||
|
||||
const firstToken = tokens.peek();
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -930,7 +964,7 @@ export class Parser {
|
|||
}
|
||||
|
||||
// FIXME should fail to parse absolute paths
|
||||
const name = this.parseNamespacePath(tokens);
|
||||
const name = this.parseModulePath(tokens);
|
||||
|
||||
const t1 = tokens.get();
|
||||
if (t1.kind !== SyntaxKind.BoltBraced) {
|
||||
|
@ -1526,7 +1560,7 @@ import { Scanner } from "./scanner"
|
|||
import { TextFile, TextSpan, TextPos } from "./text"
|
||||
import * as fs from "fs"
|
||||
import {JSScanner} from "./foreign/js/scanner";
|
||||
import {emit} from "./emitter";
|
||||
import {emitNode} from "./emitter";
|
||||
|
||||
export function parseSourceFile(filepath: string, pkg: Package): BoltSourceFile {
|
||||
const file = new TextFile(filepath);
|
||||
|
|
497
src/resolver.ts
Normal file
497
src/resolver.ts
Normal 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]);
|
||||
//}
|
||||
|
||||
}
|
|
@ -1,8 +1,5 @@
|
|||
|
||||
import {
|
||||
TypeChecker,
|
||||
Scope
|
||||
} from "../checker"
|
||||
import { TypeChecker } from "../types"
|
||||
|
||||
import {
|
||||
Syntax,
|
||||
|
@ -33,7 +30,6 @@ import {
|
|||
JSSyntax,
|
||||
JSSourceFile,
|
||||
isBoltSourceFile,
|
||||
BoltImportDeclaration,
|
||||
BoltIdentifier,
|
||||
isBoltDeclaration,
|
||||
isBoltStatement,
|
||||
|
@ -42,11 +38,12 @@ import {
|
|||
createJSParameter,
|
||||
} from "../ast"
|
||||
|
||||
import { hasPublicModifier, setOrigNodeRange } from "../util"
|
||||
import { Program, SourceFile } from "../program"
|
||||
import { setOrigNodeRange } from "../common"
|
||||
import { Program } from "../program"
|
||||
import { Transformer, TransformManager } from "./index"
|
||||
import { assert } from "../util"
|
||||
import { inject } from "../di"
|
||||
import { isExported } from "../common"
|
||||
|
||||
export interface JSCompilerOptions {
|
||||
|
||||
|
@ -191,7 +188,7 @@ export class BoltToJSTransform implements Transformer {
|
|||
const params: JSParameter[] = [];
|
||||
let body: JSStatement[] = [];
|
||||
let modifiers = 0;
|
||||
if (hasPublicModifier(node)) {
|
||||
if (isExported(node)) {
|
||||
modifiers |= JSDeclarationModifiers.IsExported;;
|
||||
}
|
||||
let i = 0;
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
|
||||
import { SourceFile } from "../program"
|
||||
import { TransformManager } from "../transformers";
|
||||
import { SourceFile } from "../ast"
|
||||
|
||||
export class ConstFoldTransform {
|
||||
|
||||
constructor(public transformers: TransformManager) {
|
||||
|
||||
}
|
||||
|
||||
public isApplicable(node: SourceFile): boolean {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -8,9 +8,10 @@ import {
|
|||
BoltPattern,
|
||||
isBoltSourceFile,
|
||||
BoltMacroCall,
|
||||
BoltSourceFile,
|
||||
} from "../ast"
|
||||
|
||||
import { TypeChecker } from "../checker"
|
||||
import { TypeChecker } from "../types"
|
||||
import { BoltTokenStream, Parser, isModifierKeyword } from "../parser"
|
||||
import { Evaluator, TRUE, FALSE } from "../evaluator"
|
||||
import { Transformer, TransformManager } from "./index"
|
||||
|
@ -30,7 +31,6 @@ export class ExpandBoltTransform implements Transformer {
|
|||
private toExpand: BoltMacroCall[] = [];
|
||||
|
||||
constructor(
|
||||
private transforms: TransformManager,
|
||||
@inject private evaluator: Evaluator,
|
||||
@inject private checker: TypeChecker
|
||||
) {
|
||||
|
@ -57,8 +57,8 @@ export class ExpandBoltTransform implements Transformer {
|
|||
return isBoltSourceFile(node)
|
||||
}
|
||||
|
||||
public transform(node: BoltSyntax) {
|
||||
return this.expand(node);
|
||||
public transform(node: SourceFile) {
|
||||
return this.expand(node as BoltSourceFile) as BoltSourceFile;
|
||||
}
|
||||
|
||||
private expand(node: BoltSyntax) {
|
||||
|
|
155
src/types.ts
155
src/types.ts
|
@ -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 {
|
||||
OpaqueType,
|
||||
|
@ -8,6 +10,7 @@ enum TypeKind {
|
|||
FunctionType,
|
||||
RecordType,
|
||||
VariantType,
|
||||
UnionType,
|
||||
TupleType,
|
||||
}
|
||||
|
||||
|
@ -28,34 +31,14 @@ export class OpaqueType extends TypeBase {
|
|||
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 {
|
||||
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 {
|
||||
kind: TypeKind.NeverType = TypeKind.NeverType;
|
||||
}
|
||||
|
||||
export function isNeverType(value: any): value is NeverType {
|
||||
return value instanceof NeverType;
|
||||
}
|
||||
|
||||
export class FunctionType extends TypeBase {
|
||||
|
||||
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 {
|
||||
|
||||
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 {
|
||||
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 {
|
||||
return value.kind === TypeKind.TupleType;
|
||||
}
|
||||
|
||||
export function createVoidType() {
|
||||
return createTupleType();
|
||||
}
|
||||
|
||||
export function isVoidType(value: any) {
|
||||
return isTupleType(value) && value.elementTypes.length === 0;
|
||||
}
|
||||
|
@ -190,32 +157,94 @@ export function intersectTypes(a: Type, b: Type): Type {
|
|||
return new NeverType();
|
||||
}
|
||||
|
||||
export type TypeInfo = never;
|
||||
|
||||
export function isTypeAssignable(a: Type, b: Type): boolean {
|
||||
if (isNeverType(a)) {
|
||||
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.`);
|
||||
interface AssignmentError {
|
||||
node: Syntax;
|
||||
}
|
||||
|
||||
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)}`);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
20
src/util.ts
20
src/util.ts
|
@ -4,6 +4,24 @@ import * as fs from "fs"
|
|||
import moment from "moment"
|
||||
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 {
|
||||
if (!test) {
|
||||
throw new Error(`Invariant violation: an internal sanity check failed.`);
|
||||
|
@ -248,7 +266,7 @@ export interface MapLike<T> {
|
|||
[key: string]: T;
|
||||
}
|
||||
|
||||
export type FormatArg = string | Date | number
|
||||
export type FormatArg = any;
|
||||
|
||||
export function format(message: string, data: MapLike<FormatArg>) {
|
||||
|
||||
|
|
Loading…
Reference in a new issue