more CG syntax. parsing the easy stuff
This commit is contained in:
parent
71237d0307
commit
1ed325e98b
3 changed files with 204 additions and 82 deletions
114
src/ast.ts
114
src/ast.ts
|
|
@ -1,35 +1,24 @@
|
|||
import type { Value } from './types';
|
||||
|
||||
|
||||
// Literals and Variables
|
||||
|
||||
export type Literal = {
|
||||
kind: 'literal'
|
||||
value: Value
|
||||
}
|
||||
|
||||
export type BinaryOp = {
|
||||
kind: 'binaryop'
|
||||
operator: string
|
||||
left: AST
|
||||
right: AST
|
||||
}
|
||||
|
||||
export type Variable = {
|
||||
kind: 'variable'
|
||||
name: string
|
||||
}
|
||||
|
||||
export type Let = {
|
||||
kind: 'let'
|
||||
export type Constructor = {
|
||||
kind: 'constructor'
|
||||
name: string
|
||||
value: AST
|
||||
body: AST
|
||||
}
|
||||
|
||||
export type If = {
|
||||
kind: 'if'
|
||||
condition: AST
|
||||
then: AST
|
||||
else: AST
|
||||
}
|
||||
// Functions
|
||||
|
||||
export type Lambda = {
|
||||
kind: 'lambda'
|
||||
|
|
@ -43,4 +32,93 @@ export type Apply = {
|
|||
args: AST[]
|
||||
}
|
||||
|
||||
export type AST = Literal | BinaryOp | Variable | Let | If | Lambda | Apply
|
||||
// Bindings
|
||||
|
||||
export type Let = {
|
||||
kind: 'let'
|
||||
name: string
|
||||
value: AST
|
||||
body: AST
|
||||
}
|
||||
|
||||
// Matching
|
||||
|
||||
export type Match = {
|
||||
kind: 'match'
|
||||
expr: AST
|
||||
cases: MatchCase[]
|
||||
}
|
||||
|
||||
export type MatchCase = {
|
||||
pattern: Pattern
|
||||
result: AST
|
||||
}
|
||||
|
||||
export type Pattern =
|
||||
| { kind: 'wildcard' }
|
||||
| { kind: 'var', name: string }
|
||||
| { kind: 'literal', value: number | string }
|
||||
| { kind: 'constructor', name: string, args: Pattern[] }
|
||||
| { kind: 'list', elements: Pattern[] }
|
||||
| { kind: 'record', fields: { [key: string]: Pattern } }
|
||||
|
||||
// Data Structures
|
||||
|
||||
export type Record = {
|
||||
kind: 'record'
|
||||
fields: { [key: string]: AST }
|
||||
}
|
||||
|
||||
export type RecordAccess = {
|
||||
kind: 'record-access'
|
||||
record: AST
|
||||
field: string
|
||||
}
|
||||
|
||||
export type RecordUpdate = {
|
||||
kind: 'record-update'
|
||||
record: AST
|
||||
updates: { [key: string]: AST }
|
||||
}
|
||||
|
||||
export type List = {
|
||||
kind: 'list'
|
||||
elements: AST[]
|
||||
}
|
||||
|
||||
// Top-level constructs
|
||||
|
||||
export type Definition = {
|
||||
kind: 'definition'
|
||||
name: string
|
||||
body: AST
|
||||
// type?: string // TODO
|
||||
}
|
||||
|
||||
export type TypeDef = {
|
||||
kind: 'typedef'
|
||||
name: string
|
||||
variants: Array<{ name: string, args: string[] }>
|
||||
}
|
||||
|
||||
export type Import = {
|
||||
kind: 'import'
|
||||
module: string
|
||||
items: string[] | 'all'
|
||||
}
|
||||
|
||||
export type AST =
|
||||
| Literal
|
||||
| Variable
|
||||
| Constructor
|
||||
| Lambda
|
||||
| Apply
|
||||
| Let
|
||||
| Match
|
||||
| Record
|
||||
| RecordAccess
|
||||
| RecordUpdate
|
||||
| List
|
||||
| Definition
|
||||
| TypeDef
|
||||
| Import
|
||||
|
|
|
|||
39
src/main.ts
39
src/main.ts
|
|
@ -1,31 +1,10 @@
|
|||
import { evaluate } from './interpreter'
|
||||
import type { AST } from './ast'
|
||||
import type { Env } from './env'
|
||||
// import { evaluate } from './interpreter'
|
||||
// import type { AST } from './ast'
|
||||
// import type { Env } from './env'
|
||||
import { tokenize } from './lexer'
|
||||
import { Parser } from './parser'
|
||||
|
||||
const ast: AST = {
|
||||
kind: 'binaryop',
|
||||
operator: '+',
|
||||
left: {
|
||||
kind: 'literal',
|
||||
value: { kind: 'int', value: 1 }
|
||||
},
|
||||
right: {
|
||||
kind: 'binaryop',
|
||||
operator: '*',
|
||||
left: {
|
||||
kind: 'literal',
|
||||
value: { kind: 'int', value: 2 }
|
||||
},
|
||||
right: {
|
||||
kind: 'literal',
|
||||
value: { kind: 'int', value: 3 }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const env: Env = new Map();
|
||||
// const env: Env = new Map();
|
||||
|
||||
// const res = evaluate(ast, env);
|
||||
|
||||
|
|
@ -42,13 +21,13 @@ console.log(tokenize(str));
|
|||
// const p = new Parser(tokens);
|
||||
// console.log(p.parse());
|
||||
|
||||
/*
|
||||
const tokens2 = tokenize("let x = (y) => 5 + y in x(3)");
|
||||
// const tokens2 = tokenize("let x = (y) => 5 + y in x(3)");
|
||||
// const tokens2 = tokenize("let x = 5 in x * 4");
|
||||
// const tokens2 = tokenize("(x, y) => x + y");
|
||||
const tokens2 = tokenize('[1, 2, 3]');
|
||||
const p2 = new Parser(tokens2);
|
||||
const ast3 = p2.parse();
|
||||
const env3: Env = new Map();
|
||||
console.log(ast3);
|
||||
console.log(evaluate(ast3, env3));
|
||||
*/
|
||||
// const env3: Env = new Map();
|
||||
// console.log(ast3);
|
||||
// console.log(evaluate(ast3, env3));
|
||||
|
|
|
|||
133
src/parser.ts
133
src/parser.ts
|
|
@ -14,6 +14,7 @@ export class Parser {
|
|||
if (this.pos >= this.tokens.length) {
|
||||
return { kind: 'eof' } as Token;
|
||||
}
|
||||
|
||||
return this.tokens[this.pos];
|
||||
}
|
||||
|
||||
|
|
@ -21,63 +22,134 @@ export class Parser {
|
|||
return this.tokens[this.pos++];
|
||||
}
|
||||
|
||||
private expect(kind: Token['kind']): Token {
|
||||
const token = this.current();
|
||||
|
||||
if (token.kind !== kind) {
|
||||
throw new Error(`Expected ${kind}, got ${token.kind}`);
|
||||
}
|
||||
|
||||
return this.advance();
|
||||
}
|
||||
|
||||
private isInfixOp(): boolean {
|
||||
const kind = this.current().kind;
|
||||
return kind === 'plus' || kind === 'minus' ||
|
||||
kind === 'star' || kind === 'slash' ||
|
||||
kind === 'greater-than' || kind === 'ampersand';
|
||||
|
||||
}
|
||||
|
||||
private tokenToOpName(token: Token): string {
|
||||
switch (token.kind) {
|
||||
case 'plus': return '+';
|
||||
case 'minus': return '-';
|
||||
case 'star': return '*';
|
||||
case 'slash': return '/';
|
||||
case 'greater-than': return '>';
|
||||
case 'ampersand': return '&';
|
||||
default: throw new Error(`Not an operator: ${token.kind}`);
|
||||
}
|
||||
}
|
||||
|
||||
parse(): AST {
|
||||
return this.parseExpression();
|
||||
}
|
||||
|
||||
private parseExpression(): AST {
|
||||
if (this.current().kind === 'let') {
|
||||
return this.parseLet();
|
||||
let left = this.parsePrimary();
|
||||
|
||||
while (this.isInfixOp()) {
|
||||
const opToken = this.advance();
|
||||
const opName = this.tokenToOpName(opToken);
|
||||
const right = this.parsePrimary();
|
||||
|
||||
left = {
|
||||
kind: 'apply',
|
||||
func: { kind: 'variable', name: opName },
|
||||
args: [left, right]
|
||||
}
|
||||
}
|
||||
return this.parseAdditive();
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
private parsePrimary(): AST {
|
||||
const token = this.current();
|
||||
|
||||
if (token.kind === 'open-paren') {
|
||||
const savedPos = this.pos;
|
||||
this.advance();
|
||||
|
||||
let isLambda = false;
|
||||
if (this.current().kind === 'close-paren') {
|
||||
isLambda = true;
|
||||
} else if (this.current().kind === 'ident') {
|
||||
let tempPos = this.pos;
|
||||
this.advance();
|
||||
if (this.current().kind === 'comma' || (this.current().kind === 'close-paren' && this.tokens[this.pos + 1]?.kind === 'arrow')) {
|
||||
isLambda = true;
|
||||
}
|
||||
this.pos = tempPos;
|
||||
}
|
||||
|
||||
this.pos = savedPos;
|
||||
|
||||
if (isLambda) {
|
||||
return this.parseLambda();
|
||||
}
|
||||
|
||||
this.advance();
|
||||
const expr = this.parseExpression();
|
||||
this.expect('close-paren');
|
||||
return expr;
|
||||
}
|
||||
|
||||
if (token.kind === 'number') {
|
||||
if (token.kind === 'open-bracket') {
|
||||
this.advance();
|
||||
|
||||
const items: AST[] = [];
|
||||
let first = true;
|
||||
|
||||
while (this.current().kind !== 'close-bracket') {
|
||||
if (!first) {
|
||||
this.expect('comma');
|
||||
}
|
||||
first = false;
|
||||
|
||||
items.push(this.parseExpression());
|
||||
}
|
||||
|
||||
this.expect('close-bracket');
|
||||
return { kind: 'list', elements: items };
|
||||
}
|
||||
|
||||
if (token.kind === 'open-brace') {
|
||||
this.advance();
|
||||
|
||||
const fields: { [key: string]: AST } = {};
|
||||
let first = true;
|
||||
|
||||
while (this.current().kind !== 'close-brace') {
|
||||
if (!first) {
|
||||
this.expect('comma');
|
||||
}
|
||||
first = false;
|
||||
|
||||
const keyToken = this.expect('ident');
|
||||
const key = (keyToken as { value: string }).value;
|
||||
this.expect('equals');
|
||||
fields[key] = this.parseExpression();
|
||||
}
|
||||
|
||||
this.expect('close-brace');
|
||||
return { kind: 'record', fields };
|
||||
}
|
||||
|
||||
if (token.kind === 'int') {
|
||||
this.advance();
|
||||
return { kind: 'literal', value: { kind: 'int', value: token.value } };
|
||||
}
|
||||
|
||||
if (token.kind === 'float') {
|
||||
this.advance();
|
||||
return { kind: 'literal', value: { kind: 'float', value: token.value } };
|
||||
}
|
||||
|
||||
if (token.kind === 'string') {
|
||||
this.advance();
|
||||
return { kind: 'literal', value: { kind: 'string', value: token.value } };
|
||||
}
|
||||
|
||||
if (token.kind === 'ident') {
|
||||
this.advance();
|
||||
|
||||
return { kind: 'variable', name: token.value };
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected token: ${token.kind}`);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
private parseMultiplicative(): AST {
|
||||
let left = this.parsePostfix();
|
||||
|
||||
|
|
@ -165,13 +237,6 @@ export class Parser {
|
|||
|
||||
return expr;
|
||||
}
|
||||
*/
|
||||
|
||||
private expect(kind: Token['kind']): Token {
|
||||
const token = this.current();
|
||||
if (token.kind !== kind) {
|
||||
throw new Error(`Expected ${kind}, got ${token.kind}`);
|
||||
}
|
||||
|
||||
return this.advance();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue