Type constraints
This commit is contained in:
parent
f1bba4f58d
commit
784e095345
4 changed files with 46 additions and 4 deletions
|
|
@ -195,8 +195,7 @@ range : Int \ Int \ List Int = start end \ start >= end
|
||||||
| True \ []
|
| True \ []
|
||||||
| False \ [start, ...range (start + 1) end];
|
| False \ [start, ...range (start + 1) end];
|
||||||
|
|
||||||
# TODO Number
|
sum : Num a :: List a \ a = list \ fold add 0 list;
|
||||||
sum : List a \ a = list \ fold add 0 list;
|
|
||||||
|
|
||||||
any : (a \ Bool) \ List a \ Bool = f list \ fold (acc x \ or acc (f x)) False list;
|
any : (a \ Bool) \ List a \ Bool = f list \ fold (acc x \ or acc (f x)) False list;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ export type Token = (
|
||||||
|
|
||||||
// Symbols
|
// Symbols
|
||||||
| { kind: 'colon' }
|
| { kind: 'colon' }
|
||||||
|
| { kind: 'double-colon' }
|
||||||
| { kind: 'colon-equals' }
|
| { kind: 'colon-equals' }
|
||||||
| { kind: 'semicolon' }
|
| { kind: 'semicolon' }
|
||||||
| { kind: 'backslash' }
|
| { kind: 'backslash' }
|
||||||
|
|
@ -216,7 +217,10 @@ export function tokenize(source: string): Token[] {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ':': {
|
case ':': {
|
||||||
if (source[i + 1] === '=') {
|
if (source[i + 1] === ':') {
|
||||||
|
tokens.push({ kind: 'double-colon', line: startLine, column: startColumn, start });
|
||||||
|
advance();
|
||||||
|
} else if (source[i + 1] === '=') {
|
||||||
tokens.push({ kind: 'colon-equals', line: startLine, column: startColumn, start });
|
tokens.push({ kind: 'colon-equals', line: startLine, column: startColumn, start });
|
||||||
advance();
|
advance();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,7 @@ export class Parser {
|
||||||
let annotation: Annotation | undefined;
|
let annotation: Annotation | undefined;
|
||||||
if (this.current().kind === 'colon') {
|
if (this.current().kind === 'colon') {
|
||||||
this.advance();
|
this.advance();
|
||||||
annotation = { constraints: [], type: this.parseType() };
|
annotation = { constraints: this.parsedConstraints, type: this.parseType() };
|
||||||
|
|
||||||
// Declaration only
|
// Declaration only
|
||||||
if (this.current().kind === 'semicolon') {
|
if (this.current().kind === 'semicolon') {
|
||||||
|
|
@ -629,7 +629,12 @@ export class Parser {
|
||||||
kind === 'open-paren' || kind === 'open-brace';
|
kind === 'open-paren' || kind === 'open-brace';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private parsedConstraints: { className: string, typeVar: string }[] = [];
|
||||||
|
|
||||||
private parseType(): TypeAST {
|
private parseType(): TypeAST {
|
||||||
|
// Check for constraints: Num a, Eq b :: <type>
|
||||||
|
this.parsedConstraints = this.tryParseConstraints();
|
||||||
|
|
||||||
const left = this.parseTypeApply();
|
const left = this.parseTypeApply();
|
||||||
|
|
||||||
if (this.current().kind === 'backslash') {
|
if (this.current().kind === 'backslash') {
|
||||||
|
|
@ -641,6 +646,34 @@ export class Parser {
|
||||||
return left;
|
return left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private tryParseConstraints(): { className: string, typeVar: string }[] {
|
||||||
|
// Look ahead for :: to decide if we have constraints
|
||||||
|
let offset = 0;
|
||||||
|
let foundDoubleColon = false;
|
||||||
|
while (true) {
|
||||||
|
const t = this.peek(offset);
|
||||||
|
if (t.kind === 'double-colon') { foundDoubleColon = true; break }
|
||||||
|
if (t.kind === 'backslash' || t.kind === 'semicolon' || t.kind === 'equals' || t.kind === 'eof') break;
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
if (!foundDoubleColon) return [];
|
||||||
|
|
||||||
|
// Parse constraints: ClassName varName (, ClassName varName)*
|
||||||
|
const constraints: { className: string, typeVar: string }[] = [];
|
||||||
|
while (true) {
|
||||||
|
const className = (this.expect('type-ident') as { value: string }).value;
|
||||||
|
const typeVar = (this.expect('ident') as { value: string }).value;
|
||||||
|
constraints.push({ className, typeVar });
|
||||||
|
if (this.current().kind === 'comma') {
|
||||||
|
this.advance();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.expect('double-colon');
|
||||||
|
return constraints;
|
||||||
|
}
|
||||||
|
|
||||||
private parseTypeDefinition(): TypeDefinition {
|
private parseTypeDefinition(): TypeDefinition {
|
||||||
const nameToken = this.advance();
|
const nameToken = this.advance();
|
||||||
const name = (nameToken as { value: string }).value;
|
const name = (nameToken as { value: string }).value;
|
||||||
|
|
|
||||||
|
|
@ -418,6 +418,12 @@ export function typecheck(defs: Definition[], typeDefs: TypeDefinition[] = [], c
|
||||||
for (const def of defs) {
|
for (const def of defs) {
|
||||||
if (def.annotation) {
|
if (def.annotation) {
|
||||||
env.set(def.name, def.annotation.type);
|
env.set(def.name, def.annotation.type);
|
||||||
|
|
||||||
|
// Register explicit constraints
|
||||||
|
if (def.annotation.constraints.length > 0) {
|
||||||
|
const c = def.annotation.constraints[0];
|
||||||
|
moduleConstraints.set(def.name, { param: c.typeVar, className: c.className });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue