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: '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;
|
||||||
|
|
||||||
|
|
|
||||||
19
src/lexer.ts
19
src/lexer.ts
|
|
@ -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
|
||||||
|
|
|
||||||
11
src/main.ts
11
src/main.ts
|
|
@ -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,8 +380,15 @@ export class Parser {
|
||||||
}
|
}
|
||||||
first = false;
|
first = false;
|
||||||
|
|
||||||
|
// Spread
|
||||||
|
if (this.current().kind === 'dot-dot-dot') {
|
||||||
|
this.advance();
|
||||||
|
const expr = this.parseExpression();
|
||||||
|
items.push({ spread: expr })
|
||||||
|
} else {
|
||||||
items.push(this.parseExpression());
|
items.push(this.parseExpression());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.expect('close-bracket');
|
this.expect('close-bracket');
|
||||||
return { kind: 'list', elements: items };
|
return { kind: 'list', elements: items };
|
||||||
|
|
|
||||||
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