mondo: improve mondo package api + update readme
This commit is contained in:
parent
2e017e46f9
commit
c9cafa37fd
4 changed files with 89 additions and 60 deletions
|
|
@ -5,23 +5,32 @@ an experimental parser for an *uzulang*, a custom dsl for patterns that can stan
|
|||
- [uzulang I](https://garten.salat.dev/uzu/uzulang1.html)
|
||||
- [uzulang II](https://garten.salat.dev/uzu/uzulang2.html)
|
||||
|
||||
## Example Usage
|
||||
|
||||
```js
|
||||
import { MondoRunner } from 'uzu'
|
||||
|
||||
const runner = MondoRunner({ seq, cat, s, crush, speed, '*': fast });
|
||||
const pat = runner.run('s [bd hh*2 (cp.crush 4) <mt ht lt>] . speed .8')
|
||||
```
|
||||
|
||||
the above code will create the following call structure:
|
||||
|
||||
```lisp
|
||||
(speed
|
||||
(s
|
||||
(seq bd
|
||||
(* hh 2)
|
||||
(crush cp 4)
|
||||
(cat mt ht lt)
|
||||
)
|
||||
) .8
|
||||
)
|
||||
import { MondoRunner } from 'mondo'
|
||||
// define our library of functions and variables
|
||||
let lib = {
|
||||
add: (a, b) => a + b,
|
||||
mul: (a, b) => a * b,
|
||||
PI: Math.PI,
|
||||
};
|
||||
// this function will evaluate nodes in the syntax tree
|
||||
function evaluator(node) {
|
||||
// check if node is a leaf node (!= list)
|
||||
if (node.type !== 'list') {
|
||||
// check lib if we find a match in the lib, otherwise return value
|
||||
return lib[node.value] ?? node.value;
|
||||
}
|
||||
// now it can only be a list..
|
||||
const [fn, ...args] = node.children;
|
||||
// children in a list will already be evaluated
|
||||
// the first child is expected to be a function
|
||||
if (typeof fn !== 'function') {
|
||||
throw new Error(`"${fn}" is not a function ${typeof fn}`);
|
||||
}
|
||||
return fn(...args);
|
||||
}
|
||||
const runner = new MondoRunner(evaluator);
|
||||
const pat = runner.run('add 1 (mul 2 PI)') // 7.283185307179586
|
||||
```
|
||||
|
|
|
|||
|
|
@ -306,11 +306,10 @@ export function printAst(ast, compact = false, lvl = 0) {
|
|||
|
||||
// lisp runner
|
||||
export class MondoRunner {
|
||||
constructor(lib) {
|
||||
constructor(evaluator) {
|
||||
this.parser = new MondoParser();
|
||||
this.lib = lib;
|
||||
this.assert(!!this.lib.leaf, `no handler for leaft nodes! add "leaf" to your lib`);
|
||||
this.assert(!!this.lib.call, `no handler for call nodes! add "call" to your lib`);
|
||||
this.evaluator = evaluator;
|
||||
this.assert(typeof evaluator === 'function', `expected an evaluator function to be passed to new MondoRunner`);
|
||||
}
|
||||
// a helper to check conditions and throw if they are not met
|
||||
assert(condition, error) {
|
||||
|
|
@ -331,15 +330,10 @@ export class MondoRunner {
|
|||
} else if (['quotes_double', 'quotes_single'].includes(ast.type)) {
|
||||
ast.value = ast.value.slice(1, -1);
|
||||
}
|
||||
return this.lib.leaf(ast, scope);
|
||||
return this.evaluator(ast, scope);
|
||||
}
|
||||
|
||||
// is list
|
||||
if (!ast.children.length) {
|
||||
throw new Error(`empty list`);
|
||||
}
|
||||
|
||||
if (ast.children[0].value === 'lambda') {
|
||||
if (ast.children[0]?.value === 'lambda') {
|
||||
const [_, args, body] = ast.children;
|
||||
const argNames = args.children.map((child) => child.value);
|
||||
return (x) => {
|
||||
|
|
@ -349,10 +343,8 @@ export class MondoRunner {
|
|||
return this.evaluate(body, scope);
|
||||
};
|
||||
}
|
||||
|
||||
const args = ast.children.map((arg) => this.evaluate(arg, scope));
|
||||
// we could short circuit arg[0] if its plain...
|
||||
// evaluate args
|
||||
return this.lib.call(args[0], args.slice(1), scope);
|
||||
// evaluate all children before evaluating list
|
||||
ast.children = ast.children.map((arg) => this.evaluate(arg, scope));
|
||||
return this.evaluator(ast, scope);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||
*/
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { MondoParser, printAst } from '../mondo.mjs';
|
||||
import { MondoParser, printAst, MondoRunner } from '../mondo.mjs';
|
||||
|
||||
const parser = new MondoParser();
|
||||
const p = (code) => parser.parse(code, -1);
|
||||
|
|
@ -128,3 +128,28 @@ describe('mondo sugar', () => {
|
|||
it('should desugar_lambda', () =>
|
||||
expect(printAst(parser.desugar_lambda(lambda.children, target))).toEqual('(fast 2 xyz)')); */
|
||||
});
|
||||
|
||||
describe('mondo arithmetic', () => {
|
||||
let lib = {
|
||||
add: (a, b) => a + b,
|
||||
mul: (a, b) => a * b,
|
||||
PI: Math.PI,
|
||||
};
|
||||
function evaluator(node) {
|
||||
// check if node is a leaf node (!= list)
|
||||
if (node.type !== 'list') {
|
||||
// check lib if we find a match in the lib, otherwise return value
|
||||
return lib[node.value] ?? node.value;
|
||||
}
|
||||
// now it can only be a list..
|
||||
const [fn, ...args] = node.children;
|
||||
// children in a list will already be evaluated
|
||||
// the first child is expected to be a function
|
||||
if (typeof fn !== 'function') {
|
||||
throw new Error(`"${fn}" is not a function ${typeof fn}`);
|
||||
}
|
||||
return fn(...args);
|
||||
}
|
||||
const runner = new MondoRunner(evaluator);
|
||||
it('should desugar (.)', () => expect(runner.run('add 1 (mul 2 PI)').toFixed(2)).toEqual('7.28'));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -43,8 +43,12 @@ lib['..'] = range;
|
|||
lib['or'] = (...children) => chooseIn(...children); // always has structure but is cyclewise.. e.g. "s oh*8.dec[.04 | .5]"
|
||||
//lib['or'] = (...children) => chooseOut(...children); // "s oh*8.dec[.04 | .5]" is better but "dec[.04 | .5].s oh*8" has no struct
|
||||
|
||||
let runner = new MondoRunner({
|
||||
call(name, args, scope) {
|
||||
function evaluator(node, scope) {
|
||||
const { type } = node;
|
||||
// node is list
|
||||
if (type === 'list') {
|
||||
const { children } = node;
|
||||
const [name, ...args] = children;
|
||||
// name is expected to be a pattern of functions!
|
||||
const first = name.firstCycle(true)[0];
|
||||
if (typeof first?.value !== 'function') {
|
||||
|
|
@ -58,30 +62,29 @@ let runner = new MondoRunner({
|
|||
return fn(...args);
|
||||
})
|
||||
.innerJoin();
|
||||
},
|
||||
leaf(token, scope) {
|
||||
let { value, type } = token;
|
||||
// local scope
|
||||
if (type === 'plain' && scope[value]) {
|
||||
return reify(scope[value]); // -> local scope has no location
|
||||
}
|
||||
const variable = lib[value] ?? strudelScope[value];
|
||||
let pat;
|
||||
if (type === 'plain' && typeof variable !== 'undefined') {
|
||||
// problem: collisions when we want a string that happens to also be a variable name
|
||||
// example: "s sine" -> sine is also a variable
|
||||
pat = reify(variable);
|
||||
} else {
|
||||
pat = reify(value);
|
||||
}
|
||||
}
|
||||
// node is leaf
|
||||
let { value } = node;
|
||||
if (type === 'plain' && scope[value]) {
|
||||
return reify(scope[value]); // -> local scope has no location
|
||||
}
|
||||
const variable = lib[value] ?? strudelScope[value];
|
||||
let pat;
|
||||
if (type === 'plain' && typeof variable !== 'undefined') {
|
||||
// problem: collisions when we want a string that happens to also be a variable name
|
||||
// example: "s sine" -> sine is also a variable
|
||||
pat = reify(variable);
|
||||
} else {
|
||||
pat = reify(value);
|
||||
}
|
||||
if (node.loc) {
|
||||
pat = pat.withLoc(node.loc[0], node.loc[1]);
|
||||
}
|
||||
pat.foo = true;
|
||||
return pat;
|
||||
}
|
||||
|
||||
if (token.loc) {
|
||||
pat = pat.withLoc(token.loc[0], token.loc[1]);
|
||||
}
|
||||
pat.foo = true;
|
||||
return pat;
|
||||
},
|
||||
});
|
||||
let runner = new MondoRunner(evaluator);
|
||||
|
||||
export function mondo(code, offset = 0) {
|
||||
if (Array.isArray(code)) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue