diff --git a/src/ast.d.ts b/src/ast.d.ts index 694273749..915b1d5b3 100644 --- a/src/ast.d.ts +++ b/src/ast.d.ts @@ -61,79 +61,79 @@ export const enum SyntaxKind { BoltResumeStatement = 69, BoltExpressionStatement = 70, BoltParameter = 71, - BoltModule = 74, - BoltFunctionDeclaration = 76, - BoltVariableDeclaration = 77, - BoltPlainImportSymbol = 79, - BoltImportDeclaration = 80, - BoltTraitDeclaration = 81, - BoltImplDeclaration = 82, - BoltTypeAliasDeclaration = 83, - BoltRecordField = 85, - BoltRecordDeclaration = 86, - BoltMacroCall = 88, - JSOperator = 91, - JSIdentifier = 92, - JSString = 93, - JSInteger = 94, - JSFromKeyword = 95, - JSReturnKeyword = 96, - JSTryKeyword = 97, - JSFinallyKeyword = 98, - JSCatchKeyword = 99, - JSImportKeyword = 100, - JSAsKeyword = 101, - JSConstKeyword = 102, - JSLetKeyword = 103, - JSExportKeyword = 104, - JSFunctionKeyword = 105, - JSWhileKeyword = 106, - JSForKeyword = 107, - JSCloseBrace = 108, - JSCloseBracket = 109, - JSCloseParen = 110, - JSOpenBrace = 111, - JSOpenBracket = 112, - JSOpenParen = 113, - JSSemi = 114, - JSComma = 115, - JSDot = 116, - JSDotDotDot = 117, - JSMulOp = 118, - JSAddOp = 119, - JSDivOp = 120, - JSSubOp = 121, - JSLtOp = 122, - JSGtOp = 123, - JSBOrOp = 124, - JSBXorOp = 125, - JSBAndOp = 126, - JSBNotOp = 127, - JSNotOp = 128, - JSBindPattern = 130, - JSConstantExpression = 132, - JSMemberExpression = 133, - JSCallExpression = 134, - JSBinaryExpression = 135, - JSUnaryExpression = 136, - JSNewExpression = 137, - JSSequenceExpression = 138, - JSConditionalExpression = 139, - JSLiteralExpression = 141, - JSReferenceExpression = 142, - JSCatchBlock = 145, - JSTryCatchStatement = 146, - JSExpressionStatement = 147, - JSConditionalStatement = 148, - JSReturnStatement = 149, - JSParameter = 150, - JSImportStarBinding = 154, - JSImportAsBinding = 155, - JSImportDeclaration = 156, - JSFunctionDeclaration = 157, - JSArrowFunctionDeclaration = 158, - JSLetDeclaration = 159, - JSSourceFile = 160, + BoltModule = 75, + BoltFunctionDeclaration = 77, + BoltVariableDeclaration = 78, + BoltPlainImportSymbol = 80, + BoltImportDeclaration = 81, + BoltTraitDeclaration = 82, + BoltImplDeclaration = 83, + BoltTypeAliasDeclaration = 84, + BoltRecordField = 86, + BoltRecordDeclaration = 87, + BoltMacroCall = 89, + JSOperator = 92, + JSIdentifier = 93, + JSString = 94, + JSInteger = 95, + JSFromKeyword = 96, + JSReturnKeyword = 97, + JSTryKeyword = 98, + JSFinallyKeyword = 99, + JSCatchKeyword = 100, + JSImportKeyword = 101, + JSAsKeyword = 102, + JSConstKeyword = 103, + JSLetKeyword = 104, + JSExportKeyword = 105, + JSFunctionKeyword = 106, + JSWhileKeyword = 107, + JSForKeyword = 108, + JSCloseBrace = 109, + JSCloseBracket = 110, + JSCloseParen = 111, + JSOpenBrace = 112, + JSOpenBracket = 113, + JSOpenParen = 114, + JSSemi = 115, + JSComma = 116, + JSDot = 117, + JSDotDotDot = 118, + JSMulOp = 119, + JSAddOp = 120, + JSDivOp = 121, + JSSubOp = 122, + JSLtOp = 123, + JSGtOp = 124, + JSBOrOp = 125, + JSBXorOp = 126, + JSBAndOp = 127, + JSBNotOp = 128, + JSNotOp = 129, + JSBindPattern = 131, + JSConstantExpression = 133, + JSMemberExpression = 134, + JSCallExpression = 135, + JSBinaryExpression = 136, + JSUnaryExpression = 137, + JSNewExpression = 138, + JSSequenceExpression = 139, + JSConditionalExpression = 140, + JSLiteralExpression = 142, + JSReferenceExpression = 143, + JSCatchBlock = 146, + JSTryCatchStatement = 147, + JSExpressionStatement = 148, + JSConditionalStatement = 149, + JSReturnStatement = 150, + JSParameter = 151, + JSImportStarBinding = 155, + JSImportAsBinding = 156, + JSImportDeclaration = 157, + JSFunctionDeclaration = 158, + JSArrowFunctionDeclaration = 159, + JSLetDeclaration = 160, + JSSourceFile = 161, } @@ -387,7 +387,7 @@ export interface BoltSourceFile extends SyntaxBase { export interface BoltQualName extends SyntaxBase { kind: SyntaxKind.BoltQualName; - modulePath: BoltIdentifier[]; + modulePath: BoltIdentifier[] | null; name: BoltSymbol; } @@ -507,7 +507,7 @@ export interface BoltCaseExpression extends SyntaxBase { kind: SyntaxKind.BoltBlockExpression; - statements: BoltStatement[]; + elements: BoltFunctionBodyElement[]; } export interface BoltConstantExpression extends SyntaxBase { @@ -557,6 +557,12 @@ export type BoltDeclaration | BoltMacroCall +export type BoltTypeDeclaration + = BoltTraitDeclaration + | BoltTypeAliasDeclaration + | BoltRecordDeclaration + + export const enum BoltDeclarationModifiers { Mutable = 1,Public = 2,IsType = 4,IsForeign = 8,} @@ -648,7 +654,7 @@ export interface BoltRecordField extends SyntaxBase export interface BoltRecordDeclaration extends SyntaxBase { kind: SyntaxKind.BoltRecordDeclaration; modifiers: BoltDeclarationModifiers; - name: BoltQualName; + name: BoltIdentifier; typeParms: BoltTypeParameter[] | null; members: BoltRecordMember[] | null; } @@ -667,6 +673,9 @@ export type BoltSourceElement | BoltTypeAliasDeclaration | BoltRecordDeclaration | BoltMacroCall + | BoltTraitDeclaration + | BoltTypeAliasDeclaration + | BoltRecordDeclaration export interface BoltMacroCall extends SyntaxBase { @@ -1379,7 +1388,7 @@ export function createBoltParenthesized(text: string, span?: TextSpan | null): B export function createBoltBraced(text: string, span?: TextSpan | null): BoltBraced; export function createBoltBracketed(text: string, span?: TextSpan | null): BoltBracketed; export function createBoltSourceFile(elements: BoltSourceElement[], span?: TextSpan | null): BoltSourceFile; -export function createBoltQualName(modulePath: BoltIdentifier[], name: BoltSymbol, span?: TextSpan | null): BoltQualName; +export function createBoltQualName(modulePath: BoltIdentifier[] | null, name: BoltSymbol, span?: TextSpan | null): BoltQualName; export function createBoltReferenceTypeExpression(name: BoltQualName, arguments: BoltTypeExpression[] | null, span?: TextSpan | null): BoltReferenceTypeExpression; export function createBoltTypeParameter(index: number, name: BoltIdentifier, defaultType: BoltTypeExpression | null, span?: TextSpan | null): BoltTypeParameter; export function createBoltBindPattern(name: BoltIdentifier, span?: TextSpan | null): BoltBindPattern; @@ -1396,7 +1405,7 @@ export function createBoltMatchArm(pattern: BoltPattern, body: BoltExpression, s export function createBoltMatchExpression(value: BoltExpression, arms: BoltMatchArm[], span?: TextSpan | null): BoltMatchExpression; export function createBoltCase(test: BoltExpression, result: BoltExpression, span?: TextSpan | null): BoltCase; export function createBoltCaseExpression(cases: BoltCase[], span?: TextSpan | null): BoltCaseExpression; -export function createBoltBlockExpression(statements: BoltStatement[], span?: TextSpan | null): BoltBlockExpression; +export function createBoltBlockExpression(elements: BoltFunctionBodyElement[], span?: TextSpan | null): BoltBlockExpression; export function createBoltConstantExpression(value: BoltValue, span?: TextSpan | null): BoltConstantExpression; export function createBoltReturnStatement(value: BoltExpression | null, span?: TextSpan | null): BoltReturnStatement; export function createBoltResumeStatement(value: BoltExpression, span?: TextSpan | null): BoltResumeStatement; @@ -1411,7 +1420,7 @@ export function createBoltTraitDeclaration(modifiers: BoltDeclarationModifiers, export function createBoltImplDeclaration(modifiers: BoltDeclarationModifiers, name: BoltIdentifier, trait: BoltTypeExpression, typeParams: BoltTypeParameter[] | null, elements: BoltDeclaration[], span?: TextSpan | null): BoltImplDeclaration; export function createBoltTypeAliasDeclaration(modifiers: BoltDeclarationModifiers, name: BoltIdentifier, typeParams: BoltTypeParameter[] | null, typeExpr: BoltTypeExpression, span?: TextSpan | null): BoltTypeAliasDeclaration; export function createBoltRecordField(name: BoltIdentifier, type: BoltTypeExpression, span?: TextSpan | null): BoltRecordField; -export function createBoltRecordDeclaration(modifiers: BoltDeclarationModifiers, name: BoltQualName, typeParms: BoltTypeParameter[] | null, members: BoltRecordMember[] | null, span?: TextSpan | null): BoltRecordDeclaration; +export function createBoltRecordDeclaration(modifiers: BoltDeclarationModifiers, name: BoltIdentifier, typeParms: BoltTypeParameter[] | null, members: BoltRecordMember[] | null, span?: TextSpan | null): BoltRecordDeclaration; export function createBoltMacroCall(name: BoltIdentifier, text: string, span?: TextSpan | null): BoltMacroCall; export function createJSOperator(text: string, span?: TextSpan | null): JSOperator; export function createJSIdentifier(text: string, span?: TextSpan | null): JSIdentifier; @@ -1546,6 +1555,7 @@ export function isBoltResumeStatement(value: any): value is BoltResumeStatement; export function isBoltExpressionStatement(value: any): value is BoltExpressionStatement; export function isBoltParameter(value: any): value is BoltParameter; export function isBoltDeclaration(value: any): value is BoltDeclaration; +export function isBoltTypeDeclaration(value: any): value is BoltTypeDeclaration; export function isBoltModule(value: any): value is BoltModule; export function isBoltFunctionBodyElement(value: any): value is BoltFunctionBodyElement; export function isBoltFunctionDeclaration(value: any): value is BoltFunctionDeclaration; diff --git a/src/checker.ts b/src/checker.ts index 86dff2822..5028c534d 100644 --- a/src/checker.ts +++ b/src/checker.ts @@ -1,343 +1,291 @@ +/** + * + * ``` + * 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 { - Syntax, - kindToString, - SyntaxKind, - BoltImportDeclaration, - BoltPattern, - isBoltTypeAliasDeclaration, - BoltFunctionBodyElement, -} from "./ast" - -import { FastStringMap, getFullTextOfQualName } from "./util" - -export class Type { - -} - -export class PrimType extends Type { - -} - -export class FunctionType extends Type { - - constructor( - public paramTypes: Type[], - public returnType: Type, - ) { - super(); - } - -} - -export class VariantType extends Type { - - constructor(public elementTypes: Type[]) { - super(); - } - -} - -export const stringType = new PrimType() -export const intType = new PrimType() -export const boolType = new PrimType() -export const voidType = new PrimType() -export const anyType = new PrimType() -export const noneType = new PrimType(); - -export class RecordType { - - private fieldTypes = new FastStringMap(); - - constructor( - iterable: IterableIterator<[string, Type]>, - ) { - for (const [name, type] of iterable) { - this.fieldTypes.set(name, type); - } - } - - hasField(name: string) { - return name in this.fieldTypes; - } - - getTypeOfField(name: string) { - return this.fieldTypes.get(name); - } - -} +import {Syntax, SyntaxKind, BoltReferenceExpression, BoltDeclaration, BoltSourceFile, BoltSyntax, BoltReferenceTypeExpression, BoltTypeDeclaration} from "./ast"; +import {FastStringMap} from "./util"; +import {DiagnosticPrinter, E_TYPES_NOT_ASSIGNABLE, E_TYPE_DECLARATION_NOT_FOUND, E_DECLARATION_NOT_FOUND} from "./diagnostics"; interface SymbolInfo { - type: Type | null; - definitions: Syntax[]; + declarations: BoltDeclaration[]; } -export class Scope { - - private symbolsByLocalName = new FastStringMap(); - - constructor( - public originatingNode: Syntax, - public parentScope?: Scope | null - ) { - - } - - public getSymbolNamed(name: string): SymbolInfo | null { - let currScope: Scope | null = this; - while (true) { - if (currScope.symbolsByLocalName.has(name)) { - return currScope.symbolsByLocalName.get(name); - } - currScope = currScope.parentScope; - if (currScope === null) { - break; - } - } - return null; - } - - public getTypeNamed(name: string): Type | null { - const sym = this.getSymbolNamed(name); - if (sym === null || !introducesNewType(sym.definitions[0].kind)) { - return null; - } - return sym.type!; - } - -} - -function* map(iterable: Iterable, proc: (value: T) => R): IterableIterator { - const iterator = iterable[Symbol.iterator](); - while (true) { - let { done, value }= iterator.next(); - if (done) { - break - } - yield proc(value) - } -} - -function introducesNewType(kind: SyntaxKind): boolean { - return kind === SyntaxKind.BoltRecordDeclaration - || kind === SyntaxKind.BoltTypeAliasDeclaration; +interface TypeSymbolInfo { + declarations: BoltTypeDeclaration[]; } function introducesNewScope(kind: SyntaxKind): boolean { - return kind === SyntaxKind.BoltFunctionDeclaration + return kind === SyntaxKind.BoltSourceFile + || kind === SyntaxKind.BoltModule + || kind === SyntaxKind.BoltFunctionDeclaration + || kind === SyntaxKind.BoltBlockExpression; +} + +function introducesNewTypeScope(kind: SyntaxKind): boolean { + return kind === SyntaxKind.BoltModule || kind === SyntaxKind.BoltSourceFile; } -function getFullName(node: Syntax) { - let out = [] - let curr: Syntax | null = node; - while (true) { - switch (curr.kind) { - case SyntaxKind.BoltIdentifier: - out.unshift(curr.text) - break; - case SyntaxKind.BoltModule: - out.unshift(getFullTextOfQualName(curr.name)); - break; - case SyntaxKind.BoltRecordDeclaration: - out.unshift(getFullTextOfQualName(curr.name)) - break; - } - curr = curr.parentNode; - if (curr === null) { - break; - } - } - return out.join('.'); +type Scope = unknown; +type TypeScope = unknown; + +function createSymbol(node: BoltDeclaration): SymbolInfo { + return { declarations: [ node ] }; +} + +function createTypeSymbol(node: BoltTypeDeclaration): TypeSymbolInfo { + return { declarations: [ node ] }; } export class TypeChecker { - private symbols = new FastStringMap(); - private types = new Map(); - private scopes = new Map(); + constructor(private diagnostics: DiagnosticPrinter) { - private inferTypeFromUsage(bindings: BoltPattern, body: BoltFunctionBodyElement[]) { - return anyType; } - private getTypeOfBody(body: BoltFunctionBodyElement[]) { - return anyType; + private symbols = new FastStringMap(); + private typeSymbols = new FastStringMap(); + + public checkSourceFile(node: BoltSourceFile): void { + + const refExps = node.findAllChildrenOfKind(SyntaxKind.BoltReferenceExpression); + for (const refExp of refExps) { + if (this.resolveReferenceExpression(refExp) === null) { + this.diagnostics.add({ + message: E_DECLARATION_NOT_FOUND, + args: { name: refExp.name.name.text }, + severity: 'error', + node: refExp, + }) + } + } + + const typeRefExps = node.findAllChildrenOfKind(SyntaxKind.BoltReferenceTypeExpression); + for (const typeRefExp of typeRefExps) { + if (this.resolveTypeReferenceExpression(typeRefExp) === null) { + this.diagnostics.add({ + message: E_TYPE_DECLARATION_NOT_FOUND, + args: { name: typeRefExp.name.name.text }, + severity: 'error', + node: typeRefExp, + }) + } + } + } - private createType(node: Syntax): Type { + public registerSourceFile(node: BoltSourceFile): void { + this.addAllSymbolsInNode(node); + } - console.error(`creating type for ${kindToString(node.kind)}`); + private addAllSymbolsInNode(node: BoltSyntax): void { switch (node.kind) { - case SyntaxKind.BoltReferenceExpression: - return anyType; - - case SyntaxKind.BoltConstantExpression: - return node.value.type; - - case SyntaxKind.BoltExpressionStatement: - return voidType; - - case SyntaxKind.BoltCallExpression: - // TODO - return anyType; + case SyntaxKind.BoltSourceFile: + case SyntaxKind.BoltModule: + for (const element of node.elements) { + this.addAllSymbolsInNode(element); + } + break; case SyntaxKind.BoltFunctionDeclaration: - let returnType = anyType; - if (node.returnType !== null) { - returnType = this.getTypeOfNode(node.returnType) - } - if (node.body !== null) { - returnType = this.intersectTypes(returnType, this.getTypeOfBody(node.body)) - } - let paramTypes = node.params.map(param => { - let paramType = this.getTypeOfNode(param); - if (node.body !== null) { - paramType = this.intersectTypes( - paramType, - this.inferTypeFromUsage(param.bindings, node.body) - ) - } - return paramType - }) - return new FunctionType(paramTypes, returnType); - - case SyntaxKind.BoltReferenceTypeExpression: - const name = getFullTextOfQualName(node.name); - const scope = this.getScope(node); - let reffed = scope.getTypeNamed(name); - if (reffed === null) { - reffed = anyType; - } - return reffed; + { + const scope = this.getScopeSurroundingNode(node); + const sym = createSymbol(node); + this.addSymbol(node.name.text, scope, sym); + break; + } case SyntaxKind.BoltRecordDeclaration: - - const fullName = getFullName(node); - let type; - - if (node.members === null) { - type = new PrimType(); - this.symbols.set(fullName, type); - } else { - type = new RecordType(map(node.members, member => ([field.name.text, this.getTypeOfNode(field.type)]))); - this.symbols.set(fullName, type); - } - - return type; - - case SyntaxKind.BoltParameter: - if (node.type !== null) { - return this.getTypeOfNode(node.type) - } - return anyType; - - default: - throw new Error(`Could not derive type of ${kindToString(node.kind)}`) + { + const typeScope = this.getTypeScopeSurroundingNode(node); + const typeSym = createTypeSymbol(node); + this.addTypeSymbol(node.name.text, typeScope, typeSym); + } } } - public getSymbolNamed(name: string) { - if (!this.symbols.has(name)) { + private addSymbol(name: string, scope: Scope, sym: SymbolInfo): void { + this.symbols.set(`${name}@${(scope as any).id}`, sym); + } + + private addTypeSymbol(name: string, scope: TypeScope, sym: TypeSymbolInfo): void { + this.typeSymbols.set(`${name}@${(scope as any).id}`, sym); + } + + public getParentScope(scope: Scope): Scope | null { + let node = scope as Syntax; + if (node.kind === SyntaxKind.BoltSourceFile) { return null; } - return this.symbols.get(name); - } - - public getTypeOfNode(node: Syntax): Type { - if (this.types.has(node)) { - return this.types.get(node)! - } - const newType = this.createType(node) - this.types.set(node, newType) - return newType; - } - - public check(node: Syntax) { - - this.getTypeOfNode(node); - - switch (node.kind) { - - case SyntaxKind.BoltRecordDeclaration: - case SyntaxKind.BoltConstantExpression: - break; - - case SyntaxKind.BoltExpressionStatement: - this.check(node.expression); - break; - - case SyntaxKind.BoltCallExpression: - this.check(node.operator); - for (const operand of node.operands) { - this.check(operand); - } - // TODO check whether the overload matches the referenced operator - break; - - case SyntaxKind.BoltFunctionDeclaration: - if (node.body !== null) { - if (Array.isArray(node.body)) { - for (const element of node.body) { - this.check(element) - } - } - } - break; - - case SyntaxKind.BoltReferenceExpression: - // TODO implement this - break; - - case SyntaxKind.BoltModule: - case SyntaxKind.BoltSourceFile: - for (const element of node.elements) { - this.check(element) - } - break; - - default: - throw new Error(`Could not type-check node ${kindToString(node.kind)}`) - - } - - } - - public getScope(node: Syntax): Scope { + node = node.parentNode!; while (!introducesNewScope(node.kind)) { node = node.parentNode!; } - if (this.scopes.has(node)) { - return this.scopes.get(node)! - } - const scope = new Scope(node) - this.scopes.set(node, scope) - return scope + return node; } - private intersectTypes(a: Type, b: Type): Type { - if (a === noneType || b == noneType) { - return noneType; + public getParentTypeScope(scope: TypeScope): TypeScope | null { + let node = scope as Syntax; + if (node.kind === SyntaxKind.BoltSourceFile) { + return null; } - if (b === anyType) { - return a + node = node.parentNode!; + while (!introducesNewTypeScope(node.kind)) { + node = node.parentNode!; } - if (a === anyType) { - return b; + return node; + } + + private getScopeSurroundingNode(node: Syntax): Scope { + if (node.kind === SyntaxKind.BoltSourceFile) { + return node; } - if (a instanceof FunctionType && b instanceof FunctionType) { - if (a.paramTypes.length !== b.paramTypes.length) { - return noneType; + return this.getScopeForNode(node.parentNode); + } + + private getTypeScopeSurroundingNode(node: Syntax): TypeScope { + if (node.kind === SyntaxKind.BoltSourceFile) { + return node; + } + return this.getScopeForNode(node.parentNode); + } + + private getScopeForNode(node: Syntax): Scope { + if (node.kind === SyntaxKind.BoltSourceFile) { + return node; + } + let currNode = node; + while (!introducesNewScope(currNode.kind)) { + currNode = currNode.parentNode!; + } + return currNode; + } + + private getTypeScopeForNode(node: Syntax): TypeScope { + if (node.kind === SyntaxKind.BoltSourceFile) { + return node; + } + let currNode = node; + while (!introducesNewTypeScope(currNode.kind)) { + currNode = currNode.parentNode!; + } + return currNode; + } + + private lookupSymbolInScope(name: string, scope: Scope): SymbolInfo | null { + const key = `${name}@${(scope as any).id}`; + if (!this.symbols.has(key)) { + return null; + } + return this.symbols.get(key); + } + + private lookupSymbolInTypeScope(name: string, scope: TypeScope): TypeSymbolInfo | null { + const key = `${name}@${(scope as any).id}`; + if (!this.typeSymbols.has(key)) { + return null; + } + return this.typeSymbols.get(key); + } + + public findSymbolInScopeOf(name: string, scope: Scope): SymbolInfo | null { + while (true) { + const sym = this.lookupSymbolInScope(name, scope); + if (sym !== null) { + return sym; } - const returnType = this.intersectTypes(a.returnType, b.returnType); - const paramTypes = a.paramTypes.map((_, i) => this.intersectTypes(a.paramTypes[i], b.paramTypes[i])); - return new FunctionType(paramTypes, returnType) + const parentScope = this.getParentScope(scope); + if (parentScope === null) { + break; + } + scope = parentScope; } - return noneType; + return null; + } + + public findSymbolInTypeScopeOf(name: string, scope: TypeScope): TypeSymbolInfo | null { + while (true) { + const sym = this.lookupSymbolInTypeScope(name, scope); + if (sym !== null) { + return sym; + } + const parentTypeScope = this.getParentTypeScope(scope); + if (parentTypeScope === null) { + break; + } + scope = parentTypeScope; + } + return null; + } + + public resolveReferenceExpression(node: BoltReferenceExpression): BoltDeclaration | null { + let scope = this.getScopeSurroundingNode(node); + if (node.name.modulePath !== null) { + while (true) { + let shouldSearchParentScopes = false; + let currScope = scope; + for (const name of node.name.modulePath) { + const sym = this.lookupSymbolInScope(name.text, currScope); + if (sym === null) { + shouldSearchParentScopes = true; + break; + } + if (sym.declarations[0].kind !== SyntaxKind.BoltModule) { + shouldSearchParentScopes = true; + break; + } + currScope = this.getScopeForNode(sym.declarations[0]); + } + if (!shouldSearchParentScopes) { + scope = currScope; + break; + } + const parentScope = this.getParentScope(scope); + if (parentScope === null) { + return null; + } + scope = parentScope; + } + } + const sym = this.findSymbolInScopeOf(node.name.name.text, scope); + if (sym === null) { + return null; + } + return sym.declarations[0]!; + } + + public resolveTypeReferenceExpression(node: BoltReferenceTypeExpression): BoltTypeDeclaration | null { + const typeScope = this.getTypeScopeSurroundingNode(node); + const typeSym = this.findSymbolInTypeScopeOf(node.name.name.text, typeScope); + if (typeSym === null) { + return null; + } + return typeSym.declarations[0]!; } } diff --git a/src/common.ts b/src/common.ts new file mode 100644 index 000000000..dc9067b2f --- /dev/null +++ b/src/common.ts @@ -0,0 +1,53 @@ +import { + BoltFunctionBodyElement, + BoltReturnStatement, + SyntaxKind, + BoltExpression +} from "./ast"; + +export type BoltFunctionBody = BoltFunctionBodyElement[]; + +export function getAllReturnStatements(body: BoltFunctionBody): BoltReturnStatement[] { + + const results: BoltReturnStatement[] = []; + + for (const element of body) { + visit(element); + } + + return results; + + function visit(node: BoltFunctionBodyElement) { + switch (node.kind) { + case SyntaxKind.BoltReturnStatement: + results.push(node); + break; + case SyntaxKind.BoltExpressionStatement: + visitExpression(node.expression); + break; + } + } + + function visitExpression(node: BoltExpression) { + switch (node.kind) { + case SyntaxKind.BoltBlockExpression: + for (const element of node.statements) { + visit(element); + } + break; + case SyntaxKind.BoltMatchExpression: + for (const arm of node.arms) { + visitExpression(arm.body); + } + break; + case SyntaxKind.BoltCallExpression: + visitExpression(node.operator); + for (const operand of node.operands) { + visitExpression(operand); + } + break; + } + } + +} + diff --git a/src/di.ts b/src/di.ts index cdbd82f78..406d69039 100644 --- a/src/di.ts +++ b/src/di.ts @@ -38,8 +38,8 @@ export class Container { } } - private resolve(factory: Factory): T; - private resolve(serviceId: ServiceID): any { + public resolve(factory: Factory): T; + public resolve(serviceId: ServiceID): any { return this.singletons.get(serviceId); } diff --git a/src/diagnostics.ts b/src/diagnostics.ts new file mode 100644 index 000000000..f982e2b17 --- /dev/null +++ b/src/diagnostics.ts @@ -0,0 +1,37 @@ + +import chalk from "chalk" +import {Syntax} from "./ast"; +import {format, MapLike, FormatArg} from "./util"; + +export const E_TYPE_DECLARATION_NOT_FOUND = "A type declaration named '{name}' was not found." +export const E_DECLARATION_NOT_FOUND = "Reference to an undefined declaration '{name}'."; +export const E_TYPES_NOT_ASSIGNABLE = "Types {left} and {right} are not assignable."; + +export interface Diagnostic { + message: string; + severity: string; + args?: MapLike; + node?: Syntax; +} + +export class DiagnosticPrinter { + + public hasErrors = false; + + public add(diagnostic: Diagnostic): void { + let out = '' + switch (diagnostic.severity) { + case 'error': + this.hasErrors = true; + out += chalk.bold.red('error: '); + } + if (diagnostic.args !== undefined) { + out += format(diagnostic.message, diagnostic.args); + } else { + out += diagnostic.message; + } + process.stderr.write(out + '\n'); + } + +} + diff --git a/src/frontend.ts b/src/frontend.ts index 72c097ccf..b376e4ac9 100644 --- a/src/frontend.ts +++ b/src/frontend.ts @@ -8,7 +8,7 @@ import { Program } from "./program" import { TypeChecker } from "./checker" import { Evaluator } from "./evaluator" import { emit } from "./emitter" -import { Syntax } from "./ast" +import { Syntax, BoltSourceFile } from "./ast" import { upsearchSync, FastStringMap, getFileStem, getLanguage } from "./util" import { Package } from "./package" import { verbose, memoize } from "./util" @@ -18,6 +18,7 @@ import CompileBoltToJSTransform from "./transforms/boltToJS" import ConstFoldTransform from "./transforms/constFold" import EliminateModulesTransform from "./transforms/eliminateModules" import { TransformManager } from "./transforms/index" +import {DiagnosticPrinter} from "./diagnostics" const targetExtensions: MapLike = { 'JS': '.mjs', @@ -62,12 +63,14 @@ export class Frontend { public evaluator: Evaluator; public checker: TypeChecker; + public diagnostics: DiagnosticPrinter; public timing: Timing; private container = new Container(); constructor() { - this.checker = new TypeChecker(); + this.diagnostics = new DiagnosticPrinter(); + this.checker = new TypeChecker(this.diagnostics); this.evaluator = new Evaluator(this.checker); this.timing = new Timing(); this.container.bindSelf(this.evaluator); @@ -87,6 +90,17 @@ export class Frontend { public compile(program: Program, target: string) { + for (const sourceFile of program.getAllSourceFiles()) { + this.checker.registerSourceFile(sourceFile as BoltSourceFile); + } + for (const sourceFile of program.getAllSourceFiles()) { + this.checker.checkSourceFile(sourceFile as BoltSourceFile); + } + + if (this.diagnostics.hasErrors) { + throw new Error(`Compilation failed because of type-checking errors.`); + } + switch (target) { case "JS": diff --git a/src/parser.ts b/src/parser.ts index c69b79b16..686d4583a 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -58,6 +58,7 @@ import { BoltFunctionBodyElement, createBoltSourceFile, BoltRecordField, + setParents, } from "./ast" import { parseForeignLanguage } from "./foreign" @@ -168,24 +169,25 @@ export class Parser { public parseQualName(tokens: BoltTokenStream): BoltQualName { - const path: BoltIdentifier[] = []; + let modulePath = null; - while (true) { - const t0 = tokens.peek(2); - if (t0.kind !== SyntaxKind.BoltDot) { - break; + if (tokens.peek(2).kind === SyntaxKind.BoltDot) { + modulePath = []; + while (true) { + modulePath.push(tokens.get() as BoltIdentifier) + tokens.get(); + const t0 = tokens.peek(2); + if (t0.kind !== SyntaxKind.BoltDot) { + break; + } } - path.push(tokens.get() as BoltIdentifier) - tokens.get(); } const name = tokens.get(); - if (name.kind !== SyntaxKind.BoltIdentifier) { - throw new ParseError(name, [SyntaxKind.BoltIdentifier]); - } - const startNode = path.length > 0 ? path[0] : name; + assertToken(name, SyntaxKind.BoltIdentifier); + const startNode = modulePath !== null ? modulePath[0] : name; const endNode = name; - const node = createBoltQualName(path, name, null); + const node = createBoltQualName(modulePath, name as BoltIdentifier, null); setOrigNodeRange(node, startNode, endNode); return node; } @@ -446,19 +448,14 @@ export class Parser { public parseStatement(tokens: BoltTokenStream): BoltStatement { const t0 = tokens.peek(); - if (t0.kind === SyntaxKind.BoltReturnKeyword) { + if (KIND_EXPRESSION_T0.indexOf(t0.kind) !== -1) { + return this.parseExpressionStatement(tokens); + } else if (t0.kind === SyntaxKind.BoltReturnKeyword) { return this.parseReturnStatement(tokens); } else if (t0.kind === SyntaxKind.BoltLoopKeyword) { return this.parseLoopStatement(tokens); } else { - try { - return this.parseExpressionStatement(tokens); - } catch (e) { - if (!(e instanceof ParseError)) { - throw e; - } - throw new ParseError(t0, KIND_STATEMENT_T0); - } + throw new ParseError(t0, KIND_STATEMENT_T0); } } @@ -519,7 +516,7 @@ export class Parser { const t1 = tokens.get(); assertToken(t1, SyntaxKind.BoltIdentifier); - const name = createBoltQualName([], t1 as BoltIdentifier); + const name = t1 as BoltIdentifier; let t2 = tokens.peek(); @@ -582,6 +579,10 @@ export class Parser { if (t0.kind === SyntaxKind.EndOfFile) { break; } + if (t0.kind === SyntaxKind.BoltSemi) { + tokens.get(); + continue; + } const statement = this.parseStatement(tokens); statements.push(statement); } @@ -1192,6 +1193,8 @@ export function parseSourceFile(filepath: string): BoltSourceFile { const contents = fs.readFileSync(file.origPath, 'utf8'); const scanner = new Scanner(file, contents) const parser = new Parser(); - return parser.parseSourceFile(scanner); + const sourceFile = parser.parseSourceFile(scanner); + setParents(sourceFile); + return sourceFile; } diff --git a/src/scanner.ts b/src/scanner.ts index a49dfe551..bb240b324 100644 --- a/src/scanner.ts +++ b/src/scanner.ts @@ -122,30 +122,30 @@ function isSymbol(ch: string) { export class Scanner { - protected buffer: string[] = []; - protected scanned: BoltToken[] = []; - protected currPos: TextPos; - protected offset = 0; + private buffer: string[] = []; + private scanned: BoltToken[] = []; + private currPos: TextPos; + private offset = 0; constructor(public file: TextFile, public input: string, startPos = new TextPos(0,1,1)) { this.currPos = startPos; } - protected readChar() { + private readChar() { if (this.offset == this.input.length) { return EOF } return this.input[this.offset++] } - protected peekChar(count = 1) { + private peekChar(count = 1) { while (this.buffer.length < count) { this.buffer.push(this.readChar()); } return this.buffer[count - 1]; } - protected getChar() { + private getChar() { const ch = this.buffer.length > 0 ? this.buffer.shift()! @@ -166,7 +166,7 @@ export class Scanner { return ch } - protected takeWhile(pred: (ch: string) => boolean) { + private takeWhile(pred: (ch: string) => boolean) { let text = this.getChar(); while (true) { const c0 = this.peekChar(); @@ -190,6 +190,12 @@ export class Scanner { continue; } + const c1 = this.peekChar(2); + if (c0 === '/' && c1 == '/') { + this.scanLineComment(); + continue; + } + const startPos = this.currPos.clone() if (c0 == EOF) { @@ -338,6 +344,37 @@ export class Scanner { } + private assertChar(ch: string): void { + const c0 = this.peekChar(); + if (c0 !== ch) { + throw new ScanError(this.file, this.currPos.clone(), ch); + } + this.getChar(); + } + + private scanLineComment(): string { + let text = ''; + this.assertChar('/'); + this.assertChar('/'); + while (true) { + const c0 = this.peekChar(); + if (c0 === EOF) { + break; + } + if (c0 == '\n') { + this.getChar(); + const c1 = this.peekChar(); + if (c1 === '\r') { + this.getChar(); + } + break; + } + text += c0 + this.getChar(); + } + return text; + } + public peek(count = 1): BoltToken { while (this.scanned.length < count) { this.scanned.push(this.scanToken()); diff --git a/src/transforms/boltToJS.ts b/src/transforms/boltToJS.ts index 4da2b3758..ed3a3f76d 100644 --- a/src/transforms/boltToJS.ts +++ b/src/transforms/boltToJS.ts @@ -41,7 +41,7 @@ import { BoltSourceElement, } from "../ast" -import { getFullTextOfQualName, hasPublicModifier, setOrigNodeRange, FastStringMap } from "../util" +import { hasPublicModifier, setOrigNodeRange } from "../util" import { Program, SourceFile } from "../program" import { Transformer, TransformManager } from "./index" import { assert } from "../util" @@ -119,8 +119,9 @@ export class BoltToJSTransform implements Transformer { ); case SyntaxKind.BoltReferenceExpression: + assert(node.name.modulePath === null); return createJSReferenceExpression( - getFullTextOfQualName(node.name), + node.name.name.text, node.span, ); diff --git a/src/transforms/expand.ts b/src/transforms/expand.ts index 30cdcbb44..ef03e3570 100644 --- a/src/transforms/expand.ts +++ b/src/transforms/expand.ts @@ -10,12 +10,9 @@ import { BoltMacroCall, } from "../ast" -import { TextSpan } from "../text" import { TypeChecker, Scope } from "../checker" -import { ParseError } from "../util" import { BoltTokenStream, Parser, isModifierKeyword } from "../parser" import { Evaluator, TRUE, FALSE } from "../evaluator" -import { setOrigNodeRange, createTokenStream } from "../util" import { Transformer, TransformManager } from "./index" import { inject } from "../di" import {SourceFile} from "../program" diff --git a/src/transforms/index.ts b/src/transforms/index.ts index 3a300f9a5..9a1580b49 100644 --- a/src/transforms/index.ts +++ b/src/transforms/index.ts @@ -1,8 +1,8 @@ -import { SourceFile, Program } from "./program" -import { Container } from "./di" -import {Evaluator} from "./evaluator"; -import {TypeChecker} from "./checker"; +import { SourceFile, Program } from "../program" +import { Container, Newable } from "../di" +import {Evaluator} from "../evaluator"; +import {TypeChecker} from "../checker"; export interface Transformer { isApplicable(node: SourceFile): boolean; @@ -13,10 +13,6 @@ export interface RegisterTransformerOptions { } -function createInstance(factory: Factory, ...args: any[]): T { - return new factory(...args); -} - export class TransformManager { private transformers: Transformer[] = []; @@ -26,7 +22,7 @@ export class TransformManager { } public register(transformerFactory: Newable, options: RegisterTransformerOptions = {}) { - const transformer = this.container.createInstance(transformerFactory, this); + const transformer = this.container.createInstance(transformerFactory, this) as Transformer; this.transformers.push(transformer); } diff --git a/src/treegen/ast-template.js b/src/treegen/ast-template.js index c10e38d73..46f9fc135 100644 --- a/src/treegen/ast-template.js +++ b/src/treegen/ast-template.js @@ -58,6 +58,8 @@ function isSyntax(value) { && value.__NODE_TYPE !== undefined; } +let nextNodeId = 1; + function createNode(nodeType) { const obj = Object.create(nodeProto); Object.defineProperty(obj, '__NODE_TYPE', { @@ -73,6 +75,11 @@ function createNode(nodeType) { return this.__NODE_TYPE.index; } }); + Object.defineProperty(obj, 'id', { + enumerable: true, + configurable: true, + value: nextNodeId++, + }) obj.span = null; return obj; } @@ -113,7 +120,7 @@ for (const nodeName of Object.keys(NODE_TYPES)) { exported.setParents = function setParents(node, parentNode = null) { node.parentNode = parentNode; - for (const child of getChildNodes(node)) { + for (const child of node.getChildNodes()) { setParents(child, node) } } diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 000000000..1c4ef0019 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,189 @@ + +import { FastStringMap } from "./util"; + +enum TypeKind { + OpaqueType, + AnyType, + NeverType, + FunctionType, + RecordType, + VariantType, + TupleType, +} + +export type Type + = OpaqueType + | AnyType + | NeverType + | FunctionType + | RecordType + | VariantType + | TupleType + +abstract class TypeBase { + abstract kind: TypeKind; +} + +export class OpaqueType extends TypeBase { + kind: TypeKind.OpaqueType = TypeKind.OpaqueType; +} + +export function isOpaqueType(value: any): value is OpaqueType { + return value.kind === TypeKind.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; + + constructor( + public paramTypes: Type[], + public returnType: Type, + ) { + super(); + } + + public getParameterCount(): number { + return this.paramTypes.length; + } + + public getParamTypeAtIndex(index: number) { + if (index < 0 || index >= this.paramTypes.length) { + throw new Error(`Could not get the parameter type at index ${index} because the index was out of bounds.`); + } + return this.paramTypes[index]; + } + +} + +export function isFunctionType(value: any): value is FunctionType { + return value instanceof FunctionType; +} + +export class VariantType extends TypeBase { + + kind: TypeKind.VariantType = TypeKind.VariantType; + + constructor(public elementTypes: Type[]) { + super(); + } + + public getOwnElementTypes(): IterableIterator { + return this.elementTypes[Symbol.iterator](); + } + +} + +export function isVariantType(value: any): value is VariantType { + return value instanceof VariantType; +} + +export class RecordType { + + kind: TypeKind.RecordType = TypeKind.RecordType; + + private fieldTypes = new FastStringMap(); + + constructor( + iterable: IterableIterator<[string, Type]>, + ) { + for (const [name, type] of iterable) { + this.fieldTypes.set(name, type); + } + } + + public hasField(name: string) { + return name in this.fieldTypes; + } + + public getTypeOfField(name: string) { + return this.fieldTypes.get(name); + } + +} + +export function isRecordType(value: any): value is RecordType { + return value.kind === TypeKind.RecordType; +} + +export class TupleType extends TypeBase { + + kind: TypeKind.TupleType = TypeKind.TupleType; + + constructor(public elementTypes: Type[]) { + super(); + } + +} + +export function intersectTypes(a: Type, b: Type): Type { + if (isNeverType(a) || isNeverType(b)) { + return new NeverType(); + } + if (isAnyType(b)) { + return a + } + if (isAnyType(a)) { + return b; + } + if (isFunctionType(a) && isFunctionType(b)) { + if (a.paramTypes.length !== b.paramTypes.length) { + return new NeverType(); + } + const returnType = intersectTypes(a.returnType, b.returnType); + const paramTypes = a.paramTypes + .map((_, i) => intersectTypes(a.paramTypes[i], b.paramTypes[i])); + return new FunctionType(paramTypes, returnType) + } + return new NeverType(); +} + + +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.`); +} + diff --git a/src/util.ts b/src/util.ts index 16201faad..0dfd2059d 100644 --- a/src/util.ts +++ b/src/util.ts @@ -169,12 +169,11 @@ export function hasPublicModifier(node: BoltDeclaration) { return (node.modifiers & BoltDeclarationModifiers.Public) > 0; } -export function getFullTextOfQualName(node: BoltQualName) { - let out = '' - for (const element of node.modulePath) { - out += element.text + '.'; +export function toDeclarationPath(node: BoltQualName): string[] { + if (node.modulePath === null) { + return [ node.name.text ]; } - return out + node.name.text; + return [...node.modulePath.map(id => id.text), node.name.text]; } export interface Stream { @@ -451,3 +450,41 @@ export class ScanError extends Error { } } +export interface MapLike { + [key: string]: T; +} + +export type FormatArg = string | Date | number + +export function format(message: string, data: MapLike) { + + let out = '' + + let name = ''; + let insideParam = false; + + for (const ch of message) { + if (insideParam) { + if (ch === '}') { + out += data[name]!.toString(); + reset(); + } + name += ch + } else { + if (ch === '{') { + insideParam = true; + } else { + out += ch; + } + } + } + + return out; + + function reset() { + name = ''; + insideParam = false; + } + +} +