Adding spread operator. starting to build a stdlib. omg

master
Dustin Swan 4 days ago
parent 216fe6bd30
commit 9edee10508
Signed by: dustinswan
GPG Key ID: 30D46587E2100467

@ -59,13 +59,14 @@ export type Pattern =
| { kind: 'literal', value: number | string } | { kind: 'literal', value: number | string }
| { kind: 'constructor', name: string, args: Pattern[] } | { kind: 'constructor', name: string, args: Pattern[] }
| { kind: 'list', elements: Pattern[] } | { kind: 'list', elements: Pattern[] }
| { kind: 'list-spread', head: Pattern[], spread: string }
| { kind: 'record', fields: { [key: string]: Pattern } } | { kind: 'record', fields: { [key: string]: Pattern } }
// Data Structures // Data Structures
export type List = { export type List = {
kind: 'list' kind: 'list'
elements: AST[] elements: (AST | { spread: AST })[]
} }
export type Record = { export type Record = {

@ -16,11 +16,25 @@ export function evaluate(ast: AST, env: Env): Value {
return val; return val;
} }
case 'list': case 'list': {
return { const elements: Value[] = [];
kind: 'list',
elements: ast.elements.map(el => evaluate(el, env)) for (const item of ast.elements) {
}; // Spread
if ('spread' in item) {
const spreadValue = evaluate(item.spread, env);
if (spreadValue.kind !== 'list')
throw new Error('can only spread lists');
elements.push(...spreadValue.elements);
} else {
elements.push(evaluate(item, env));
}
}
return { kind: 'list', elements };
}
case 'record': case 'record':
const fields: { [key: string]: Value } = {}; const fields: { [key: string]: Value } = {};
@ -226,6 +240,23 @@ function matchPattern(value: Value, pattern: Pattern): Bindings | null {
return bindings; return bindings;
} }
case 'list-spread': {
if (value.kind !== 'list') return null;
if (value.elements.length < pattern.head.length) return null;
const bindings: Bindings = {};
for (let i = 0; i < pattern.head.length; i++) {
const elemBindings = matchPattern(value.elements[i], pattern.head[i]);
if (elemBindings === null) return null;
Object.assign(bindings, elemBindings);
}
const rest = value.elements.slice(pattern.head.length);
bindings[pattern.spread] = { kind: 'list', elements: rest };
return bindings;
}
case 'record': { case 'record': {
if (value.kind !== 'record') return null; if (value.kind !== 'record') return null;

@ -33,6 +33,7 @@ export type Token =
| { kind: 'ampersand' } | { kind: 'ampersand' }
| { kind: 'underscore' } | { kind: 'underscore' }
| { kind: 'dot' } | { kind: 'dot' }
| { kind: 'dot-dot-dot' }
| { kind: 'at' } | { kind: 'at' }
// Anithmetic // Anithmetic
@ -148,7 +149,7 @@ export function tokenize(source: string): Token[] {
switch (char) { switch (char) {
case '>': { case '>': {
if (source[i + 1] == '=') { if (source[i + 1] === '=') {
tokens.push({ kind: 'greater-equals' }); tokens.push({ kind: 'greater-equals' });
i++; i++;
} else { } else {
@ -157,7 +158,7 @@ export function tokenize(source: string): Token[] {
break; break;
} }
case '<': { case '<': {
if (source[i + 1] == '=') { if (source[i + 1] === '=') {
tokens.push({ kind: 'less-equals' }); tokens.push({ kind: 'less-equals' });
i++; i++;
} else { } else {
@ -166,7 +167,7 @@ export function tokenize(source: string): Token[] {
break; break;
} }
case '=': { case '=': {
if (source[i + 1] == '=') { if (source[i + 1] === '=') {
tokens.push({ kind: 'equals-equals' }); tokens.push({ kind: 'equals-equals' });
i++; i++;
} else { } else {
@ -175,7 +176,7 @@ export function tokenize(source: string): Token[] {
break; break;
} }
case '!': { case '!': {
if (source[i + 1] == '=') { if (source[i + 1] === '=') {
tokens.push({ kind: 'not-equals' }); tokens.push({ kind: 'not-equals' });
i++; i++;
} else { } else {
@ -183,6 +184,15 @@ export function tokenize(source: string): Token[] {
} }
break; break;
} }
case '.': {
if (source[i + 1] === '.' && source[i + 2] === '.') {
tokens.push({ kind: 'dot-dot-dot' })
i += 2;
} else {
tokens.push({ kind: 'dot' });
}
break;
}
case ':': tokens.push({ kind: 'colon' }); break; case ':': tokens.push({ kind: 'colon' }); break;
case ';': tokens.push({ kind: 'semicolon' }); break; case ';': tokens.push({ kind: 'semicolon' }); break;
case '\\': tokens.push({ kind: 'backslash' }); break; case '\\': tokens.push({ kind: 'backslash' }); break;
@ -190,7 +200,6 @@ export function tokenize(source: string): Token[] {
case '|': tokens.push({ kind: 'pipe' }); break; case '|': tokens.push({ kind: 'pipe' }); break;
case ',': tokens.push({ kind: 'comma' }); break; case ',': tokens.push({ kind: 'comma' }); break;
case '&': tokens.push({ kind: 'ampersand' }); break; case '&': tokens.push({ kind: 'ampersand' }); break;
case '.': tokens.push({ kind: 'dot' }); break;
case '@': tokens.push({ kind: 'at' }); break; case '@': tokens.push({ kind: 'at' }); break;
// Arithmetic // Arithmetic

@ -2,21 +2,29 @@ import { evaluate } from './interpreter'
import type { Env } from './env' import type { Env } from './env'
import { tokenize } from './lexer' import { tokenize } from './lexer'
import { Parser } from './parser' import { Parser } from './parser'
import cgCode from './counter.cg?raw';
import { runApp } from './runtime'; import { runApp } from './runtime';
import { builtins } from './builtins'; import { builtins } from './builtins';
import counterApp from './counter.cg?raw';
import stdlibCode from './stdlib.cg?raw';
import testCode from './test.cg?raw';
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.width = 800; canvas.width = 800;
canvas.height = 600; canvas.height = 600;
document.body.appendChild(canvas); document.body.appendChild(canvas);
const cgCode = stdlibCode + '\n' + testCode;
const tokens = tokenize(cgCode); const tokens = tokenize(cgCode);
const parser = new Parser(tokens); const parser = new Parser(tokens);
const ast = parser.parse(); const ast = parser.parse();
console.log(ast); console.log(ast);
const env: Env = new Map(Object.entries(builtins)); const env: Env = new Map(Object.entries(builtins));
const res = evaluate(ast, env);
console.log(res);
/*
const appRecord = evaluate(ast, env); const appRecord = evaluate(ast, env);
console.log(appRecord); console.log(appRecord);
@ -29,3 +37,4 @@ const update = appRecord.fields.update;
const view = appRecord.fields.view; const view = appRecord.fields.view;
runApp({ init, update, view }, canvas); runApp({ init, update, view }, canvas);
*/

@ -181,14 +181,29 @@ export class Parser {
this.advance(); this.advance();
const elements: Pattern[] = []; const elements: Pattern[] = [];
let first = true; let first = true;
let spreadName: string | null = null;
while (this.current().kind !== 'close-bracket') { while (this.current().kind !== 'close-bracket') {
if (!first) this.expect('comma'); if (!first) this.expect('comma');
first = false; first = false;
// Spread
if (this.current().kind === 'dot-dot-dot') {
this.advance();
const nameToken = this.expect('ident');
spreadName = (nameToken as { value: string }).value;
break;
}
elements.push(this.parsePattern()); elements.push(this.parsePattern());
} }
this.expect('close-bracket'); this.expect('close-bracket');
if (spreadName !== null) {
return { kind: 'list-spread', head: elements, spread: spreadName };
}
return { kind: 'list', elements }; return { kind: 'list', elements };
} }
@ -356,7 +371,7 @@ export class Parser {
if (token.kind === 'open-bracket') { if (token.kind === 'open-bracket') {
this.advance(); this.advance();
const items: AST[] = []; const items: (AST | { spread: AST })[] = [];
let first = true; let first = true;
while (this.current().kind !== 'close-bracket') { while (this.current().kind !== 'close-bracket') {
@ -365,7 +380,14 @@ export class Parser {
} }
first = false; first = false;
items.push(this.parseExpression()); // Spread
if (this.current().kind === 'dot-dot-dot') {
this.advance();
const expr = this.parseExpression();
items.push({ spread: expr })
} else {
items.push(this.parseExpression());
}
} }
this.expect('close-bracket'); this.expect('close-bracket');

@ -0,0 +1,9 @@
map = f list \ list
| [] \ []
| [x, ...xs] \ [f x, ...map f xs];
filter = f list \ list
| [] \ []
| [x, ...xs] \ (f x
| True \ [x, ...filter f xs]
| False \ filter f xs);

@ -0,0 +1,7 @@
add1 = x \ x + 1;
mapped = map add1 [1, 2, 3, 4, 5];
isEven = x \ x % 2 == 0;
filter isEven mapped
Loading…
Cancel
Save