Add support for type-checking recursion and improove Checker.addConstraint
This commit is contained in:
parent
d2e6f9ed4f
commit
666256ed15
7 changed files with 680 additions and 56 deletions
39
package-lock.json
generated
39
package-lock.json
generated
|
@ -11,26 +11,27 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"tslib": "^2.4.0",
|
"tslib": "^2.4.0",
|
||||||
|
"yagl": "^0.5.0",
|
||||||
"yargs": "^17.5.1"
|
"yargs": "^17.5.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"bolt": "lib/bin/bolt.js"
|
"bolt": "lib/bin/bolt.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.7.13",
|
"@types/node": "^18.7.14",
|
||||||
"@types/yargs": "^17.0.11"
|
"@types/yargs": "^17.0.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "18.7.13",
|
"version": "18.7.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.14.tgz",
|
||||||
"integrity": "sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw==",
|
"integrity": "sha512-6bbDaETVi8oyIARulOE9qF1/Qdi/23z6emrUh0fNJRUmjznqrixD4MpGDdgOFk5Xb0m2H6Xu42JGdvAxaJR/wA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/yargs": {
|
"node_modules/@types/yargs": {
|
||||||
"version": "17.0.11",
|
"version": "17.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz",
|
||||||
"integrity": "sha512-aB4y9UDUXTSMxmM4MH+YnuR0g5Cph3FLQBoWoMB21DSvFVAxRVEHEMx3TLh+zUZYMCQtKiqazz0Q4Rre31f/OA==",
|
"integrity": "sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/yargs-parser": "*"
|
"@types/yargs-parser": "*"
|
||||||
|
@ -202,6 +203,11 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/yagl": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yagl/-/yagl-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-nM6dkvVEgPkDdtNUmGdgvTyAwNnp88g8RvduiNewECU9mKDmazMFqSxkfK8gsvW/Zhzno7hzRHVlR6atFZk44g=="
|
||||||
|
},
|
||||||
"node_modules/yargs": {
|
"node_modules/yargs": {
|
||||||
"version": "17.5.1",
|
"version": "17.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz",
|
||||||
|
@ -230,15 +236,15 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "18.7.13",
|
"version": "18.7.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.14.tgz",
|
||||||
"integrity": "sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw==",
|
"integrity": "sha512-6bbDaETVi8oyIARulOE9qF1/Qdi/23z6emrUh0fNJRUmjznqrixD4MpGDdgOFk5Xb0m2H6Xu42JGdvAxaJR/wA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/yargs": {
|
"@types/yargs": {
|
||||||
"version": "17.0.11",
|
"version": "17.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz",
|
||||||
"integrity": "sha512-aB4y9UDUXTSMxmM4MH+YnuR0g5Cph3FLQBoWoMB21DSvFVAxRVEHEMx3TLh+zUZYMCQtKiqazz0Q4Rre31f/OA==",
|
"integrity": "sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/yargs-parser": "*"
|
"@types/yargs-parser": "*"
|
||||||
|
@ -368,6 +374,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
|
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
|
||||||
},
|
},
|
||||||
|
"yagl": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yagl/-/yagl-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-nM6dkvVEgPkDdtNUmGdgvTyAwNnp88g8RvduiNewECU9mKDmazMFqSxkfK8gsvW/Zhzno7hzRHVlR6atFZk44g=="
|
||||||
|
},
|
||||||
"yargs": {
|
"yargs": {
|
||||||
"version": "17.5.1",
|
"version": "17.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz",
|
||||||
|
|
|
@ -24,10 +24,11 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"tslib": "^2.4.0",
|
"tslib": "^2.4.0",
|
||||||
|
"yagl": "^0.5.0",
|
||||||
"yargs": "^17.5.1"
|
"yargs": "^17.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.7.13",
|
"@types/node": "^18.7.14",
|
||||||
"@types/yargs": "^17.0.11"
|
"@types/yargs": "^17.0.12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
256
src/checker.ts
256
src/checker.ts
|
@ -1,12 +1,15 @@
|
||||||
import {
|
import {
|
||||||
Expression,
|
Expression,
|
||||||
|
LetDeclaration,
|
||||||
Pattern,
|
Pattern,
|
||||||
|
SourceFile,
|
||||||
Syntax,
|
Syntax,
|
||||||
SyntaxKind,
|
SyntaxKind,
|
||||||
TypeExpression
|
TypeExpression
|
||||||
} from "./cst";
|
} from "./cst";
|
||||||
import { BindingNotFoudDiagnostic, Diagnostics, UnificationFailedDiagnostic } from "./diagnostics";
|
import { ArityMismatchDiagnostic, BindingNotFoudDiagnostic, Diagnostics, UnificationFailedDiagnostic } from "./diagnostics";
|
||||||
import { assert } from "./util";
|
import { assert } from "./util";
|
||||||
|
import { LabeledDirectedHashGraph, LabeledGraph, strongconnect, toposort } from "yagl"
|
||||||
|
|
||||||
export enum TypeKind {
|
export enum TypeKind {
|
||||||
Arrow,
|
Arrow,
|
||||||
|
@ -55,7 +58,7 @@ class TVar extends TypeBase {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TArrow extends TypeBase {
|
export class TArrow extends TypeBase {
|
||||||
|
|
||||||
public readonly kind = TypeKind.Arrow;
|
public readonly kind = TypeKind.Arrow;
|
||||||
|
|
||||||
|
@ -185,6 +188,19 @@ class TVSet {
|
||||||
public add(tv: TVar): void {
|
public add(tv: TVar): void {
|
||||||
this.mapping.set(tv.id, tv);
|
this.mapping.set(tv.id, tv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public has(tv: TVar): boolean {
|
||||||
|
return this.mapping.has(tv.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public intersectsType(type: Type): boolean {
|
||||||
|
for (const tv of type.getTypeVars()) {
|
||||||
|
if (this.has(tv)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public delete(tv: TVar): void {
|
public delete(tv: TVar): void {
|
||||||
this.mapping.delete(tv.id);
|
this.mapping.delete(tv.id);
|
||||||
|
@ -307,6 +323,7 @@ export interface InferContext {
|
||||||
typeVars: TVSet;
|
typeVars: TVSet;
|
||||||
env: TypeEnv;
|
env: TypeEnv;
|
||||||
constraints: ConstraintSet;
|
constraints: ConstraintSet;
|
||||||
|
returnType: Type;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Checker {
|
export class Checker {
|
||||||
|
@ -314,6 +331,9 @@ export class Checker {
|
||||||
private nextTypeVarId = 0;
|
private nextTypeVarId = 0;
|
||||||
private nextConTypeId = 0;
|
private nextConTypeId = 0;
|
||||||
|
|
||||||
|
private graph?: LabeledGraph<Syntax, Syntax>;
|
||||||
|
private currentCycle?: Map<Syntax, Type>;
|
||||||
|
|
||||||
private stringType = new TCon(this.nextConTypeId++, [], 'String');
|
private stringType = new TCon(this.nextConTypeId++, [], 'String');
|
||||||
private intType = new TCon(this.nextConTypeId++, [], 'Int');
|
private intType = new TCon(this.nextConTypeId++, [], 'Int');
|
||||||
private boolType = new TCon(this.nextConTypeId++, [], 'Bool');
|
private boolType = new TCon(this.nextConTypeId++, [], 'Bool');
|
||||||
|
@ -348,7 +368,29 @@ export class Checker {
|
||||||
}
|
}
|
||||||
|
|
||||||
private addConstraint(constraint: Constraint): void {
|
private addConstraint(constraint: Constraint): void {
|
||||||
this.constraints[this.constraints.length-1].push(constraint);
|
switch (constraint.kind) {
|
||||||
|
case ConstraintKind.Many:
|
||||||
|
{
|
||||||
|
for (const element of constraint.elements) {
|
||||||
|
this.addConstraint(element);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case ConstraintKind.Equal:
|
||||||
|
{
|
||||||
|
const count = this.constraints.length;
|
||||||
|
for (let i = count-1; i > 0; i--) {
|
||||||
|
const typeVars = this.typeVars[i];
|
||||||
|
const constraints = this.constraints[i];
|
||||||
|
if (typeVars.intersectsType(constraint.left) || typeVars.intersectsType(constraint.right)) {
|
||||||
|
constraints.push(constraint);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.constraints[0].push(constraint);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private pushContext(context: InferContext) {
|
private pushContext(context: InferContext) {
|
||||||
|
@ -361,6 +403,9 @@ export class Checker {
|
||||||
if (context.constraints !== null) {
|
if (context.constraints !== null) {
|
||||||
this.constraints.push(context.constraints);
|
this.constraints.push(context.constraints);
|
||||||
}
|
}
|
||||||
|
if (context.returnType !== null) {
|
||||||
|
this.returnTypes.push(context.returnType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private popContext(context: InferContext) {
|
private popContext(context: InferContext) {
|
||||||
|
@ -373,6 +418,9 @@ export class Checker {
|
||||||
if (context.constraints !== null) {
|
if (context.constraints !== null) {
|
||||||
this.constraints.pop();
|
this.constraints.pop();
|
||||||
}
|
}
|
||||||
|
if (context.returnType !== null) {
|
||||||
|
this.returnTypes.pop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private lookup(name: string): Scheme | null {
|
private lookup(name: string): Scheme | null {
|
||||||
|
@ -430,7 +478,8 @@ export class Checker {
|
||||||
const typeVars = new TVSet();
|
const typeVars = new TVSet();
|
||||||
const env = new TypeEnv();
|
const env = new TypeEnv();
|
||||||
const constraints = new ConstraintSet();
|
const constraints = new ConstraintSet();
|
||||||
const context = { typeVars, env, constraints };
|
const returnType = this.createTypeVar();
|
||||||
|
const context = { typeVars, env, constraints, returnType };
|
||||||
node.context = context;
|
node.context = context;
|
||||||
|
|
||||||
this.pushContext(context);
|
this.pushContext(context);
|
||||||
|
@ -451,6 +500,8 @@ export class Checker {
|
||||||
|
|
||||||
this.popContext(context);
|
this.popContext(context);
|
||||||
|
|
||||||
|
this.inferBindings(node.pattern, type, context.typeVars, context.constraints);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -475,6 +526,25 @@ export class Checker {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case SyntaxKind.IfStatement:
|
||||||
|
{
|
||||||
|
for (const cs of node.cases) {
|
||||||
|
if (cs.test !== null) {
|
||||||
|
this.addConstraint(
|
||||||
|
new CEqual(
|
||||||
|
this.inferExpression(cs.test),
|
||||||
|
this.getBoolType(),
|
||||||
|
cs.test
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (const element of cs.elements) {
|
||||||
|
this.infer(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case SyntaxKind.ReturnStatement:
|
case SyntaxKind.ReturnStatement:
|
||||||
{
|
{
|
||||||
let type;
|
let type;
|
||||||
|
@ -502,7 +572,7 @@ export class Checker {
|
||||||
this.pushContext(context);
|
this.pushContext(context);
|
||||||
|
|
||||||
const paramTypes = [];
|
const paramTypes = [];
|
||||||
const returnType = this.createTypeVar();
|
const returnType = context.returnType;
|
||||||
for (const param of node.params) {
|
for (const param of node.params) {
|
||||||
const paramType = this.createTypeVar()
|
const paramType = this.createTypeVar()
|
||||||
this.inferBindings(param.pattern, paramType, [], []);
|
this.inferBindings(param.pattern, paramType, [], []);
|
||||||
|
@ -524,11 +594,9 @@ export class Checker {
|
||||||
}
|
}
|
||||||
case SyntaxKind.BlockBody:
|
case SyntaxKind.BlockBody:
|
||||||
{
|
{
|
||||||
this.returnTypes.push(returnType);
|
|
||||||
for (const element of node.body.elements) {
|
for (const element of node.body.elements) {
|
||||||
this.infer(element);
|
this.infer(element);
|
||||||
}
|
}
|
||||||
this.returnTypes.pop();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -538,13 +606,10 @@ export class Checker {
|
||||||
|
|
||||||
this.popContext(context);
|
this.popContext(context);
|
||||||
|
|
||||||
this.inferBindings(node.pattern, type, context.typeVars, context.constraints);
|
|
||||||
|
|
||||||
// FIXME these two may need to go below inferBindings
|
// FIXME these two may need to go below inferBindings
|
||||||
//this.typeVars.pop();
|
//this.typeVars.pop();
|
||||||
//this.constraints.pop();
|
//this.constraints.pop();
|
||||||
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -560,15 +625,28 @@ export class Checker {
|
||||||
|
|
||||||
switch (node.kind) {
|
switch (node.kind) {
|
||||||
|
|
||||||
|
case SyntaxKind.NestedExpression:
|
||||||
|
return this.inferExpression(node.expression);
|
||||||
|
|
||||||
case SyntaxKind.ReferenceExpression:
|
case SyntaxKind.ReferenceExpression:
|
||||||
{
|
{
|
||||||
assert(node.name.modulePath.length === 0);
|
assert(node.name.modulePath.length === 0);
|
||||||
|
const target = node.getScope().lookup(node.name.name.text) as LetDeclaration;
|
||||||
|
if (target === node.getScope().node) {
|
||||||
|
return target.type!;
|
||||||
|
}
|
||||||
|
const targetType = this.currentCycle.get(target);
|
||||||
|
if (targetType) {
|
||||||
|
return targetType;
|
||||||
|
}
|
||||||
const scheme = this.lookup(node.name.name.text);
|
const scheme = this.lookup(node.name.name.text);
|
||||||
if (scheme === null) {
|
if (scheme === null) {
|
||||||
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.name.text, node.name.name));
|
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.name.text, node.name.name));
|
||||||
return new TAny();
|
return new TAny();
|
||||||
}
|
}
|
||||||
return this.instantiate(scheme);
|
const type = this.instantiate(scheme);
|
||||||
|
this.currentCycle.set(target, type);
|
||||||
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
case SyntaxKind.CallExpression:
|
case SyntaxKind.CallExpression:
|
||||||
|
@ -641,7 +719,7 @@ export class Checker {
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unexpected ${node}`);
|
throw new Error(`Unexpected ${node.constructor.name}`);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -682,9 +760,124 @@ export class Checker {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public check(node: Syntax): void {
|
private computeReferenceGraph(node: SourceFile): LabeledGraph<Syntax, Syntax> {
|
||||||
|
const graph = new LabeledDirectedHashGraph<Syntax, Syntax>();
|
||||||
|
const visit = (node: Syntax, source: Syntax | null) => {
|
||||||
|
switch (node.kind) {
|
||||||
|
case SyntaxKind.ConstantExpression:
|
||||||
|
break;
|
||||||
|
case SyntaxKind.SourceFile:
|
||||||
|
{
|
||||||
|
for (const element of node.elements) {
|
||||||
|
visit(element, source);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyntaxKind.ReferenceExpression:
|
||||||
|
{
|
||||||
|
// TODO only add references to nodes on the same level
|
||||||
|
assert(node.name.modulePath.length === 0);
|
||||||
|
const target = node.getScope().lookup(node.name.name.text);
|
||||||
|
if (source !== null && target !== null && target.kind === SyntaxKind.LetDeclaration) {
|
||||||
|
graph.addEdge(source, target, node);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyntaxKind.NamedTupleExpression:
|
||||||
|
{
|
||||||
|
for (const arg of node.elements) {
|
||||||
|
visit(arg, source);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyntaxKind.NestedExpression:
|
||||||
|
{
|
||||||
|
visit(node.expression, source);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyntaxKind.InfixExpression:
|
||||||
|
{
|
||||||
|
visit(node.left, source);
|
||||||
|
visit(node.right, source);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyntaxKind.CallExpression:
|
||||||
|
{
|
||||||
|
visit(node.func, source);
|
||||||
|
for (const arg of node.args) {
|
||||||
|
visit(arg, source);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyntaxKind.IfStatement:
|
||||||
|
{
|
||||||
|
for (const cs of node.cases) {
|
||||||
|
if (cs.test !== null) {
|
||||||
|
visit(cs.test, source);
|
||||||
|
}
|
||||||
|
for (const element of cs.elements) {
|
||||||
|
visit(element, source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyntaxKind.ExpressionStatement:
|
||||||
|
{
|
||||||
|
visit(node.expression, source);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyntaxKind.ReturnStatement:
|
||||||
|
{
|
||||||
|
if (node.expression !== null) {
|
||||||
|
visit(node.expression, source);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyntaxKind.LetDeclaration:
|
||||||
|
{
|
||||||
|
graph.addVertex(node);
|
||||||
|
if (node.body !== null) {
|
||||||
|
switch (node.body.kind) {
|
||||||
|
case SyntaxKind.ExprBody:
|
||||||
|
{
|
||||||
|
visit(node.body.expression, node);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyntaxKind.BlockBody:
|
||||||
|
{
|
||||||
|
for (const element of node.body.elements) {
|
||||||
|
visit(element, node);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected ${node.constructor.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visit(node, null);
|
||||||
|
return graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
public check(node: SourceFile): void {
|
||||||
|
|
||||||
|
this.graph = this.computeReferenceGraph(node);
|
||||||
|
|
||||||
|
const typeVars = new TVSet();
|
||||||
const constraints = new ConstraintSet();
|
const constraints = new ConstraintSet();
|
||||||
const env = new TypeEnv();
|
const env = new TypeEnv();
|
||||||
|
|
||||||
|
this.typeVars.push(typeVars);
|
||||||
|
this.constraints.push(constraints);
|
||||||
|
this.typeEnvs.push(env);
|
||||||
|
|
||||||
|
const a = this.createTypeVar();
|
||||||
|
const b = this.createTypeVar();
|
||||||
|
const d = this.createTypeVar();
|
||||||
|
|
||||||
env.set('String', new Forall([], [], this.stringType));
|
env.set('String', new Forall([], [], this.stringType));
|
||||||
env.set('Int', new Forall([], [], this.intType));
|
env.set('Int', new Forall([], [], this.intType));
|
||||||
env.set('True', new Forall([], [], this.boolType));
|
env.set('True', new Forall([], [], this.boolType));
|
||||||
|
@ -693,15 +886,35 @@ export class Checker {
|
||||||
env.set('-', new Forall([], [], new TArrow([ this.intType, this.intType ], this.intType)));
|
env.set('-', new Forall([], [], new TArrow([ this.intType, this.intType ], this.intType)));
|
||||||
env.set('*', new Forall([], [], new TArrow([ this.intType, this.intType ], this.intType)));
|
env.set('*', new Forall([], [], new TArrow([ this.intType, this.intType ], this.intType)));
|
||||||
env.set('/', new Forall([], [], new TArrow([ this.intType, this.intType ], this.intType)));
|
env.set('/', new Forall([], [], new TArrow([ this.intType, this.intType ], this.intType)));
|
||||||
this.typeVars.push(new TVSet);
|
env.set('==', new Forall([ a ], [], new TArrow([ a, a ], this.boolType)));
|
||||||
this.constraints.push(constraints);
|
env.set('not', new Forall([], [], new TArrow([ this.boolType ], this.boolType)));
|
||||||
this.typeEnvs.push(env);
|
|
||||||
this.forwardDeclare(node);
|
//this.infer(node);
|
||||||
this.infer(node);
|
for (const node of this.graph.getVertices()) {
|
||||||
this.solve(new CMany(constraints));
|
this.forwardDeclare(node);
|
||||||
|
}
|
||||||
|
for (const nodes of strongconnect(this.graph)) {
|
||||||
|
this.currentCycle = new Map();
|
||||||
|
for (const node of nodes) {
|
||||||
|
for (const node of nodes) {
|
||||||
|
this.currentCycle.set(node, null);
|
||||||
|
}
|
||||||
|
this.infer(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.currentCycle = new Map();
|
||||||
|
for (const element of node.elements) {
|
||||||
|
if (element.kind !== SyntaxKind.LetDeclaration) {
|
||||||
|
//this.forwardDeclare(element);
|
||||||
|
this.infer(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.typeVars.pop();
|
this.typeVars.pop();
|
||||||
this.constraints.pop();
|
this.constraints.pop();
|
||||||
this.typeEnvs.pop();
|
this.typeEnvs.pop();
|
||||||
|
|
||||||
|
this.solve(new CMany(constraints));
|
||||||
}
|
}
|
||||||
|
|
||||||
private solve(constraint: Constraint): TVSub {
|
private solve(constraint: Constraint): TVSub {
|
||||||
|
@ -756,6 +969,7 @@ export class Checker {
|
||||||
if (left.kind === TypeKind.Var) {
|
if (left.kind === TypeKind.Var) {
|
||||||
if (right.hasTypeVar(left)) {
|
if (right.hasTypeVar(left)) {
|
||||||
// TODO occurs check diagnostic
|
// TODO occurs check diagnostic
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
solution.set(left, right);
|
solution.set(left, right);
|
||||||
return true;
|
return true;
|
||||||
|
@ -765,6 +979,10 @@ export class Checker {
|
||||||
return this.unify(right, left, solution);
|
return this.unify(right, left, solution);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (left.kind === TypeKind.Any || right.kind === TypeKind.Any) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (left.kind === TypeKind.Arrow && right.kind === TypeKind.Arrow) {
|
if (left.kind === TypeKind.Arrow && right.kind === TypeKind.Arrow) {
|
||||||
if (left.paramTypes.length !== right.paramTypes.length) {
|
if (left.paramTypes.length !== right.paramTypes.length) {
|
||||||
this.diagnostics.add(new ArityMismatchDiagnostic(left, right));
|
this.diagnostics.add(new ArityMismatchDiagnostic(left, right));
|
||||||
|
|
287
src/cst.ts
287
src/cst.ts
|
@ -71,6 +71,7 @@ export const enum SyntaxKind {
|
||||||
Identifier,
|
Identifier,
|
||||||
Constructor,
|
Constructor,
|
||||||
CustomOperator,
|
CustomOperator,
|
||||||
|
Assignment,
|
||||||
LParen,
|
LParen,
|
||||||
RParen,
|
RParen,
|
||||||
LBrace,
|
LBrace,
|
||||||
|
@ -111,9 +112,13 @@ export const enum SyntaxKind {
|
||||||
NestedPattern,
|
NestedPattern,
|
||||||
NamedTuplePattern,
|
NamedTuplePattern,
|
||||||
|
|
||||||
|
// Struct expression elements
|
||||||
|
StructExpressionField,
|
||||||
|
PunnedStructExpressionField,
|
||||||
|
|
||||||
// Struct pattern elements
|
// Struct pattern elements
|
||||||
FieldStructPatternElement,
|
StructPatternField,
|
||||||
PunnedFieldStructPatternElement,
|
PunnedStructPatternField,
|
||||||
VariadicStructPatternElement,
|
VariadicStructPatternElement,
|
||||||
|
|
||||||
// Expressions
|
// Expressions
|
||||||
|
@ -131,6 +136,7 @@ export const enum SyntaxKind {
|
||||||
// Statements
|
// Statements
|
||||||
ReturnStatement,
|
ReturnStatement,
|
||||||
ExpressionStatement,
|
ExpressionStatement,
|
||||||
|
IfStatement,
|
||||||
|
|
||||||
// Declarations
|
// Declarations
|
||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
|
@ -149,6 +155,7 @@ export const enum SyntaxKind {
|
||||||
StructDeclarationField,
|
StructDeclarationField,
|
||||||
|
|
||||||
// Other nodes
|
// Other nodes
|
||||||
|
IfStatementCase,
|
||||||
Initializer,
|
Initializer,
|
||||||
QualifiedName,
|
QualifiedName,
|
||||||
TypeAssert,
|
TypeAssert,
|
||||||
|
@ -175,6 +182,89 @@ function isIgnoredProperty(key: string): boolean {
|
||||||
return key === 'kind' || key === 'parent';
|
return key === 'kind' || key === 'parent';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NodeWithScope
|
||||||
|
= SourceFile
|
||||||
|
| LetDeclaration
|
||||||
|
|
||||||
|
function isNodeWithScope(node: Syntax): node is NodeWithScope {
|
||||||
|
return node.kind === SyntaxKind.SourceFile
|
||||||
|
|| node.kind === SyntaxKind.LetDeclaration;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Scope {
|
||||||
|
|
||||||
|
private mapping = new Map<string, Syntax>();
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public node: NodeWithScope,
|
||||||
|
) {
|
||||||
|
this.scan(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getParent(): Scope | null {
|
||||||
|
let curr = this.node.parent;
|
||||||
|
while (curr !== null) {
|
||||||
|
if (isNodeWithScope(curr)) {
|
||||||
|
return curr.getScope();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private scan(node: Syntax): void {
|
||||||
|
switch (node.kind) {
|
||||||
|
case SyntaxKind.SourceFile:
|
||||||
|
{
|
||||||
|
for (const element of node.elements) {
|
||||||
|
this.scan(element);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SyntaxKind.ExpressionStatement:
|
||||||
|
case SyntaxKind.ReturnStatement:
|
||||||
|
break;
|
||||||
|
case SyntaxKind.LetDeclaration:
|
||||||
|
{
|
||||||
|
for (const param of node.params) {
|
||||||
|
this.scanPattern(param.pattern, param);
|
||||||
|
}
|
||||||
|
if (node !== this.node) {
|
||||||
|
this.scanPattern(node.pattern, node);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected ${node.constructor.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private scanPattern(node: Pattern, decl: Syntax): void {
|
||||||
|
switch (node.kind) {
|
||||||
|
case SyntaxKind.BindPattern:
|
||||||
|
{
|
||||||
|
this.mapping.set(node.name.text, decl);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected ${node}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public lookup(name: string): Syntax | null {
|
||||||
|
let curr: Scope | null = this;
|
||||||
|
do {
|
||||||
|
const decl = curr.mapping.get(name);
|
||||||
|
if (decl !== undefined) {
|
||||||
|
return decl;
|
||||||
|
}
|
||||||
|
curr = curr.getParent();
|
||||||
|
} while (curr !== null);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
abstract class SyntaxBase {
|
abstract class SyntaxBase {
|
||||||
|
|
||||||
public parent: Syntax | null = null;
|
public parent: Syntax | null = null;
|
||||||
|
@ -203,6 +293,20 @@ abstract class SyntaxBase {
|
||||||
throw new Error(`Could not find a SourceFile in any of the parent nodes of ${this}`);
|
throw new Error(`Could not find a SourceFile in any of the parent nodes of ${this}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getScope(): Scope {
|
||||||
|
let curr: Syntax | null = this as any;
|
||||||
|
do {
|
||||||
|
if (isNodeWithScope(curr!)) {
|
||||||
|
if (curr.scope === undefined) {
|
||||||
|
curr.scope = new Scope(curr);
|
||||||
|
}
|
||||||
|
return curr.scope;
|
||||||
|
}
|
||||||
|
curr = curr!.parent;
|
||||||
|
} while (curr !== null);
|
||||||
|
throw new Error(`Could not find a scope for ${this}. Maybe the parent links are not set?`);
|
||||||
|
}
|
||||||
|
|
||||||
public setParents(): void {
|
public setParents(): void {
|
||||||
|
|
||||||
const visit = (value: any) => {
|
const visit = (value: any) => {
|
||||||
|
@ -437,6 +541,19 @@ export class CustomOperator extends TokenBase {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Assignment extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.Assignment;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public text: string,
|
||||||
|
startPos: TextPosition,
|
||||||
|
) {
|
||||||
|
super(startPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export class LParen extends TokenBase {
|
export class LParen extends TokenBase {
|
||||||
|
|
||||||
public readonly kind = SyntaxKind.LParen;
|
public readonly kind = SyntaxKind.LParen;
|
||||||
|
@ -547,6 +664,36 @@ export class Equals extends TokenBase {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class IfKeyword extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.IfKeyword;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return 'if';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ElseKeyword extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.ElseKeyword;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return 'else';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ElifKeyword extends TokenBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.ElifKeyword;
|
||||||
|
|
||||||
|
public get text(): string {
|
||||||
|
return 'elif';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export class StructKeyword extends TokenBase {
|
export class StructKeyword extends TokenBase {
|
||||||
|
|
||||||
public readonly kind = SyntaxKind.StructKeyword;
|
public readonly kind = SyntaxKind.StructKeyword;
|
||||||
|
@ -667,6 +814,10 @@ export type Token
|
||||||
| BlockStart
|
| BlockStart
|
||||||
| BlockEnd
|
| BlockEnd
|
||||||
| LineFoldEnd
|
| LineFoldEnd
|
||||||
|
| Assignment
|
||||||
|
| IfKeyword
|
||||||
|
| ElseKeyword
|
||||||
|
| ElifKeyword
|
||||||
|
|
||||||
export type TokenKind
|
export type TokenKind
|
||||||
= Token['kind']
|
= Token['kind']
|
||||||
|
@ -768,9 +919,9 @@ export class NamedTuplePattern extends SyntaxBase {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FieldStructPatternElement extends SyntaxBase {
|
export class StructPatternField extends SyntaxBase {
|
||||||
|
|
||||||
public readonly kind = SyntaxKind.FieldStructPatternElement;
|
public readonly kind = SyntaxKind.StructPatternField;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
public name: Identifier,
|
public name: Identifier,
|
||||||
|
@ -814,9 +965,9 @@ export class VariadicStructPatternElement extends SyntaxBase {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PunnedFieldStructPatternElement extends SyntaxBase {
|
export class PunnedStructPatternField extends SyntaxBase {
|
||||||
|
|
||||||
public readonly kind = SyntaxKind.PunnedFieldStructPatternElement;
|
public readonly kind = SyntaxKind.PunnedStructPatternField;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
public name: Identifier,
|
public name: Identifier,
|
||||||
|
@ -836,8 +987,8 @@ export class PunnedFieldStructPatternElement extends SyntaxBase {
|
||||||
|
|
||||||
export type StructPatternElement
|
export type StructPatternElement
|
||||||
= VariadicStructPatternElement
|
= VariadicStructPatternElement
|
||||||
| PunnedFieldStructPatternElement
|
| PunnedStructPatternField
|
||||||
| FieldStructPatternElement
|
| StructPatternField
|
||||||
|
|
||||||
export class StructPattern extends SyntaxBase {
|
export class StructPattern extends SyntaxBase {
|
||||||
|
|
||||||
|
@ -1003,6 +1154,75 @@ export class CallExpression extends SyntaxBase {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class StructExpressionField extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.StructExpressionField;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public name: Identifier,
|
||||||
|
public equals: Equals,
|
||||||
|
public expression: Expression,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFirstToken(): Token {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLastToken(): Token {
|
||||||
|
return this.expression.getLastToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PunnedStructExpressionField extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.PunnedStructExpressionField;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public name: Identifier,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFirstToken(): Token {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLastToken(): Token {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StructExpressionElement
|
||||||
|
= StructExpressionField
|
||||||
|
| PunnedStructExpressionField;
|
||||||
|
|
||||||
|
export class StructExpression extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.StructExpression;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public name: Constructor,
|
||||||
|
public lbrace: LBrace,
|
||||||
|
public elements: StructExpressionElement[],
|
||||||
|
public rbrace: RBrace,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFirstToken(): Token {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLastToken(): Token {
|
||||||
|
return this.rbrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export class NamedTupleExpression extends SyntaxBase {
|
export class NamedTupleExpression extends SyntaxBase {
|
||||||
|
|
||||||
public readonly kind = SyntaxKind.NamedTupleExpression;
|
public readonly kind = SyntaxKind.NamedTupleExpression;
|
||||||
|
@ -1113,6 +1333,7 @@ export class InfixExpression extends SyntaxBase {
|
||||||
|
|
||||||
export type Expression
|
export type Expression
|
||||||
= CallExpression
|
= CallExpression
|
||||||
|
| StructExpression
|
||||||
| NamedTupleExpression
|
| NamedTupleExpression
|
||||||
| ReferenceExpression
|
| ReferenceExpression
|
||||||
| ConstantExpression
|
| ConstantExpression
|
||||||
|
@ -1122,6 +1343,52 @@ export type Expression
|
||||||
| InfixExpression
|
| InfixExpression
|
||||||
| PostfixExpression
|
| PostfixExpression
|
||||||
|
|
||||||
|
export class IfStatementCase extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.IfStatementCase;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public keyword: IfKeyword | ElseKeyword | ElifKeyword,
|
||||||
|
public test: Expression | null,
|
||||||
|
public blockStart: BlockStart,
|
||||||
|
public elements: LetBodyElement[],
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFirstToken(): Token {
|
||||||
|
return this.keyword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLastToken(): Token {
|
||||||
|
if (this.elements.length > 0) {
|
||||||
|
return this.elements[this.elements.length-1].getLastToken();
|
||||||
|
}
|
||||||
|
return this.blockStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class IfStatement extends SyntaxBase {
|
||||||
|
|
||||||
|
public readonly kind = SyntaxKind.IfStatement;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public cases: IfStatementCase[],
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFirstToken(): Token {
|
||||||
|
return this.cases[0].getFirstToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLastToken(): Token {
|
||||||
|
return this.cases[this.cases.length-1].getLastToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export class ReturnStatement extends SyntaxBase {
|
export class ReturnStatement extends SyntaxBase {
|
||||||
|
|
||||||
public readonly kind = SyntaxKind.ReturnStatement;
|
public readonly kind = SyntaxKind.ReturnStatement;
|
||||||
|
@ -1169,6 +1436,7 @@ export class ExpressionStatement extends SyntaxBase {
|
||||||
export type Statement
|
export type Statement
|
||||||
= ReturnStatement
|
= ReturnStatement
|
||||||
| ExpressionStatement
|
| ExpressionStatement
|
||||||
|
| IfStatement
|
||||||
|
|
||||||
export class Param extends SyntaxBase {
|
export class Param extends SyntaxBase {
|
||||||
|
|
||||||
|
@ -1314,6 +1582,7 @@ export class LetDeclaration extends SyntaxBase {
|
||||||
|
|
||||||
public readonly kind = SyntaxKind.LetDeclaration;
|
public readonly kind = SyntaxKind.LetDeclaration;
|
||||||
|
|
||||||
|
public scope?: Scope;
|
||||||
public type?: Type;
|
public type?: Type;
|
||||||
public context?: InferContext;
|
public context?: InferContext;
|
||||||
|
|
||||||
|
@ -1433,6 +1702,8 @@ export class SourceFile extends SyntaxBase {
|
||||||
|
|
||||||
public readonly kind = SyntaxKind.SourceFile;
|
public readonly kind = SyntaxKind.SourceFile;
|
||||||
|
|
||||||
|
public scope?: Scope;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private file: TextFile,
|
private file: TextFile,
|
||||||
public elements: SourceFileElement[],
|
public elements: SourceFileElement[],
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
|
|
||||||
import { kill } from "process";
|
import { TypeKind, type Type, type TArrow } from "./checker";
|
||||||
import { TypeKind, Type } from "./checker";
|
|
||||||
import { Syntax, SyntaxKind, TextFile, TextPosition, TextRange, Token } from "./cst";
|
import { Syntax, SyntaxKind, TextFile, TextPosition, TextRange, Token } from "./cst";
|
||||||
import { countDigits } from "./util";
|
import { countDigits } from "./util";
|
||||||
|
|
||||||
|
@ -47,7 +46,7 @@ export class UnexpectedCharDiagnostic {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DESCRIPTIONS: Record<SyntaxKind, string> = {
|
const DESCRIPTIONS: Partial<Record<SyntaxKind, string>> = {
|
||||||
[SyntaxKind.StringLiteral]: 'a string literal',
|
[SyntaxKind.StringLiteral]: 'a string literal',
|
||||||
[SyntaxKind.Identifier]: "an identifier",
|
[SyntaxKind.Identifier]: "an identifier",
|
||||||
[SyntaxKind.Comma]: "','",
|
[SyntaxKind.Comma]: "','",
|
||||||
|
@ -206,11 +205,32 @@ export class UnificationFailedDiagnostic {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ArityMismatchDiagnostic {
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public left: TArrow,
|
||||||
|
public right: TArrow,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public format(): string {
|
||||||
|
return ANSI_FG_RED + ANSI_BOLD + 'error: ' + ANSI_RESET
|
||||||
|
+ ANSI_FG_GREEN + describeType(this.left) + ANSI_RESET
|
||||||
|
+ ` has ${this.left.paramTypes.length} `
|
||||||
|
+ (this.left.paramTypes.length === 1 ? 'parameter' : 'parameters')
|
||||||
|
+ ' while ' + ANSI_FG_GREEN + describeType(this.right) + ANSI_RESET
|
||||||
|
+ ` has ${this.right.paramTypes.length}.\n\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export type Diagnostic
|
export type Diagnostic
|
||||||
= UnexpectedCharDiagnostic
|
= UnexpectedCharDiagnostic
|
||||||
| BindingNotFoudDiagnostic
|
| BindingNotFoudDiagnostic
|
||||||
| UnificationFailedDiagnostic
|
| UnificationFailedDiagnostic
|
||||||
| UnexpectedTokenDiagnostic
|
| UnexpectedTokenDiagnostic
|
||||||
|
| ArityMismatchDiagnostic
|
||||||
|
|
||||||
export class Diagnostics {
|
export class Diagnostics {
|
||||||
|
|
||||||
|
|
112
src/parser.ts
112
src/parser.ts
|
@ -1,4 +1,6 @@
|
||||||
|
|
||||||
|
import { warn } from "console";
|
||||||
|
import { argv0 } from "process";
|
||||||
import {
|
import {
|
||||||
ReferenceTypeExpression,
|
ReferenceTypeExpression,
|
||||||
SourceFile,
|
SourceFile,
|
||||||
|
@ -30,8 +32,8 @@ import {
|
||||||
NamedTuplePattern,
|
NamedTuplePattern,
|
||||||
StructPattern,
|
StructPattern,
|
||||||
VariadicStructPatternElement,
|
VariadicStructPatternElement,
|
||||||
PunnedFieldStructPatternElement,
|
PunnedStructPatternField,
|
||||||
FieldStructPatternElement,
|
StructPatternField,
|
||||||
TuplePattern,
|
TuplePattern,
|
||||||
InfixExpression,
|
InfixExpression,
|
||||||
TextFile,
|
TextFile,
|
||||||
|
@ -39,6 +41,11 @@ import {
|
||||||
NamedTupleExpression,
|
NamedTupleExpression,
|
||||||
LetBodyElement,
|
LetBodyElement,
|
||||||
ReturnStatement,
|
ReturnStatement,
|
||||||
|
StructExpression,
|
||||||
|
StructExpressionField,
|
||||||
|
PunnedStructExpressionField,
|
||||||
|
IfStatementCase,
|
||||||
|
IfStatement,
|
||||||
} from "./cst"
|
} from "./cst"
|
||||||
import { Stream } from "./util";
|
import { Stream } from "./util";
|
||||||
|
|
||||||
|
@ -218,11 +225,39 @@ export class Parser {
|
||||||
if (t1.kind === SyntaxKind.LBrace) {
|
if (t1.kind === SyntaxKind.LBrace) {
|
||||||
this.getToken();
|
this.getToken();
|
||||||
const fields = [];
|
const fields = [];
|
||||||
let rparen;
|
let rbrace;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
const t2 = this.peekToken();
|
||||||
|
if (t2.kind === SyntaxKind.RBrace) {
|
||||||
|
rbrace = t2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let field;
|
||||||
|
const t3 = this.getToken();
|
||||||
|
if (t3.kind === SyntaxKind.Identifier) {
|
||||||
|
const t4 = this.peekToken();
|
||||||
|
if (t4.kind === SyntaxKind.Equals) {
|
||||||
|
this.getToken();
|
||||||
|
const expression = this.parseExpression();
|
||||||
|
field = new StructExpressionField(t3, t4, expression);
|
||||||
|
} else {
|
||||||
|
field = new PunnedStructExpressionField(t3);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO add spread fields
|
||||||
|
this.raiseParseError(t3, [ SyntaxKind.Identifier ]);
|
||||||
|
}
|
||||||
|
fields.push(field);
|
||||||
|
const t5 = this.peekToken();
|
||||||
|
if (t5.kind === SyntaxKind.Comma) {
|
||||||
|
this.getToken();
|
||||||
|
continue;
|
||||||
|
} else if (t5.kind === SyntaxKind.RBrace) {
|
||||||
|
rbrace = t5;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return new StructExpression(t0, t1, fields, rparen);
|
return new StructExpression(t0, t1, fields, rbrace);
|
||||||
}
|
}
|
||||||
const elements = [];
|
const elements = [];
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
@ -258,6 +293,7 @@ export class Parser {
|
||||||
const t1 = this.peekToken();
|
const t1 = this.peekToken();
|
||||||
if (t1.kind === SyntaxKind.LineFoldEnd
|
if (t1.kind === SyntaxKind.LineFoldEnd
|
||||||
|| t1.kind === SyntaxKind.RParen
|
|| t1.kind === SyntaxKind.RParen
|
||||||
|
|| t1.kind === SyntaxKind.BlockStart
|
||||||
|| isBinaryOperatorLike(t1)
|
|| isBinaryOperatorLike(t1)
|
||||||
|| isPrefixOperatorLike(t1)) {
|
|| isPrefixOperatorLike(t1)) {
|
||||||
break;
|
break;
|
||||||
|
@ -365,9 +401,9 @@ export class Parser {
|
||||||
if (t4.kind === SyntaxKind.Equals) {
|
if (t4.kind === SyntaxKind.Equals) {
|
||||||
this.getToken();
|
this.getToken();
|
||||||
const pattern = this.parsePattern();
|
const pattern = this.parsePattern();
|
||||||
fields.push(new FieldStructPatternElement(t3, t4, pattern));
|
fields.push(new StructPatternField(t3, t4, pattern));
|
||||||
} else {
|
} else {
|
||||||
fields.push(new PunnedFieldStructPatternElement(t3));
|
fields.push(new PunnedStructPatternField(t3));
|
||||||
}
|
}
|
||||||
} else if (t3.kind === SyntaxKind.DotDot) {
|
} else if (t3.kind === SyntaxKind.DotDot) {
|
||||||
this.getToken();
|
this.getToken();
|
||||||
|
@ -457,6 +493,8 @@ export class Parser {
|
||||||
return this.parseLetDeclaration();
|
return this.parseLetDeclaration();
|
||||||
case SyntaxKind.ReturnKeyword:
|
case SyntaxKind.ReturnKeyword:
|
||||||
return this.parseReturnStatement();
|
return this.parseReturnStatement();
|
||||||
|
case SyntaxKind.IfKeyword:
|
||||||
|
return this.parseIfStatement();
|
||||||
default:
|
default:
|
||||||
// TODO convert parse errors to include LetKeyword and ReturnKeyword
|
// TODO convert parse errors to include LetKeyword and ReturnKeyword
|
||||||
return this.parseExpressionStatement();
|
return this.parseExpressionStatement();
|
||||||
|
@ -545,6 +583,63 @@ export class Parser {
|
||||||
return new ExpressionStatement(expression);
|
return new ExpressionStatement(expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public parseIfStatement(): IfStatement {
|
||||||
|
const ifKeyword = this.expectToken(SyntaxKind.IfKeyword);
|
||||||
|
const test = this.parseExpression();
|
||||||
|
const blockStart = this.expectToken(SyntaxKind.BlockStart);
|
||||||
|
const elements = [];
|
||||||
|
for (;;) {
|
||||||
|
const t1 = this.peekToken();
|
||||||
|
if (t1.kind === SyntaxKind.BlockEnd) {
|
||||||
|
this.getToken();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
elements.push(this.parseLetBodyElement());
|
||||||
|
}
|
||||||
|
this.expectToken(SyntaxKind.LineFoldEnd);
|
||||||
|
const cases = [];
|
||||||
|
cases.push(new IfStatementCase(ifKeyword, test, blockStart, elements));
|
||||||
|
for (;;) {
|
||||||
|
const t2 = this.peekToken();
|
||||||
|
if (t2.kind === SyntaxKind.ElseKeyword) {
|
||||||
|
this.getToken();
|
||||||
|
const blockStart = this.expectToken(SyntaxKind.BlockStart);
|
||||||
|
const elements = [];
|
||||||
|
for (;;) {
|
||||||
|
const t3 = this.peekToken();
|
||||||
|
if (t3.kind === SyntaxKind.BlockEnd) {
|
||||||
|
this.getToken();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
elements.push(this.parseLetBodyElement());
|
||||||
|
}
|
||||||
|
this.expectToken(SyntaxKind.LineFoldEnd);
|
||||||
|
cases.push(new IfStatementCase(t2, null, blockStart, elements));
|
||||||
|
break;
|
||||||
|
} else if (t2.kind === SyntaxKind.ElifKeyword) {
|
||||||
|
this.getToken();
|
||||||
|
const test = this.parseExpression();
|
||||||
|
const blockStart = this.expectToken(SyntaxKind.BlockStart);
|
||||||
|
for (;;) {
|
||||||
|
const t4 = this.peekToken();
|
||||||
|
if (t4.kind === SyntaxKind.BlockEnd) {
|
||||||
|
this.getToken();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
elements.push(this.parseLetBodyElement());
|
||||||
|
}
|
||||||
|
this.expectToken(SyntaxKind.LineFoldEnd);
|
||||||
|
cases.push(new IfStatementCase(t2, test, blockStart, elements));
|
||||||
|
} else if (t2.kind === SyntaxKind.LineFoldEnd) {
|
||||||
|
this.getToken();
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
this.raiseParseError(t2, [ SyntaxKind.ElifKeyword, SyntaxKind.ElseKeyword, SyntaxKind.LineFoldEnd ]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new IfStatement(cases);
|
||||||
|
}
|
||||||
|
|
||||||
public parseReturnStatement(): ReturnStatement {
|
public parseReturnStatement(): ReturnStatement {
|
||||||
const returnKeyword = this.expectToken(SyntaxKind.ReturnKeyword);
|
const returnKeyword = this.expectToken(SyntaxKind.ReturnKeyword);
|
||||||
let expression = null;
|
let expression = null;
|
||||||
|
@ -563,7 +658,6 @@ export class Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
public parseSourceFileElement(): SourceFileElement {
|
public parseSourceFileElement(): SourceFileElement {
|
||||||
|
|
||||||
const t0 = this.peekTokenAfterModifiers();
|
const t0 = this.peekTokenAfterModifiers();
|
||||||
switch (t0.kind) {
|
switch (t0.kind) {
|
||||||
case SyntaxKind.LetKeyword:
|
case SyntaxKind.LetKeyword:
|
||||||
|
@ -572,6 +666,8 @@ export class Parser {
|
||||||
return this.parseImportDeclaration();
|
return this.parseImportDeclaration();
|
||||||
case SyntaxKind.StructKeyword:
|
case SyntaxKind.StructKeyword:
|
||||||
return this.parseStructDeclaration();
|
return this.parseStructDeclaration();
|
||||||
|
case SyntaxKind.IfKeyword:
|
||||||
|
return this.parseIfStatement();
|
||||||
default:
|
default:
|
||||||
return this.parseExpressionStatement();
|
return this.parseExpressionStatement();
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,10 @@ import {
|
||||||
TextFile,
|
TextFile,
|
||||||
Dot,
|
Dot,
|
||||||
DotDot,
|
DotDot,
|
||||||
|
Assignment,
|
||||||
|
ElifKeyword,
|
||||||
|
ElseKeyword,
|
||||||
|
IfKeyword,
|
||||||
} from "./cst"
|
} from "./cst"
|
||||||
import { Diagnostics, UnexpectedCharDiagnostic } from "./diagnostics"
|
import { Diagnostics, UnexpectedCharDiagnostic } from "./diagnostics"
|
||||||
import { Stream, BufferedStream, assert } from "./util";
|
import { Stream, BufferedStream, assert } from "./util";
|
||||||
|
@ -63,7 +67,7 @@ function toDecimal(ch: string): number {
|
||||||
}
|
}
|
||||||
|
|
||||||
function isOperatorPart(ch: string): boolean {
|
function isOperatorPart(ch: string): boolean {
|
||||||
return /\+-*\/%^&|$<>!?=/.test(ch);
|
return /[+\-*\/%^&|$<>!?=]/.test(ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ScanError extends Error {
|
export class ScanError extends Error {
|
||||||
|
@ -244,7 +248,7 @@ export class Scanner extends BufferedStream<Token> {
|
||||||
if (text === '=') {
|
if (text === '=') {
|
||||||
return new Equals(startPos);
|
return new Equals(startPos);
|
||||||
} else if (text.endsWith('=') && text[text.length-2] !== '=') {
|
} else if (text.endsWith('=') && text[text.length-2] !== '=') {
|
||||||
return new Assignment(startPos);
|
return new Assignment(text, startPos);
|
||||||
} else {
|
} else {
|
||||||
return new CustomOperator(text, startPos);
|
return new CustomOperator(text, startPos);
|
||||||
}
|
}
|
||||||
|
@ -344,6 +348,9 @@ export class Scanner extends BufferedStream<Token> {
|
||||||
case 'import': return new ImportKeyword(startPos);
|
case 'import': return new ImportKeyword(startPos);
|
||||||
case 'return': return new ReturnKeyword(startPos);
|
case 'return': return new ReturnKeyword(startPos);
|
||||||
case 'type': return new TypeKeyword(startPos);
|
case 'type': return new TypeKeyword(startPos);
|
||||||
|
case 'if': return new IfKeyword(startPos);
|
||||||
|
case 'else': return new ElseKeyword(startPos);
|
||||||
|
case 'elif': return new ElifKeyword(startPos);
|
||||||
default:
|
default:
|
||||||
if (isUpper(text[0])) {
|
if (isUpper(text[0])) {
|
||||||
return new Constructor(text, startPos);
|
return new Constructor(text, startPos);
|
||||||
|
|
Loading…
Reference in a new issue