diff --git a/packages/mondo/mondo.mjs b/packages/mondo/mondo.mjs
index 4ab8a793c..19ed27e95 100644
--- a/packages/mondo/mondo.mjs
+++ b/packages/mondo/mondo.mjs
@@ -12,10 +12,10 @@ export class MondoParser {
quotes_single: /^'(.*?)'/,
open_list: /^\(/,
close_list: /^\)/,
- open_cat: /^, // todo: rename angle
- close_cat: /^>/,
- open_seq: /^\[/, // todo: rename square
- close_seq: /^\]/,
+ open_angle: /^,
+ close_angle: /^>/,
+ open_square: /^\[/,
+ close_square: /^\]/,
open_curly: /^\{/,
close_curly: /^\}/,
number: /^-?[0-9]*\.?[0-9]+/, // before pipe!
@@ -89,11 +89,11 @@ export class MondoParser {
if (next === 'open_list') {
return this.parse_list();
}
- if (next === 'open_cat') {
- return this.parse_cat();
+ if (next === 'open_angle') {
+ return this.parse_angle();
}
- if (next === 'open_seq') {
- return this.parse_seq();
+ if (next === 'open_square') {
+ return this.parse_square();
}
if (next === 'open_curly') {
return this.parse_curly();
@@ -126,7 +126,7 @@ export class MondoParser {
return chunks;
}
desugar_stack(children, sequence_type) {
- // children is expected to contain seq or cat as first item
+ // children is expected to contain square or angle as first item
const chunks = this.split_children(children, 'stack', sequence_type);
if (!chunks.length) {
return this.desugar_children(children);
@@ -140,7 +140,7 @@ export class MondoParser {
// chunks of multiple args
if (sequence_type) {
// if given, each chunk needs to be prefixed
- // [a b, c d] => (stack (seq a b) (seq c d))
+ // [a b, c d] => (stack (square a b) (square c d))
chunk = [{ type: 'plain', value: sequence_type }, ...chunk];
}
chunk = this.desugar_children(chunk);
@@ -249,16 +249,16 @@ export class MondoParser {
children = this.desugar(children);
return { type: 'list', children };
}
- parse_cat() {
- let children = this.parse_pair('open_cat', 'close_cat');
- children = [{ type: 'plain', value: 'cat' }, ...children];
- children = this.desugar(children, 'cat');
+ parse_angle() {
+ let children = this.parse_pair('open_angle', 'close_angle');
+ children = [{ type: 'plain', value: 'angle' }, ...children];
+ children = this.desugar(children, 'angle');
return { type: 'list', children };
}
- parse_seq() {
- let children = this.parse_pair('open_seq', 'close_seq');
- children = [{ type: 'plain', value: 'seq' }, ...children];
- children = this.desugar(children, 'seq');
+ parse_square() {
+ let children = this.parse_pair('open_square', 'close_square');
+ children = [{ type: 'plain', value: 'square' }, ...children];
+ children = this.desugar(children, 'square');
return { type: 'list', children };
}
parse_curly() {
@@ -307,6 +307,8 @@ export class MondoRunner {
constructor(lib) {
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`);
}
// a helper to check conditions and throw if they are not met
assert(condition, error) {
@@ -316,16 +318,9 @@ export class MondoRunner {
}
run(code, offset = 0) {
const ast = this.parser.parse(code, offset);
- // console.log(printAst(ast));
+ console.log(printAst(ast));
return this.call(ast);
}
- // todo: always use lib.call?
- libcall(fn, args, name) {
- if (this.lib.call) {
- return this.lib.call(fn, args, name);
- }
- return fn(...args);
- }
errorhead(ast) {
return `[mondo ${ast.loc?.join(':') || ''}]`;
}
@@ -357,9 +352,6 @@ export class MondoRunner {
if (arg.type === 'list') {
return this.call(arg, scope);
}
- if (!this.lib.leaf) {
- throw new Error(`no handler for leaft nodes! add leaf to your lib`);
- }
if (arg.type === 'number') {
arg.value = Number(arg.value);
@@ -370,8 +362,6 @@ export class MondoRunner {
});
// 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, scope);
+ return this.lib.call(name, args, scope);
}
}
diff --git a/packages/mondo/test/mondo.test.mjs b/packages/mondo/test/mondo.test.mjs
index aadbafbeb..1b9a41e3d 100644
--- a/packages/mondo/test/mondo.test.mjs
+++ b/packages/mondo/test/mondo.test.mjs
@@ -12,15 +12,15 @@ const p = (code) => parser.parse(code, -1);
describe('mondo tokenizer', () => {
const parser = new MondoParser();
- it('should tokenize with locations', () =>
+ it('should tokenize with loangleions', () =>
expect(
parser
.tokenize('(one two three)')
.map((t) => t.value + '=' + t.loc.join('-'))
.join(' '),
).toEqual('(=0-1 one=1-4 two=5-8 three=9-14 )=14-15'));
- // it('should parse with locations', () => expect(parser.parse('(one two three)')).toEqual());
- it('should get locations', () =>
+ // it('should parse with loangleions', () => expect(parser.parse('(one two three)')).toEqual());
+ it('should get loangleions', () =>
expect(parser.get_locations('s bd rim')).toEqual([
[2, 4],
[5, 8],
@@ -73,32 +73,34 @@ let desguar = (a) => {
};
describe('mondo sugar', () => {
- it('should desugar []', () => expect(desguar('[a b c]')).toEqual('(seq a b c)'));
- it('should desugar [] nested', () => expect(desguar('[a [b c] d]')).toEqual('(seq a (seq b c) d)'));
- it('should desugar <>', () => expect(desguar('')).toEqual('(cat a b c)'));
- it('should desugar <> nested', () => expect(desguar(' d>')).toEqual('(cat a (cat b c) d)'));
- it('should desugar mixed [] <>', () => expect(desguar('[a ]')).toEqual('(seq a (cat b c))'));
- it('should desugar mixed <> []', () => expect(desguar('')).toEqual('(cat a (seq b c))'));
+ it('should desugar []', () => expect(desguar('[a b c]')).toEqual('(square a b c)'));
+ it('should desugar [] nested', () => expect(desguar('[a [b c] d]')).toEqual('(square a (square b c) d)'));
+ it('should desugar <>', () => expect(desguar('')).toEqual('(angle a b c)'));
+ it('should desugar <> nested', () => expect(desguar(' d>')).toEqual('(angle a (angle b c) d)'));
+ it('should desugar mixed [] <>', () => expect(desguar('[a ]')).toEqual('(square a (angle b c))'));
+ it('should desugar mixed <> []', () => expect(desguar('')).toEqual('(angle a (square b c))'));
it('should desugar .', () => expect(desguar('s jazz . fast 2')).toEqual('(fast (s jazz) 2)'));
- it('should desugar . seq', () => expect(desguar('[bd cp . fast 2]')).toEqual('(fast (seq bd cp) 2)'));
+ it('should desugar . square', () => expect(desguar('[bd cp . fast 2]')).toEqual('(fast (square bd cp) 2)'));
it('should desugar . twice', () => expect(desguar('s jazz . fast 2 . slow 2')).toEqual('(slow (fast (s jazz) 2) 2)'));
it('should desugar . nested', () => expect(desguar('(s cp . fast 2)')).toEqual('(fast (s cp) 2)'));
- it('should desugar . within []', () => expect(desguar('[bd cp . fast 2]')).toEqual('(fast (seq bd cp) 2)'));
+ it('should desugar . within []', () => expect(desguar('[bd cp . fast 2]')).toEqual('(fast (square bd cp) 2)'));
it('should desugar . within , within []', () =>
- expect(desguar('[bd cp . fast 2, x]')).toEqual('(stack (fast (seq bd cp) 2) x)'));
+ expect(desguar('[bd cp . fast 2, x]')).toEqual('(stack (fast (square bd cp) 2) x)'));
- it('should desugar . ()', () => expect(desguar('[jazz hh.(fast 2)]')).toEqual('(seq jazz (fast hh 2))'));
+ it('should desugar . ()', () => expect(desguar('[jazz hh.(fast 2)]')).toEqual('(square jazz (fast hh 2))'));
- it('should desugar , seq', () => expect(desguar('[bd, hh]')).toEqual('(stack bd hh)'));
- it('should desugar , seq 2', () => expect(desguar('[bd, hh oh]')).toEqual('(stack bd (seq hh oh))'));
- it('should desugar , seq 3', () => expect(desguar('[bd cp, hh oh]')).toEqual('(stack (seq bd cp) (seq hh oh))'));
- it('should desugar , cat', () => expect(desguar('')).toEqual('(stack bd hh)'));
- it('should desugar , cat 2', () => expect(desguar('')).toEqual('(stack bd (cat hh oh))'));
- it('should desugar , cat 3', () => expect(desguar('')).toEqual('(stack (cat bd cp) (cat hh oh))'));
+ it('should desugar , square', () => expect(desguar('[bd, hh]')).toEqual('(stack bd hh)'));
+ it('should desugar , square 2', () => expect(desguar('[bd, hh oh]')).toEqual('(stack bd (square hh oh))'));
+ it('should desugar , square 3', () =>
+ expect(desguar('[bd cp, hh oh]')).toEqual('(stack (square bd cp) (square hh oh))'));
+ it('should desugar , angle', () => expect(desguar('')).toEqual('(stack bd hh)'));
+ it('should desugar , angle 2', () => expect(desguar('')).toEqual('(stack bd (angle hh oh))'));
+ it('should desugar , angle 3', () =>
+ expect(desguar('')).toEqual('(stack (angle bd cp) (angle hh oh))'));
it('should desugar , ()', () => expect(desguar('(s bd, s cp)')).toEqual('(stack (s bd) (s cp))'));
- it('should desugar * /', () => expect(desguar('[a b*2 c d/3 e]')).toEqual('(seq a (* b 2) c (/ d 3) e)'));
- it('should desugar []*x', () => expect(desguar('[a [b c]*3]')).toEqual('(seq a (* (seq b c) 3))'));
+ it('should desugar * /', () => expect(desguar('[a b*2 c d/3 e]')).toEqual('(square a (* b 2) c (/ d 3) e)'));
+ it('should desugar []*x', () => expect(desguar('[a [b c]*3]')).toEqual('(square a (* (square b c) 3))'));
it('should desugar x:y', () => expect(desguar('x:y')).toEqual('(: x y)'));
it('should desugar x:y:z', () => expect(desguar('x:y:z')).toEqual('(: (: x y) z)'));
it('should desugar x:y*x', () => expect(desguar('bd:0*2')).toEqual('(* (: bd 0) 2)'));
@@ -106,6 +108,6 @@ describe('mondo sugar', () => {
it('should desugar README example', () =>
expect(desguar('s [bd hh*2 cp.(crush 4) ] . speed .8')).toEqual(
- '(speed (s (seq bd (* hh 2) (crush cp 4) (cat mt ht lt))) .8)',
+ '(speed (s (square bd (* hh 2) (crush cp 4) (angle mt ht lt))) .8)',
));
});
diff --git a/packages/mondough/mondough.mjs b/packages/mondough/mondough.mjs
index e81cfecb7..13b0f602f 100644
--- a/packages/mondough/mondough.mjs
+++ b/packages/mondough/mondough.mjs
@@ -1,52 +1,56 @@
-import { strudelScope, reify, fast, slow, seq, stepcat } from '@strudel/core';
+import { strudelScope, reify, fast, slow, seq, stepcat, extend, expand, pace } from '@strudel/core';
import { registerLanguage } from '@strudel/transpiler';
import { MondoRunner } from '../mondo/mondo.mjs';
-let runner = new MondoRunner(strudelScope);
+const tail = (friend, pat) => pat.fmap((a) => (b) => (Array.isArray(a) ? [...a, b] : [a, b])).appLeft(friend);
-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?
- // example: "s sine" -> sine is also a variable
- return reify(strudelScope[value]).withLoc(from, to);
- }
- return reify(value).withLoc(from, to);
-};
-strudelScope.curly = stepcat;
-strudelScope.seq = (...args) => stepcat(...args).setSteps(1);
-strudelScope.cat = (...args) => stepcat(...args).pace(1);
-
-strudelScope.call = (fn, args, name) => {
- const [pat, ...rest] = args;
- if (!['seq', 'cat', 'stack', 'curly', ':', '..', '!', '@', '%'].includes(name)) {
- args = [...rest, pat];
- }
- return fn(...args);
-};
-
-strudelScope['*'] = fast;
-strudelScope['/'] = slow;
-strudelScope['!'] = (pat, n) => pat.extend(n);
-strudelScope['@'] = (pat, n) => pat.expand(n);
-strudelScope['%'] = (pat, n) => pat.pace(n);
-
-// : operator
-const tail = (pat, friend) => pat.fmap((a) => (b) => (Array.isArray(a) ? [...a, b] : [a, b])).appLeft(friend);
-strudelScope[':'] = tail;
-
-// .. operator
const arrayRange = (start, stop, step = 1) =>
Array.from({ length: Math.abs(stop - start) / step + 1 }, (_, index) =>
start < stop ? start + index * step : start - index * step,
);
const range = (min, max) => min.squeezeBind((a) => max.bind((b) => seq(...arrayRange(a, b))));
-strudelScope['..'] = range;
+
+let lib = {};
+lib.curly = stepcat;
+lib.square = (...args) => stepcat(...args).setSteps(1);
+lib.angle = (...args) => stepcat(...args).pace(1);
+lib['*'] = fast;
+lib['/'] = slow;
+lib['!'] = extend;
+lib['@'] = expand;
+lib['%'] = pace;
+lib[':'] = tail;
+lib['..'] = range;
+
+let runner = new MondoRunner({
+ call(name, args, scope) {
+ console.log('call', name, args, scope);
+ const fn = lib[name] || strudelScope[name];
+ if (!fn) {
+ throw new Error(`[moundough]: unknown function "${name}"`);
+ }
+ if (!['square', 'angle', 'stack', 'curly'].includes(name)) {
+ // flip args (pat to end)
+ const [pat, ...rest] = args;
+ args = [...rest, pat];
+ }
+ return fn(...args);
+ },
+ 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?
+ // example: "s sine" -> sine is also a variable
+ return reify(strudelScope[value]).withLoc(from, to);
+ }
+ return reify(value).withLoc(from, to);
+ },
+});
export function mondo(code, offset = 0) {
if (Array.isArray(code)) {