2020-02-25 17:55:17 +01:00
|
|
|
|
|
|
|
import {
|
|
|
|
Syntax,
|
2020-05-10 15:56:34 +02:00
|
|
|
kindToString,
|
2020-02-25 18:34:17 +01:00
|
|
|
SyntaxKind,
|
2020-05-10 15:56:34 +02:00
|
|
|
BoltImportDeclaration,
|
|
|
|
BoltPattern,
|
2020-02-25 17:55:17 +01:00
|
|
|
} from "./ast"
|
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
import { FastStringMap, getFullTextOfQualName } from "./util"
|
2020-02-26 18:53:28 +01:00
|
|
|
|
|
|
|
export class Type {
|
2020-02-25 17:55:17 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-02-26 18:53:28 +01:00
|
|
|
export class PrimType extends Type {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-03-03 14:53:54 +01:00
|
|
|
export class FunctionType extends Type {
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
public paramTypes: Type[],
|
|
|
|
public returnType: Type,
|
|
|
|
) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-02-26 18:53:28 +01:00
|
|
|
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()
|
2020-03-03 14:53:54 +01:00
|
|
|
export const anyType = new PrimType()
|
|
|
|
export const noneType = new PrimType();
|
2020-02-26 18:53:28 +01:00
|
|
|
|
|
|
|
export class RecordType {
|
|
|
|
|
|
|
|
fieldTypes: FastStringMap<Type> = Object.create(null);
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
iterable: IterableIterator<[string, Type]>,
|
|
|
|
) {
|
|
|
|
for (const [name, typ] of iterable) {
|
|
|
|
this.fieldTypes[name] = typ;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hasField(name: string) {
|
|
|
|
return name in this.fieldTypes;
|
|
|
|
}
|
|
|
|
|
|
|
|
getTypeOfField(name: string) {
|
|
|
|
if (name in this.fieldTypes) {
|
|
|
|
return this.fieldTypes[name]
|
|
|
|
}
|
|
|
|
throw new Error(`Field '${name}' does not exist on this record type.`)
|
|
|
|
}
|
|
|
|
|
2020-02-25 17:55:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export class Scope {
|
|
|
|
|
|
|
|
constructor(public origin: Syntax) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-02-26 18:53:28 +01:00
|
|
|
function* map<T, R>(iterable: Iterable<T>, proc: (value: T) => R): IterableIterator<R> {
|
|
|
|
const iterator = iterable[Symbol.iterator]();
|
|
|
|
while (true) {
|
|
|
|
let { done, value }= iterator.next();
|
|
|
|
if (done) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
yield proc(value)
|
|
|
|
}
|
|
|
|
}
|
2020-02-25 17:55:17 +01:00
|
|
|
|
2020-02-26 18:53:28 +01:00
|
|
|
function getFullName(node: Syntax) {
|
|
|
|
let out = []
|
|
|
|
let curr: Syntax | null = node;
|
|
|
|
while (true) {
|
|
|
|
switch (curr.kind) {
|
2020-05-10 15:56:34 +02:00
|
|
|
case SyntaxKind.BoltIdentifier:
|
2020-03-03 14:53:54 +01:00
|
|
|
out.unshift(curr.text)
|
|
|
|
break;
|
2020-05-10 15:56:34 +02:00
|
|
|
case SyntaxKind.BoltModule:
|
|
|
|
out.unshift(getFullTextOfQualName(curr.name));
|
2020-02-26 18:53:28 +01:00
|
|
|
break;
|
2020-05-10 15:56:34 +02:00
|
|
|
case SyntaxKind.BoltRecordDeclaration:
|
|
|
|
out.unshift(getFullTextOfQualName(curr.name))
|
2020-02-26 18:53:28 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
curr = curr.parentNode;
|
|
|
|
if (curr === null) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return out.join('.');
|
|
|
|
}
|
|
|
|
|
|
|
|
export class TypeChecker {
|
2020-02-25 17:55:17 +01:00
|
|
|
|
2020-02-26 18:53:28 +01:00
|
|
|
protected symbols: FastStringMap<Type> = Object.create(null)
|
|
|
|
protected types = new Map<Syntax, Type>();
|
2020-02-25 17:55:17 +01:00
|
|
|
protected scopes = new Map<Syntax, Scope>();
|
|
|
|
|
2020-03-03 14:53:54 +01:00
|
|
|
constructor() {
|
|
|
|
}
|
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
protected inferTypeFromUsage(bindings: BoltPattern, body: Body) {
|
2020-03-03 14:53:54 +01:00
|
|
|
return anyType;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected getTypeOfBody(body: Body) {
|
|
|
|
return anyType;
|
|
|
|
}
|
2020-02-26 18:53:28 +01:00
|
|
|
|
2020-03-03 14:53:54 +01:00
|
|
|
protected createType(node: Syntax): Type {
|
2020-02-26 18:53:28 +01:00
|
|
|
|
2020-02-25 17:55:17 +01:00
|
|
|
switch (node.kind) {
|
2020-02-26 18:53:28 +01:00
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
case SyntaxKind.BoltReferenceExpression:
|
2020-05-10 11:58:07 +02:00
|
|
|
return anyType;
|
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
case SyntaxKind.BoltConstantExpression:
|
2020-02-26 18:53:28 +01:00
|
|
|
return node.value.type;
|
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
case SyntaxKind.BoltNewTypeDeclaration:
|
2020-03-03 14:53:54 +01:00
|
|
|
console.log(getFullName(node.name))
|
|
|
|
this.symbols[getFullName(node.name)] = new PrimType();
|
|
|
|
return noneType;
|
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
case SyntaxKind.BoltFunctionDeclaration:
|
2020-03-03 14:53:54 +01:00
|
|
|
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);
|
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
case SyntaxKind.BoltReferenceTypeNode:
|
|
|
|
const name = getFullTextOfQualName(node.name);
|
|
|
|
const reffed = this.getTypeNamed(name);
|
2020-03-03 14:53:54 +01:00
|
|
|
if (reffed === null) {
|
2020-05-10 15:56:34 +02:00
|
|
|
throw new Error(`Could not find a type named '${name}'`);
|
2020-03-03 14:53:54 +01:00
|
|
|
}
|
|
|
|
return reffed;
|
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
case SyntaxKind.BoltRecordDeclaration:
|
2020-02-26 18:53:28 +01:00
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
const typ = new RecordType(map(node.fields, field => ([field.name.text, this.getTypeOfNode(field.type)])));
|
2020-02-26 18:53:28 +01:00
|
|
|
|
|
|
|
this.symbols[getFullName(node)] = typ;
|
|
|
|
|
|
|
|
return typ;
|
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
case SyntaxKind.BoltParameter:
|
|
|
|
if (node.typeNode !== null) {
|
|
|
|
return this.getTypeOfNode(node.typeNode)
|
2020-03-03 14:53:54 +01:00
|
|
|
}
|
|
|
|
return anyType;
|
2020-02-26 18:53:28 +01:00
|
|
|
|
|
|
|
default:
|
2020-05-10 15:56:34 +02:00
|
|
|
throw new Error(`Could not derive type of ${kindToString(node.kind)}`)
|
2020-02-26 18:53:28 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
getTypeNamed(name: string) {
|
2020-03-03 14:53:54 +01:00
|
|
|
return name in this.symbols
|
|
|
|
? this.symbols[name]
|
2020-02-26 18:53:28 +01:00
|
|
|
: null
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
check(node: Syntax) {
|
|
|
|
|
2020-03-03 14:53:54 +01:00
|
|
|
this.getTypeOfNode(node);
|
|
|
|
|
2020-02-26 18:53:28 +01:00
|
|
|
switch (node.kind) {
|
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
case SyntaxKind.BoltSentence:
|
|
|
|
case SyntaxKind.BoltRecordDeclaration:
|
|
|
|
case SyntaxKind.BoltNewTypeDeclaration:
|
2020-02-26 18:53:28 +01:00
|
|
|
break;
|
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
case SyntaxKind.BoltFunctionDeclaration:
|
2020-03-03 14:53:54 +01:00
|
|
|
if (node.body !== null) {
|
|
|
|
if (Array.isArray(node.body)) {
|
|
|
|
for (const element of node.body) {
|
|
|
|
this.check(element)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-02-26 18:53:28 +01:00
|
|
|
break;
|
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
case SyntaxKind.BoltReferenceExpression:
|
2020-05-10 11:58:07 +02:00
|
|
|
// TODO implement this
|
|
|
|
break;
|
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
case SyntaxKind.BoltModule:
|
|
|
|
case SyntaxKind.BoltSourceFile:
|
2020-02-26 18:53:28 +01:00
|
|
|
for (const element of node.elements) {
|
|
|
|
this.check(element)
|
2020-02-25 17:55:17 +01:00
|
|
|
}
|
2020-02-26 18:53:28 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2020-05-10 15:56:34 +02:00
|
|
|
throw new Error(`Could not type-check node ${kindToString(node.kind)}`)
|
2020-02-26 18:53:28 +01:00
|
|
|
|
2020-02-25 17:55:17 +01:00
|
|
|
}
|
2020-02-26 18:53:28 +01:00
|
|
|
|
2020-02-25 17:55:17 +01:00
|
|
|
}
|
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
getImportedSymbols(node: BoltImportDeclaration) {
|
2020-02-25 18:34:17 +01:00
|
|
|
return [{ name: 'fac' }]
|
|
|
|
}
|
|
|
|
|
2020-02-25 17:55:17 +01:00
|
|
|
getScope(node: Syntax): Scope {
|
2020-05-10 15:56:34 +02:00
|
|
|
while (node.kind !== SyntaxKind.BoltFunctionDeclaration && node.kind !== SyntaxKind.BoltSourceFile) {
|
2020-02-25 17:55:17 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-03-03 14:53:54 +01:00
|
|
|
protected intersectTypes(a: Type, b: Type): Type {
|
|
|
|
if (a === noneType || b == noneType) {
|
|
|
|
return noneType;
|
|
|
|
}
|
|
|
|
if (b === anyType) {
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
if (a === anyType) {
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
if (a instanceof FunctionType && b instanceof FunctionType) {
|
|
|
|
if (a.paramTypes.length !== b.paramTypes.length) {
|
|
|
|
return noneType;
|
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
return noneType;
|
|
|
|
}
|
|
|
|
|
2020-02-26 18:53:28 +01:00
|
|
|
// getMapperForNode(target: string, node: Syntax): Mapper {
|
|
|
|
// return this.getScope(node).getMapper(target)
|
|
|
|
// }
|
2020-02-25 17:55:17 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|