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": {
|
||||
"source-map-support": "^0.5.21",
|
||||
"tslib": "^2.4.0",
|
||||
"yagl": "^0.5.0",
|
||||
"yargs": "^17.5.1"
|
||||
},
|
||||
"bin": {
|
||||
"bolt": "lib/bin/bolt.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.7.13",
|
||||
"@types/yargs": "^17.0.11"
|
||||
"@types/node": "^18.7.14",
|
||||
"@types/yargs": "^17.0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.7.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.13.tgz",
|
||||
"integrity": "sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw==",
|
||||
"version": "18.7.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.14.tgz",
|
||||
"integrity": "sha512-6bbDaETVi8oyIARulOE9qF1/Qdi/23z6emrUh0fNJRUmjznqrixD4MpGDdgOFk5Xb0m2H6Xu42JGdvAxaJR/wA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/yargs": {
|
||||
"version": "17.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.11.tgz",
|
||||
"integrity": "sha512-aB4y9UDUXTSMxmM4MH+YnuR0g5Cph3FLQBoWoMB21DSvFVAxRVEHEMx3TLh+zUZYMCQtKiqazz0Q4Rre31f/OA==",
|
||||
"version": "17.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz",
|
||||
"integrity": "sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/yargs-parser": "*"
|
||||
|
@ -202,6 +203,11 @@
|
|||
"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": {
|
||||
"version": "17.5.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz",
|
||||
|
@ -230,15 +236,15 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "18.7.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.13.tgz",
|
||||
"integrity": "sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw==",
|
||||
"version": "18.7.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.14.tgz",
|
||||
"integrity": "sha512-6bbDaETVi8oyIARulOE9qF1/Qdi/23z6emrUh0fNJRUmjznqrixD4MpGDdgOFk5Xb0m2H6Xu42JGdvAxaJR/wA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "17.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.11.tgz",
|
||||
"integrity": "sha512-aB4y9UDUXTSMxmM4MH+YnuR0g5Cph3FLQBoWoMB21DSvFVAxRVEHEMx3TLh+zUZYMCQtKiqazz0Q4Rre31f/OA==",
|
||||
"version": "17.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz",
|
||||
"integrity": "sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/yargs-parser": "*"
|
||||
|
@ -368,6 +374,11 @@
|
|||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
"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": {
|
||||
"version": "17.5.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz",
|
||||
|
|
|
@ -24,10 +24,11 @@
|
|||
"dependencies": {
|
||||
"source-map-support": "^0.5.21",
|
||||
"tslib": "^2.4.0",
|
||||
"yagl": "^0.5.0",
|
||||
"yargs": "^17.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.7.13",
|
||||
"@types/yargs": "^17.0.11"
|
||||
"@types/node": "^18.7.14",
|
||||
"@types/yargs": "^17.0.12"
|
||||
}
|
||||
}
|
||||
|
|
256
src/checker.ts
256
src/checker.ts
|
@ -1,12 +1,15 @@
|
|||
import {
|
||||
Expression,
|
||||
LetDeclaration,
|
||||
Pattern,
|
||||
SourceFile,
|
||||
Syntax,
|
||||
SyntaxKind,
|
||||
TypeExpression
|
||||
} from "./cst";
|
||||
import { BindingNotFoudDiagnostic, Diagnostics, UnificationFailedDiagnostic } from "./diagnostics";
|
||||
import { ArityMismatchDiagnostic, BindingNotFoudDiagnostic, Diagnostics, UnificationFailedDiagnostic } from "./diagnostics";
|
||||
import { assert } from "./util";
|
||||
import { LabeledDirectedHashGraph, LabeledGraph, strongconnect, toposort } from "yagl"
|
||||
|
||||
export enum TypeKind {
|
||||
Arrow,
|
||||
|
@ -55,7 +58,7 @@ class TVar extends TypeBase {
|
|||
|
||||
}
|
||||
|
||||
class TArrow extends TypeBase {
|
||||
export class TArrow extends TypeBase {
|
||||
|
||||
public readonly kind = TypeKind.Arrow;
|
||||
|
||||
|
@ -186,6 +189,19 @@ class TVSet {
|
|||
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 {
|
||||
this.mapping.delete(tv.id);
|
||||
}
|
||||
|
@ -307,6 +323,7 @@ export interface InferContext {
|
|||
typeVars: TVSet;
|
||||
env: TypeEnv;
|
||||
constraints: ConstraintSet;
|
||||
returnType: Type;
|
||||
}
|
||||
|
||||
export class Checker {
|
||||
|
@ -314,6 +331,9 @@ export class Checker {
|
|||
private nextTypeVarId = 0;
|
||||
private nextConTypeId = 0;
|
||||
|
||||
private graph?: LabeledGraph<Syntax, Syntax>;
|
||||
private currentCycle?: Map<Syntax, Type>;
|
||||
|
||||
private stringType = new TCon(this.nextConTypeId++, [], 'String');
|
||||
private intType = new TCon(this.nextConTypeId++, [], 'Int');
|
||||
private boolType = new TCon(this.nextConTypeId++, [], 'Bool');
|
||||
|
@ -348,7 +368,29 @@ export class Checker {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
@ -361,6 +403,9 @@ export class Checker {
|
|||
if (context.constraints !== null) {
|
||||
this.constraints.push(context.constraints);
|
||||
}
|
||||
if (context.returnType !== null) {
|
||||
this.returnTypes.push(context.returnType);
|
||||
}
|
||||
}
|
||||
|
||||
private popContext(context: InferContext) {
|
||||
|
@ -373,6 +418,9 @@ export class Checker {
|
|||
if (context.constraints !== null) {
|
||||
this.constraints.pop();
|
||||
}
|
||||
if (context.returnType !== null) {
|
||||
this.returnTypes.pop();
|
||||
}
|
||||
}
|
||||
|
||||
private lookup(name: string): Scheme | null {
|
||||
|
@ -430,7 +478,8 @@ export class Checker {
|
|||
const typeVars = new TVSet();
|
||||
const env = new TypeEnv();
|
||||
const constraints = new ConstraintSet();
|
||||
const context = { typeVars, env, constraints };
|
||||
const returnType = this.createTypeVar();
|
||||
const context = { typeVars, env, constraints, returnType };
|
||||
node.context = context;
|
||||
|
||||
this.pushContext(context);
|
||||
|
@ -451,6 +500,8 @@ export class Checker {
|
|||
|
||||
this.popContext(context);
|
||||
|
||||
this.inferBindings(node.pattern, type, context.typeVars, context.constraints);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -475,6 +526,25 @@ export class Checker {
|
|||
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:
|
||||
{
|
||||
let type;
|
||||
|
@ -502,7 +572,7 @@ export class Checker {
|
|||
this.pushContext(context);
|
||||
|
||||
const paramTypes = [];
|
||||
const returnType = this.createTypeVar();
|
||||
const returnType = context.returnType;
|
||||
for (const param of node.params) {
|
||||
const paramType = this.createTypeVar()
|
||||
this.inferBindings(param.pattern, paramType, [], []);
|
||||
|
@ -524,11 +594,9 @@ export class Checker {
|
|||
}
|
||||
case SyntaxKind.BlockBody:
|
||||
{
|
||||
this.returnTypes.push(returnType);
|
||||
for (const element of node.body.elements) {
|
||||
this.infer(element);
|
||||
}
|
||||
this.returnTypes.pop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -538,13 +606,10 @@ export class Checker {
|
|||
|
||||
this.popContext(context);
|
||||
|
||||
this.inferBindings(node.pattern, type, context.typeVars, context.constraints);
|
||||
|
||||
// FIXME these two may need to go below inferBindings
|
||||
//this.typeVars.pop();
|
||||
//this.constraints.pop();
|
||||
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
@ -560,15 +625,28 @@ export class Checker {
|
|||
|
||||
switch (node.kind) {
|
||||
|
||||
case SyntaxKind.NestedExpression:
|
||||
return this.inferExpression(node.expression);
|
||||
|
||||
case SyntaxKind.ReferenceExpression:
|
||||
{
|
||||
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);
|
||||
if (scheme === null) {
|
||||
this.diagnostics.add(new BindingNotFoudDiagnostic(node.name.name.text, node.name.name));
|
||||
return new TAny();
|
||||
}
|
||||
return this.instantiate(scheme);
|
||||
const type = this.instantiate(scheme);
|
||||
this.currentCycle.set(target, type);
|
||||
return type;
|
||||
}
|
||||
|
||||
case SyntaxKind.CallExpression:
|
||||
|
@ -641,7 +719,7 @@ export class Checker {
|
|||
}
|
||||
|
||||
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 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('Int', new Forall([], [], this.intType));
|
||||
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)));
|
||||
this.typeVars.push(new TVSet);
|
||||
this.constraints.push(constraints);
|
||||
this.typeEnvs.push(env);
|
||||
this.forwardDeclare(node);
|
||||
this.infer(node);
|
||||
this.solve(new CMany(constraints));
|
||||
env.set('==', new Forall([ a ], [], new TArrow([ a, a ], this.boolType)));
|
||||
env.set('not', new Forall([], [], new TArrow([ this.boolType ], this.boolType)));
|
||||
|
||||
//this.infer(node);
|
||||
for (const node of this.graph.getVertices()) {
|
||||
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.constraints.pop();
|
||||
this.typeEnvs.pop();
|
||||
|
||||
this.solve(new CMany(constraints));
|
||||
}
|
||||
|
||||
private solve(constraint: Constraint): TVSub {
|
||||
|
@ -756,6 +969,7 @@ export class Checker {
|
|||
if (left.kind === TypeKind.Var) {
|
||||
if (right.hasTypeVar(left)) {
|
||||
// TODO occurs check diagnostic
|
||||
return false;
|
||||
}
|
||||
solution.set(left, right);
|
||||
return true;
|
||||
|
@ -765,6 +979,10 @@ export class Checker {
|
|||
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.paramTypes.length !== right.paramTypes.length) {
|
||||
this.diagnostics.add(new ArityMismatchDiagnostic(left, right));
|
||||
|
|
287
src/cst.ts
287
src/cst.ts
|
@ -71,6 +71,7 @@ export const enum SyntaxKind {
|
|||
Identifier,
|
||||
Constructor,
|
||||
CustomOperator,
|
||||
Assignment,
|
||||
LParen,
|
||||
RParen,
|
||||
LBrace,
|
||||
|
@ -111,9 +112,13 @@ export const enum SyntaxKind {
|
|||
NestedPattern,
|
||||
NamedTuplePattern,
|
||||
|
||||
// Struct expression elements
|
||||
StructExpressionField,
|
||||
PunnedStructExpressionField,
|
||||
|
||||
// Struct pattern elements
|
||||
FieldStructPatternElement,
|
||||
PunnedFieldStructPatternElement,
|
||||
StructPatternField,
|
||||
PunnedStructPatternField,
|
||||
VariadicStructPatternElement,
|
||||
|
||||
// Expressions
|
||||
|
@ -131,6 +136,7 @@ export const enum SyntaxKind {
|
|||
// Statements
|
||||
ReturnStatement,
|
||||
ExpressionStatement,
|
||||
IfStatement,
|
||||
|
||||
// Declarations
|
||||
VariableDeclaration,
|
||||
|
@ -149,6 +155,7 @@ export const enum SyntaxKind {
|
|||
StructDeclarationField,
|
||||
|
||||
// Other nodes
|
||||
IfStatementCase,
|
||||
Initializer,
|
||||
QualifiedName,
|
||||
TypeAssert,
|
||||
|
@ -175,6 +182,89 @@ function isIgnoredProperty(key: string): boolean {
|
|||
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 {
|
||||
|
||||
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}`);
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
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 {
|
||||
|
||||
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 {
|
||||
|
||||
public readonly kind = SyntaxKind.StructKeyword;
|
||||
|
@ -667,6 +814,10 @@ export type Token
|
|||
| BlockStart
|
||||
| BlockEnd
|
||||
| LineFoldEnd
|
||||
| Assignment
|
||||
| IfKeyword
|
||||
| ElseKeyword
|
||||
| ElifKeyword
|
||||
|
||||
export type TokenKind
|
||||
= 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 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 name: Identifier,
|
||||
|
@ -836,8 +987,8 @@ export class PunnedFieldStructPatternElement extends SyntaxBase {
|
|||
|
||||
export type StructPatternElement
|
||||
= VariadicStructPatternElement
|
||||
| PunnedFieldStructPatternElement
|
||||
| FieldStructPatternElement
|
||||
| PunnedStructPatternField
|
||||
| StructPatternField
|
||||
|
||||
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 {
|
||||
|
||||
public readonly kind = SyntaxKind.NamedTupleExpression;
|
||||
|
@ -1113,6 +1333,7 @@ export class InfixExpression extends SyntaxBase {
|
|||
|
||||
export type Expression
|
||||
= CallExpression
|
||||
| StructExpression
|
||||
| NamedTupleExpression
|
||||
| ReferenceExpression
|
||||
| ConstantExpression
|
||||
|
@ -1122,6 +1343,52 @@ export type Expression
|
|||
| InfixExpression
|
||||
| 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 {
|
||||
|
||||
public readonly kind = SyntaxKind.ReturnStatement;
|
||||
|
@ -1169,6 +1436,7 @@ export class ExpressionStatement extends SyntaxBase {
|
|||
export type Statement
|
||||
= ReturnStatement
|
||||
| ExpressionStatement
|
||||
| IfStatement
|
||||
|
||||
export class Param extends SyntaxBase {
|
||||
|
||||
|
@ -1314,6 +1582,7 @@ export class LetDeclaration extends SyntaxBase {
|
|||
|
||||
public readonly kind = SyntaxKind.LetDeclaration;
|
||||
|
||||
public scope?: Scope;
|
||||
public type?: Type;
|
||||
public context?: InferContext;
|
||||
|
||||
|
@ -1433,6 +1702,8 @@ export class SourceFile extends SyntaxBase {
|
|||
|
||||
public readonly kind = SyntaxKind.SourceFile;
|
||||
|
||||
public scope?: Scope;
|
||||
|
||||
public constructor(
|
||||
private file: TextFile,
|
||||
public elements: SourceFileElement[],
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
import { kill } from "process";
|
||||
import { TypeKind, Type } from "./checker";
|
||||
import { TypeKind, type Type, type TArrow } from "./checker";
|
||||
import { Syntax, SyntaxKind, TextFile, TextPosition, TextRange, Token } from "./cst";
|
||||
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.Identifier]: "an identifier",
|
||||
[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
|
||||
= UnexpectedCharDiagnostic
|
||||
| BindingNotFoudDiagnostic
|
||||
| UnificationFailedDiagnostic
|
||||
| UnexpectedTokenDiagnostic
|
||||
| ArityMismatchDiagnostic
|
||||
|
||||
export class Diagnostics {
|
||||
|
||||
|
|
112
src/parser.ts
112
src/parser.ts
|
@ -1,4 +1,6 @@
|
|||
|
||||
import { warn } from "console";
|
||||
import { argv0 } from "process";
|
||||
import {
|
||||
ReferenceTypeExpression,
|
||||
SourceFile,
|
||||
|
@ -30,8 +32,8 @@ import {
|
|||
NamedTuplePattern,
|
||||
StructPattern,
|
||||
VariadicStructPatternElement,
|
||||
PunnedFieldStructPatternElement,
|
||||
FieldStructPatternElement,
|
||||
PunnedStructPatternField,
|
||||
StructPatternField,
|
||||
TuplePattern,
|
||||
InfixExpression,
|
||||
TextFile,
|
||||
|
@ -39,6 +41,11 @@ import {
|
|||
NamedTupleExpression,
|
||||
LetBodyElement,
|
||||
ReturnStatement,
|
||||
StructExpression,
|
||||
StructExpressionField,
|
||||
PunnedStructExpressionField,
|
||||
IfStatementCase,
|
||||
IfStatement,
|
||||
} from "./cst"
|
||||
import { Stream } from "./util";
|
||||
|
||||
|
@ -218,11 +225,39 @@ export class Parser {
|
|||
if (t1.kind === SyntaxKind.LBrace) {
|
||||
this.getToken();
|
||||
const fields = [];
|
||||
let rparen;
|
||||
let rbrace;
|
||||
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 = [];
|
||||
for (;;) {
|
||||
|
@ -258,6 +293,7 @@ export class Parser {
|
|||
const t1 = this.peekToken();
|
||||
if (t1.kind === SyntaxKind.LineFoldEnd
|
||||
|| t1.kind === SyntaxKind.RParen
|
||||
|| t1.kind === SyntaxKind.BlockStart
|
||||
|| isBinaryOperatorLike(t1)
|
||||
|| isPrefixOperatorLike(t1)) {
|
||||
break;
|
||||
|
@ -365,9 +401,9 @@ export class Parser {
|
|||
if (t4.kind === SyntaxKind.Equals) {
|
||||
this.getToken();
|
||||
const pattern = this.parsePattern();
|
||||
fields.push(new FieldStructPatternElement(t3, t4, pattern));
|
||||
fields.push(new StructPatternField(t3, t4, pattern));
|
||||
} else {
|
||||
fields.push(new PunnedFieldStructPatternElement(t3));
|
||||
fields.push(new PunnedStructPatternField(t3));
|
||||
}
|
||||
} else if (t3.kind === SyntaxKind.DotDot) {
|
||||
this.getToken();
|
||||
|
@ -457,6 +493,8 @@ export class Parser {
|
|||
return this.parseLetDeclaration();
|
||||
case SyntaxKind.ReturnKeyword:
|
||||
return this.parseReturnStatement();
|
||||
case SyntaxKind.IfKeyword:
|
||||
return this.parseIfStatement();
|
||||
default:
|
||||
// TODO convert parse errors to include LetKeyword and ReturnKeyword
|
||||
return this.parseExpressionStatement();
|
||||
|
@ -545,6 +583,63 @@ export class Parser {
|
|||
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 {
|
||||
const returnKeyword = this.expectToken(SyntaxKind.ReturnKeyword);
|
||||
let expression = null;
|
||||
|
@ -563,7 +658,6 @@ export class Parser {
|
|||
}
|
||||
|
||||
public parseSourceFileElement(): SourceFileElement {
|
||||
|
||||
const t0 = this.peekTokenAfterModifiers();
|
||||
switch (t0.kind) {
|
||||
case SyntaxKind.LetKeyword:
|
||||
|
@ -572,6 +666,8 @@ export class Parser {
|
|||
return this.parseImportDeclaration();
|
||||
case SyntaxKind.StructKeyword:
|
||||
return this.parseStructDeclaration();
|
||||
case SyntaxKind.IfKeyword:
|
||||
return this.parseIfStatement();
|
||||
default:
|
||||
return this.parseExpressionStatement();
|
||||
}
|
||||
|
|
|
@ -30,6 +30,10 @@ import {
|
|||
TextFile,
|
||||
Dot,
|
||||
DotDot,
|
||||
Assignment,
|
||||
ElifKeyword,
|
||||
ElseKeyword,
|
||||
IfKeyword,
|
||||
} from "./cst"
|
||||
import { Diagnostics, UnexpectedCharDiagnostic } from "./diagnostics"
|
||||
import { Stream, BufferedStream, assert } from "./util";
|
||||
|
@ -63,7 +67,7 @@ function toDecimal(ch: string): number {
|
|||
}
|
||||
|
||||
function isOperatorPart(ch: string): boolean {
|
||||
return /\+-*\/%^&|$<>!?=/.test(ch);
|
||||
return /[+\-*\/%^&|$<>!?=]/.test(ch);
|
||||
}
|
||||
|
||||
export class ScanError extends Error {
|
||||
|
@ -244,7 +248,7 @@ export class Scanner extends BufferedStream<Token> {
|
|||
if (text === '=') {
|
||||
return new Equals(startPos);
|
||||
} else if (text.endsWith('=') && text[text.length-2] !== '=') {
|
||||
return new Assignment(startPos);
|
||||
return new Assignment(text, startPos);
|
||||
} else {
|
||||
return new CustomOperator(text, startPos);
|
||||
}
|
||||
|
@ -344,6 +348,9 @@ export class Scanner extends BufferedStream<Token> {
|
|||
case 'import': return new ImportKeyword(startPos);
|
||||
case 'return': return new ReturnKeyword(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:
|
||||
if (isUpper(text[0])) {
|
||||
return new Constructor(text, startPos);
|
||||
|
|
Loading…
Reference in a new issue