mondo: proper lambda functions with local scope

This commit is contained in:
Felix Roos
2025-03-19 18:16:37 +01:00
parent 642ddcdb59
commit e04f250adb
2 changed files with 29 additions and 19 deletions
+24 -18
View File
@@ -214,11 +214,14 @@ export class MondoParser {
if (pipeIndex === -1) break;
// pipe up front => lambda
if (pipeIndex === 0) {
// . as lambda: (.fast 2) = x=>x.fast(2)
// TODO: this doesn't work for (.fast 2 .speed 2)
// probably needs proper ast representation of lambda
children[pipeIndex] = { type: 'plain', value: '.' };
continue;
// . as lambda: (.fast 2) = (lambda (_) (fast _ 2))
const args = [{ type: 'plain', value: '_' }];
const body = this.desugar([args[0], ...children]);
return [
{ type: 'plain', value: 'lambda' },
{ type: 'list', children: args },
{ type: 'list', children: body },
];
}
const rightSide = children.slice(pipeIndex + 2);
const right = children[pipeIndex + 1];
@@ -343,7 +346,7 @@ export class MondoRunner {
errorhead(ast) {
return `[mondo ${ast.loc?.join(':') || ''}]`;
}
call(ast) {
call(ast, scope = []) {
// for a node to be callable, it needs to be a list
this.assert(ast.type === 'list', `${this.errorhead(ast)} function call: expected list, got ${ast.type}`);
// the first element is expected to be the function name
@@ -354,10 +357,22 @@ export class MondoRunner {
`${this.errorhead(first)} expected function name, got ${first.type}${name ? ` "${name}"` : ''}.`,
);
if (name === 'lambda') {
const [_, args, body] = ast.children;
const argNames = args.children.map((child) => child.value);
// console.log('lambda', argNames, body.children);
return (x) => {
scope = {
[argNames[0]]: x, // TODO: merge scope... + support multiple args
};
return this.call(body, scope);
};
}
// process args
const args = ast.children.slice(1).map((arg) => {
if (arg.type === 'list') {
return this.call(arg);
return this.call(arg, scope);
}
if (!this.lib.leaf) {
throw new Error(`no handler for leaft nodes! add leaf to your lib`);
@@ -368,21 +383,12 @@ export class MondoRunner {
} else if (['quotes_double', 'quotes_single'].includes(arg.type)) {
arg.value = arg.value.slice(1, -1);
}
return this.lib.leaf(arg);
return this.lib.leaf(arg, scope);
});
if (name === '.') {
// lambda : (.fast 2) = x=>fast(2, x)
const second = ast.children[1];
const callee = second.value;
const innerFn = this.lib[callee];
this.assert(innerFn, `${this.errorhead(second)} unknown function name "${callee}"`);
return (pat) => this.libcall(innerFn, [pat, ...args.slice(1)], callee);
}
// look up function in lib
const fn = this.lib[name];
this.assert(fn, `${this.errorhead(first)} unknown function name "${name}"`);
return this.libcall(fn, args, name);
return this.libcall(fn, args, name, scope);
}
}
+5 -1
View File
@@ -4,8 +4,12 @@ import { MondoRunner } from '../mondo/mondo.mjs';
let runner = new MondoRunner(strudelScope);
strudelScope.leaf = (token) => {
strudelScope.leaf = (token, scope) => {
let { value } = token;
// local scope
if (token.type === 'plain' && scope[value]) {
return reify(scope[value]); // -> local scope has no location
}
const [from, to] = token.loc;
if (token.type === 'plain' && strudelScope[value]) {
// what if we want a string that happens to also be a variable name?