Adding spread operator. starting to build a stdlib. omg
This commit is contained in:
parent
216fe6bd30
commit
9edee10508
7 changed files with 102 additions and 14 deletions
|
|
@ -59,13 +59,14 @@ export type Pattern =
|
|||
| { kind: 'literal', value: number | string }
|
||||
| { kind: 'constructor', name: string, args: Pattern[] }
|
||||
| { kind: 'list', elements: Pattern[] }
|
||||
| { kind: 'list-spread', head: Pattern[], spread: string }
|
||||
| { kind: 'record', fields: { [key: string]: Pattern } }
|
||||
|
||||
// Data Structures
|
||||
|
||||
export type List = {
|
||||
kind: 'list'
|
||||
elements: AST[]
|
||||
elements: (AST | { spread: AST })[]
|
||||
}
|
||||
|
||||
export type Record = {
|
||||
|
|
|
|||
|
|
@ -16,11 +16,25 @@ export function evaluate(ast: AST, env: Env): Value {
|
|||
return val;
|
||||
}
|
||||
|
||||
case 'list':
|
||||
return {
|
||||
kind: 'list',
|
||||
elements: ast.elements.map(el => evaluate(el, env))
|
||||
};
|
||||
case 'list': {
|
||||
const elements: Value[] = [];
|
||||
|
||||
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':
|
||||
const fields: { [key: string]: Value } = {};
|
||||
|
|
@ -226,6 +240,23 @@ function matchPattern(value: Value, pattern: Pattern): Bindings | null {
|
|||
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': {
|
||||
if (value.kind !== 'record') return null;
|
||||
|
||||
|
|
|
|||
19
src/lexer.ts
19
src/lexer.ts
|
|
@ -33,6 +33,7 @@ export type Token =
|
|||
| { kind: 'ampersand' }
|
||||
| { kind: 'underscore' }
|
||||
| { kind: 'dot' }
|
||||
| { kind: 'dot-dot-dot' }
|
||||
| { kind: 'at' }
|
||||
|
||||
// Anithmetic
|
||||
|
|
@ -148,7 +149,7 @@ export function tokenize(source: string): Token[] {
|
|||
|
||||
switch (char) {
|
||||
case '>': {
|
||||
if (source[i + 1] == '=') {
|
||||
if (source[i + 1] === '=') {
|
||||
tokens.push({ kind: 'greater-equals' });
|
||||
i++;
|
||||
} else {
|
||||
|
|
@ -157,7 +158,7 @@ export function tokenize(source: string): Token[] {
|
|||
break;
|
||||
}
|
||||
case '<': {
|
||||
if (source[i + 1] == '=') {
|
||||
if (source[i + 1] === '=') {
|
||||
tokens.push({ kind: 'less-equals' });
|
||||
i++;
|
||||
} else {
|
||||
|
|
@ -166,7 +167,7 @@ export function tokenize(source: string): Token[] {
|
|||
break;
|
||||
}
|
||||
case '=': {
|
||||
if (source[i + 1] == '=') {
|
||||
if (source[i + 1] === '=') {
|
||||
tokens.push({ kind: 'equals-equals' });
|
||||
i++;
|
||||
} else {
|
||||
|
|
@ -175,7 +176,7 @@ export function tokenize(source: string): Token[] {
|
|||
break;
|
||||
}
|
||||
case '!': {
|
||||
if (source[i + 1] == '=') {
|
||||
if (source[i + 1] === '=') {
|
||||
tokens.push({ kind: 'not-equals' });
|
||||
i++;
|
||||
} else {
|
||||
|
|
@ -183,6 +184,15 @@ export function tokenize(source: string): Token[] {
|
|||
}
|
||||
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: 'semicolon' }); 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: 'comma' }); break;
|
||||
case '&': tokens.push({ kind: 'ampersand' }); break;
|
||||
case '.': tokens.push({ kind: 'dot' }); break;
|
||||
case '@': tokens.push({ kind: 'at' }); break;
|
||||
|
||||
// Arithmetic
|
||||
|
|
|
|||
11
src/main.ts
11
src/main.ts
|
|
@ -2,21 +2,29 @@ import { evaluate } from './interpreter'
|
|||
import type { Env } from './env'
|
||||
import { tokenize } from './lexer'
|
||||
import { Parser } from './parser'
|
||||
import cgCode from './counter.cg?raw';
|
||||
import { runApp } from './runtime';
|
||||
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');
|
||||
canvas.width = 800;
|
||||
canvas.height = 600;
|
||||
document.body.appendChild(canvas);
|
||||
|
||||
const cgCode = stdlibCode + '\n' + testCode;
|
||||
|
||||
const tokens = tokenize(cgCode);
|
||||
const parser = new Parser(tokens);
|
||||
const ast = parser.parse();
|
||||
console.log(ast);
|
||||
|
||||
const env: Env = new Map(Object.entries(builtins));
|
||||
const res = evaluate(ast, env);
|
||||
console.log(res);
|
||||
/*
|
||||
const appRecord = evaluate(ast, env);
|
||||
|
||||
console.log(appRecord);
|
||||
|
|
@ -29,3 +37,4 @@ const update = appRecord.fields.update;
|
|||
const view = appRecord.fields.view;
|
||||
|
||||
runApp({ init, update, view }, canvas);
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -181,14 +181,29 @@ export class Parser {
|
|||
this.advance();
|
||||
const elements: Pattern[] = [];
|
||||
let first = true;
|
||||
let spreadName: string | null = null;
|
||||
|
||||
while (this.current().kind !== 'close-bracket') {
|
||||
if (!first) this.expect('comma');
|
||||
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());
|
||||
}
|
||||
|
||||
this.expect('close-bracket');
|
||||
|
||||
if (spreadName !== null) {
|
||||
return { kind: 'list-spread', head: elements, spread: spreadName };
|
||||
}
|
||||
|
||||
return { kind: 'list', elements };
|
||||
}
|
||||
|
||||
|
|
@ -356,7 +371,7 @@ export class Parser {
|
|||
if (token.kind === 'open-bracket') {
|
||||
this.advance();
|
||||
|
||||
const items: AST[] = [];
|
||||
const items: (AST | { spread: AST })[] = [];
|
||||
let first = true;
|
||||
|
||||
while (this.current().kind !== 'close-bracket') {
|
||||
|
|
@ -365,7 +380,14 @@ export class Parser {
|
|||
}
|
||||
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');
|
||||
|
|
|
|||
9
src/stdlib.cg
Normal file
9
src/stdlib.cg
Normal file
|
|
@ -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);
|
||||
7
src/test.cg
Normal file
7
src/test.cg
Normal file
|
|
@ -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…
Add table
Add a link
Reference in a new issue