mondo: refactor

- rename MondoRunner.call => .evaluate
- can now evaluate leafs
- remove .( because it's confusing and half baked
- support (.) as id function
This commit is contained in:
Felix Roos
2025-03-26 03:22:23 +01:00
parent 2938bfd88f
commit 7716fdb98e
3 changed files with 37 additions and 90 deletions
+4 -17
View File
@@ -9,7 +9,7 @@ an experimental parser for an *uzulang*, a custom dsl for patterns that can stan
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')
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:
@@ -52,16 +52,6 @@ n("0 1 2").add(n("<0 -4>")).scale("C:minor")
---
```plaintext
n[0 1 2].(add<0 -4>).scale"C minor"
```
```js
n("0 1 2".add("<0 -4>")).scale("C:minor")
```
---
```plaintext
n[0 1 2].scale"C minor"
.sometimes (12.note.add)
@@ -75,11 +65,11 @@ n("0 1 2").scale("C:minor")
---
```plaintext
note g2*8.dec <sine saw>/2.(range .1 .4)
note g2*8.dec <sine saw .range .01 .4>/2
```
```js
note("g2*8").dec(cat(sine, saw).slow(2).range(.1, .4))
note("g2*8").dec(cat(sine, saw).range(.1, .4).slow(2))
```
---
@@ -95,7 +85,7 @@ n("<0 1 2 3 4>*4").scale("C:minor").jux(cat(rev,press))
---
mondo`
sound [bd sd.(every 3 (.fast 4))].jux <rev (.iter 4)>
sound [bd (sd.every 3 (.fast 4))].jux <rev (.iter 4)>
`
// og "Alternate Timelines for TidalCycles" example:
// jux <(rev) (iter 4)> $ sound [bd (every 3 (fast 4) [sn])]
@@ -129,9 +119,6 @@ comments
variables: note g2*8.dec sine
sine.(range 0 4)/2 doesnt work
sine/2.(range 0 4) works
n (irand 8. ribbon 0 2) .scale"C minor" => lags because no whole
### reference
+28 -69
View File
@@ -176,31 +176,9 @@ export class MondoParser {
}
get_lambda(args, children) {
// (.fast 2) = (lambda (_) (fast _ 2))
const body = this.desugar(children);
return [
{ type: 'plain', value: 'lambda' },
{ type: 'list', children: args },
{ type: 'list', children: body },
];
}
// inserts target into lambda body
desugar_lambda(lambda, target) {
// lambda looks like return value from this.get_lambda
const [_, args, body] = lambda;
if (args.length > 1) {
throw new Error('desugar_lambda with >1 arg is unsupported rn');
}
const argNames = args.children.map((child) => child.value);
let desugar = (child) => {
if (child.type === 'plain' && child.value === argNames[0]) {
return target;
}
if (child.type === 'list') {
child.children = child.children.map(desugar);
}
return child;
};
return desugar(body);
children = this.desugar(children);
const body = children.length === 1 ? children[0] : { type: 'list', children };
return [{ type: 'plain', value: 'lambda' }, { type: 'list', children: args }, body];
}
// returns location range of given ast (even if desugared)
get_range(ast, range = [Infinity, 0]) {
@@ -228,40 +206,23 @@ export class MondoParser {
let chunks = this.split_children(children, 'pipe');
while (chunks.length > 1) {
let [left, right, ...rest] = chunks;
if (right.length && right[0].type === 'list') {
// x.(y) => not allowed anymore for now..
const snip = this.get_code_snippet(right[0]);
throw new Error(`${this.errorhead(right[0])} cannot apply list: expected "(${snip})" to be a word`);
}
if (!left.length) {
const arg = { type: 'plain', value: '_' };
return this.get_lambda([arg], [arg, ...children]);
}
if (right.length && right[0].type === 'list') {
// s jazz hh.(.fast 2) => s jazz (hh.fast 2) = s jazz (fast 2 hh)
const target = left[left.length - 1]; // hh
if (right[0].children[0].value !== 'lambda') {
const snip = this.get_code_snippet(right[0]);
throw new Error(`${this.errorhead(right[0])} no lambda: expected "${snip}" to start with "."`);
}
const call = this.desugar_lambda(right[0].children, target);
chunks = [[...left.slice(0, -1), call, ...right.slice(1)], ...rest]; // jazz (fast 2 hh)
} else {
//s jazz hh.fast 2 => (fast 2 (s jazz hh))
// const call = left.length > 1 ? { type: 'list', children: next(left) } : left[0];
const call = left.length > 1 ? { type: 'list', children: left } : left[0];
chunks = [[...right, call], ...rest];
}
// s jazz hh.fast 2 => (fast 2 (s jazz hh))
const call = left.length > 1 ? { type: 'list', children: left } : left[0];
chunks = [[...right, call], ...rest];
}
// return next(chunks[0]);
return chunks[0];
}
/* desugar_dollars(children) {
let chunks = this.split_children(children, 'dollar');
while (chunks.length > 1) {
let [left, right, ...rest] = chunks;
//fast 2 $ s jazz hh => (fast 2 (s jazz hh))
const call = right.length > 1 ? { type: 'list', children: right } : right[0];
chunks = [[...left, call], ...rest];
}
return chunks[0];
} */
parse_pair(open_type, close_type) {
this.consume(open_type);
const children = [];
@@ -365,11 +326,20 @@ export class MondoRunner {
run(code, offset = 0) {
const ast = this.parser.parse(code, offset);
console.log(printAst(ast));
return this.call(ast);
return this.evaluate(ast);
}
call(ast, scope = []) {
// for a node to be callable, it needs to be a list
this.assert(ast.type === 'list', `${this.parser.errorhead(ast)} function call: expected list, got ${ast.type}`);
evaluate(ast, scope = []) {
if (ast.type !== 'list') {
// is leaf
if (ast.type === 'number') {
ast.value = Number(ast.value);
} else if (['quotes_double', 'quotes_single'].includes(ast.type)) {
arg.value = arg.value.slice(1, -1);
}
return this.lib.leaf(ast, scope);
}
// is list
// the first element is expected to be the function name
const first = ast.children[0];
const name = first.value;
@@ -385,23 +355,12 @@ export class MondoRunner {
scope = {
[argNames[0]]: x, // TODO: merge scope... + support multiple args
};
return this.call(body, scope);
return this.evaluate(body, scope);
};
}
// process args
const args = ast.children.slice(1).map((arg) => {
if (arg.type === 'list') {
return this.call(arg, scope);
}
if (arg.type === 'number') {
arg.value = Number(arg.value);
} else if (['quotes_double', 'quotes_single'].includes(arg.type)) {
arg.value = arg.value.slice(1, -1);
}
return this.lib.leaf(arg, scope);
});
// evaluate args
const args = ast.children.slice(1).map((arg) => this.evaluate(arg, scope));
return this.lib.call(name, args, scope);
}
}
+5 -4
View File
@@ -88,7 +88,7 @@ describe('mondo sugar', () => {
it('should desugar . within , within []', () =>
expect(desguar('[bd cp . fast 2, x]')).toEqual('(stack (fast 2 (square bd cp)) x)'));
it('should desugar .(.', () => expect(desguar('[jazz hh.(.fast 2)]')).toEqual('(square jazz (fast 2 hh))'));
// it('should desugar .(.', () => expect(desguar('[jazz hh.(.fast 2)]')).toEqual('(square jazz (fast 2 hh))'));
it('should desugar , |', () => expect(desguar('[bd, hh | oh]')).toEqual('(stack bd (or hh oh))'));
it('should desugar , | of []', () =>
@@ -114,15 +114,16 @@ describe('mondo sugar', () => {
it('should desugar x $ y . z', () => expect(desguar('x $ y . z')).toEqual('(z (x y))')); */
it('should desugar README example', () =>
expect(desguar('s [bd hh*2 cp.(.crush 4) <mt ht lt>] . speed .8')).toEqual(
expect(desguar('s [bd hh*2 (cp.crush 4) <mt ht lt>] . speed .8')).toEqual(
'(speed .8 (s (square bd (* 2 hh) (crush 4 cp) (angle mt ht lt))))',
));
it('should desugar (.)', () => expect(desguar('(.)')).toEqual('(lambda (_) _)'));
it('should desugar lambda', () => expect(desguar('(.fast 2)')).toEqual('(lambda (_) (fast 2 _))'));
it('should desugar lambda with pipe', () =>
expect(desguar('(.fast 2 .room 1)')).toEqual('(lambda (_) (room 1 (fast 2 _)))'));
const lambda = parser.parse('(lambda (_) (fast 2 _))');
/* const lambda = parser.parse('(lambda (_) (fast 2 _))');
const target = { type: 'plain', value: 'xyz' };
it('should desugar_lambda', () =>
expect(printAst(parser.desugar_lambda(lambda.children, target))).toEqual('(fast 2 xyz)'));
expect(printAst(parser.desugar_lambda(lambda.children, target))).toEqual('(fast 2 xyz)')); */
});