mondo: remember pair locations

+ allow passing a scope to MondoRunner.run
+ make def a side effect
+ proper lambda with multiple args + closure
This commit is contained in:
Felix Roos 2025-04-01 21:49:28 +02:00
parent f2372e7a41
commit 94a3a60e49
No known key found for this signature in database
3 changed files with 38 additions and 31 deletions

View file

@ -27,7 +27,7 @@ function evaluator(node) {
// children in a list will already be evaluated // children in a list will already be evaluated
// the first child is expected to be a function // the first child is expected to be a function
if (typeof fn !== 'function') { if (typeof fn !== 'function') {
throw new Error(`"${fn}" is not a function ${typeof fn}`); throw new Error(`"${fn}" is not a function`);
} }
return fn(...args); return fn(...args);
} }

View file

@ -24,7 +24,7 @@ export class MondoParser {
pipe: /^\./, pipe: /^\./,
stack: /^[,$]/, stack: /^[,$]/,
or: /^[|]/, or: /^[|]/,
plain: /^[a-zA-Z0-9-~_^#]+/, plain: /^[a-zA-Z0-9-~_^#+-]+/,
}; };
// matches next token // matches next token
next_token(code, offset = 0) { next_token(code, offset = 0) {
@ -88,6 +88,8 @@ export class MondoParser {
parse_expr() { parse_expr() {
if (!this.tokens[0]) { if (!this.tokens[0]) {
throw new Error(`unexpected end of file`); throw new Error(`unexpected end of file`);
// TODO: could we allow that? like (((((((( s bd
// return { type: 'list', children: [] };
} }
let next = this.tokens[0]?.type; let next = this.tokens[0]?.type;
if (next === 'open_list') { if (next === 'open_list') {
@ -219,13 +221,17 @@ export class MondoParser {
return chunks[0]; return chunks[0];
} }
parse_pair(open_type, close_type) { parse_pair(open_type, close_type) {
const begin = this.tokens[0].loc?.[0];
this.consume(open_type); this.consume(open_type);
const children = []; const children = [];
while (this.tokens[0]?.type !== close_type) { while (this.tokens[0]?.type !== close_type) {
children.push(this.parse_expr()); children.push(this.parse_expr());
} }
const end = this.tokens[0].loc?.[1];
this.consume(close_type); this.consume(close_type);
return children; const node = { type: 'list', children };
begin !== undefined && (node.loc = [begin, end]);
return node;
} }
desugar(children, type) { desugar(children, type) {
// if type is given, the first element is expected to contain it as plain value // if type is given, the first element is expected to contain it as plain value
@ -247,27 +253,27 @@ export class MondoParser {
return children; return children;
} }
parse_list() { parse_list() {
let children = this.parse_pair('open_list', 'close_list'); let node = this.parse_pair('open_list', 'close_list');
children = this.desugar(children); node.children = this.desugar(node.children);
return { type: 'list', children }; return node;
} }
parse_angle() { parse_angle() {
let children = this.parse_pair('open_angle', 'close_angle'); let node = this.parse_pair('open_angle', 'close_angle');
children = [{ type: 'plain', value: 'angle' }, ...children]; node.children.unshift({ type: 'plain', value: 'angle' });
children = this.desugar(children, 'angle'); node.children = this.desugar(node.children, 'angle');
return { type: 'list', children }; return node;
} }
parse_square() { parse_square() {
let children = this.parse_pair('open_square', 'close_square'); let node = this.parse_pair('open_square', 'close_square');
children = [{ type: 'plain', value: 'square' }, ...children]; node.children.unshift({ type: 'plain', value: 'square' });
children = this.desugar(children, 'square'); node.children = this.desugar(node.children, 'square');
return { type: 'list', children }; return node;
} }
parse_curly() { parse_curly() {
let children = this.parse_pair('open_curly', 'close_curly'); let node = this.parse_pair('open_curly', 'close_curly');
children = [{ type: 'plain', value: 'curly' }, ...children]; node.children.unshift({ type: 'plain', value: 'curly' });
children = this.desugar(children, 'curly'); node.children = this.desugar(node.children, 'curly');
return { type: 'list', children }; return node;
} }
consume(type) { consume(type) {
// shift removes first element and returns it // shift removes first element and returns it
@ -317,10 +323,10 @@ export class MondoRunner {
throw new Error(error); throw new Error(error);
} }
} }
run(code, offset = 0) { run(code, scope, offset = 0) {
const ast = this.parser.parse(code, offset); const ast = this.parser.parse(code, offset);
console.log(printAst(ast)); console.log(printAst(ast));
return this.evaluate(ast); return this.evaluate(ast, scope);
} }
evaluate_def(ast, scope) { evaluate_def(ast, scope) {
// (def name body) // (def name body)
@ -330,18 +336,19 @@ export class MondoRunner {
const name = ast.children[1].value; const name = ast.children[1].value;
const body = this.evaluate(ast.children[2], scope); const body = this.evaluate(ast.children[2], scope);
scope[name] = body; scope[name] = body;
return this.evaluator(ast, scope); // def with fall through
} }
evaluate_lambda(ast, scope) { evaluate_lambda(ast, scope) {
// (fn (_) (ply 2 _) // (fn (_) (ply 2 _)
// ^args ^ body // ^args ^ body
const [_, args, body] = ast.children; const [_, formalArgs, body] = ast.children;
const argNames = args.children.map((child) => child.value); return (...args) => {
return (x) => { const params = Object.fromEntries(formalArgs.children.map((arg, i) => [arg.value, args[i]]));
scope = { const closure = {
[argNames[0]]: x, // TODO: merge scope... + support multiple args ...scope,
...params,
}; };
return this.evaluate(body, scope); return this.evaluate(body, closure);
}; };
} }
evaluate_list(ast, scope) { evaluate_list(ast, scope) {
@ -362,12 +369,12 @@ export class MondoRunner {
if (ast.type !== 'list') { if (ast.type !== 'list') {
return this.evaluate_leaf(ast, scope); return this.evaluate_leaf(ast, scope);
} }
if (ast.children[0]?.value === 'def') {
return this.evaluate_def(ast, scope);
}
if (ast.children[0]?.value === 'fn') { if (ast.children[0]?.value === 'fn') {
return this.evaluate_lambda(ast, scope); return this.evaluate_lambda(ast, scope);
} }
if (ast.children[0]?.value === 'def') {
this.evaluate_def(ast, scope);
}
return this.evaluate_list(ast, scope); return this.evaluate_list(ast, scope);
} }
} }

View file

@ -93,7 +93,7 @@ export function mondo(code, offset = 0) {
if (Array.isArray(code)) { if (Array.isArray(code)) {
code = code.join(''); code = code.join('');
} }
const pat = runner.run(code, offset); const pat = runner.run(code, undefined, offset);
return pat.markcss('color: var(--caret,--foreground);text-decoration:underline'); return pat.markcss('color: var(--caret,--foreground);text-decoration:underline');
} }