Starting to add types

This commit is contained in:
Dustin Swan 2026-03-25 19:58:37 -06:00
parent a8e879289d
commit 3132b79aae
No known key found for this signature in database
GPG key ID: 30D46587E2100467
5 changed files with 203 additions and 9 deletions

View file

@ -1,5 +1,5 @@
import type { Token } from './lexer'
import type { AST, MatchCase, Pattern, Definition } from './ast'
import type { AST, MatchCase, Pattern, Definition, TypeAST, TypeDefinition, TypeConstructor } from './ast'
import { ParseError } from './error'
export class Parser {
@ -150,14 +150,35 @@ export class Parser {
}
}
parse(): Definition[] {
parse(): { definitions: Definition[], typeDefinitions: TypeDefinition[] } {
const definitions: Definition[] = [];
const typeDefinitions: TypeDefinition[] = [];
while (this.current().kind !== 'eof') {
if (this.current().kind === 'type-ident') {
typeDefinitions.push(this.parseTypeDefinition());
continue;
}
// type annotation
if (this.current().kind === 'ident' && this.peek().kind === 'colon') {
this.advance(); // eat ident
this.advance();
const type = this.parseType();
this.expect('semicolon');
// parse definition
const def = this.parseDefinition();
def.annotation = { constraints: [], type };
definitions.push(def);
continue;
}
definitions.push(this.parseDefinition());
}
return definitions;
return { definitions, typeDefinitions };
}
private parseDefinition(): Definition {
@ -545,4 +566,103 @@ export class Parser {
throw ParseError(`Unexpected token: ${token.kind}`, token.line, token.column, this.source);
}
private parseTypeAtom(): TypeAST {
const token = this.current();
if (token.kind === 'type-ident') {
this.advance();
return { kind: 'type-name', name: token.value };
}
if (token.kind === 'ident') {
this.advance();
return { kind: 'type-var', name: token.value };
}
if (token.kind === 'open-paren') {
this.advance();
const type = this.parseType();
this.expect('close-paren');
return type;
}
if (token.kind === 'open-brace') {
this.advance();
const fields: { name: string, type: TypeAST }[] = [];
while (this.current().kind !== 'close-brace') {
const name = this.expectIdent() as { value: string };
this.expect('colon');
const type = this.parseType();
fields.push({ name: name.value, type });
if (this.current().kind === 'comma') this.advance();
}
this.expect('close-brace');
return { kind: 'type-record', fields };
}
throw ParseError(`Expected type, got ${token.kind}`, token.line, token.column, this.source);
}
private parseTypeApply(): TypeAST {
const base = this.parseTypeAtom();
const args: TypeAST[] = [];
while (this.canStartTypeAtom()) {
args.push(this.parseTypeAtom());
}
if (args.length === 0) return base;
return { kind: 'type-apply', constructor: base, args };
}
private canStartTypeAtom(): boolean {
const kind = this.current().kind;
return kind === 'type-ident' || kind === 'ident' ||
kind === 'open-paren' || kind === 'open-brace';
}
private parseType(): TypeAST {
const left = this.parseTypeApply();
if (this.current().kind === 'backslash') {
this.advance();
const right = this.parseType();
return { kind: 'type-function', param: left, result: right };
}
return left;
}
private parseTypeDefinition(): TypeDefinition {
const nameToken = this.advance();
const name = (nameToken as { value: string }).value;
// Collect type params
const params: string[] = [];
while (this.current().kind === 'ident') {
params.push((this.advance() as { value: string }).value);
}
this.expect('equals');
// Parse constructors separated by |
const constructors: TypeConstructor[] = [];
constructors.push(this.parseTypeConstructor());
while (this.current().kind === 'pipe') {
this.advance();
constructors.push(this.parseTypeConstructor());
}
if (this.current().kind === 'semicolon') this.advance();
return { kind: 'type-definition', name, params, constructors, ...this.getPos(nameToken) };
}
private parseTypeConstructor(): TypeConstructor {
const name = (this.expect('type-ident') as { value: string }).value;
const args: TypeAST[] = [];
while (this.canStartTypeAtom()) {
args.push(this.parseTypeAtom());
}
return { name, args };
}
}