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 \ []
|
||||
| False \ [start, ...range (start + 1) end];
|
||||
|
||||
# TODO Number
|
||||
sum : List a \ a = list \ fold add 0 list;
|
||||
sum : Num a :: 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;
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export type Token = (
|
|||
|
||||
// Symbols
|
||||
| { kind: 'colon' }
|
||||
| { kind: 'double-colon' }
|
||||
| { kind: 'colon-equals' }
|
||||
| { kind: 'semicolon' }
|
||||
| { kind: 'backslash' }
|
||||
|
|
@ -216,7 +217,10 @@ export function tokenize(source: string): Token[] {
|
|||
break;
|
||||
}
|
||||
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 });
|
||||
advance();
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ export class Parser {
|
|||
let annotation: Annotation | undefined;
|
||||
if (this.current().kind === 'colon') {
|
||||
this.advance();
|
||||
annotation = { constraints: [], type: this.parseType() };
|
||||
annotation = { constraints: this.parsedConstraints, type: this.parseType() };
|
||||
|
||||
// Declaration only
|
||||
if (this.current().kind === 'semicolon') {
|
||||
|
|
@ -629,7 +629,12 @@ export class Parser {
|
|||
kind === 'open-paren' || kind === 'open-brace';
|
||||
}
|
||||
|
||||
private parsedConstraints: { className: string, typeVar: string }[] = [];
|
||||
|
||||
private parseType(): TypeAST {
|
||||
// Check for constraints: Num a, Eq b :: <type>
|
||||
this.parsedConstraints = this.tryParseConstraints();
|
||||
|
||||
const left = this.parseTypeApply();
|
||||
|
||||
if (this.current().kind === 'backslash') {
|
||||
|
|
@ -641,6 +646,34 @@ export class Parser {
|
|||
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 {
|
||||
const nameToken = this.advance();
|
||||
const name = (nameToken as { value: string }).value;
|
||||
|
|
|
|||
|
|
@ -418,6 +418,12 @@ export function typecheck(defs: Definition[], typeDefs: TypeDefinition[] = [], c
|
|||
for (const def of defs) {
|
||||
if (def.annotation) {
|
||||
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