Skip to content

Commit c691916

Browse files
committed
mondo: generic bracket names + simplify call handler + refactor mondough
1 parent e162956 commit c691916

File tree

3 files changed

+90
-94
lines changed

3 files changed

+90
-94
lines changed

packages/mondo/mondo.mjs

Lines changed: 22 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ export class MondoParser {
1212
quotes_single: /^'(.*?)'/,
1313
open_list: /^\(/,
1414
close_list: /^\)/,
15-
open_cat: /^</, // todo: rename angle
16-
close_cat: /^>/,
17-
open_seq: /^\[/, // todo: rename square
18-
close_seq: /^\]/,
15+
open_angle: /^</,
16+
close_angle: /^>/,
17+
open_square: /^\[/,
18+
close_square: /^\]/,
1919
open_curly: /^\{/,
2020
close_curly: /^\}/,
2121
number: /^-?[0-9]*\.?[0-9]+/, // before pipe!
@@ -89,11 +89,11 @@ export class MondoParser {
8989
if (next === 'open_list') {
9090
return this.parse_list();
9191
}
92-
if (next === 'open_cat') {
93-
return this.parse_cat();
92+
if (next === 'open_angle') {
93+
return this.parse_angle();
9494
}
95-
if (next === 'open_seq') {
96-
return this.parse_seq();
95+
if (next === 'open_square') {
96+
return this.parse_square();
9797
}
9898
if (next === 'open_curly') {
9999
return this.parse_curly();
@@ -126,7 +126,7 @@ export class MondoParser {
126126
return chunks;
127127
}
128128
desugar_stack(children, sequence_type) {
129-
// children is expected to contain seq or cat as first item
129+
// children is expected to contain square or angle as first item
130130
const chunks = this.split_children(children, 'stack', sequence_type);
131131
if (!chunks.length) {
132132
return this.desugar_children(children);
@@ -140,7 +140,7 @@ export class MondoParser {
140140
// chunks of multiple args
141141
if (sequence_type) {
142142
// if given, each chunk needs to be prefixed
143-
// [a b, c d] => (stack (seq a b) (seq c d))
143+
// [a b, c d] => (stack (square a b) (square c d))
144144
chunk = [{ type: 'plain', value: sequence_type }, ...chunk];
145145
}
146146
chunk = this.desugar_children(chunk);
@@ -249,16 +249,16 @@ export class MondoParser {
249249
children = this.desugar(children);
250250
return { type: 'list', children };
251251
}
252-
parse_cat() {
253-
let children = this.parse_pair('open_cat', 'close_cat');
254-
children = [{ type: 'plain', value: 'cat' }, ...children];
255-
children = this.desugar(children, 'cat');
252+
parse_angle() {
253+
let children = this.parse_pair('open_angle', 'close_angle');
254+
children = [{ type: 'plain', value: 'angle' }, ...children];
255+
children = this.desugar(children, 'angle');
256256
return { type: 'list', children };
257257
}
258-
parse_seq() {
259-
let children = this.parse_pair('open_seq', 'close_seq');
260-
children = [{ type: 'plain', value: 'seq' }, ...children];
261-
children = this.desugar(children, 'seq');
258+
parse_square() {
259+
let children = this.parse_pair('open_square', 'close_square');
260+
children = [{ type: 'plain', value: 'square' }, ...children];
261+
children = this.desugar(children, 'square');
262262
return { type: 'list', children };
263263
}
264264
parse_curly() {
@@ -307,6 +307,8 @@ export class MondoRunner {
307307
constructor(lib) {
308308
this.parser = new MondoParser();
309309
this.lib = lib;
310+
this.assert(!!this.lib.leaf, `no handler for leaft nodes! add "leaf" to your lib`);
311+
this.assert(!!this.lib.call, `no handler for call nodes! add "call" to your lib`);
310312
}
311313
// a helper to check conditions and throw if they are not met
312314
assert(condition, error) {
@@ -316,16 +318,9 @@ export class MondoRunner {
316318
}
317319
run(code, offset = 0) {
318320
const ast = this.parser.parse(code, offset);
319-
// console.log(printAst(ast));
321+
console.log(printAst(ast));
320322
return this.call(ast);
321323
}
322-
// todo: always use lib.call?
323-
libcall(fn, args, name) {
324-
if (this.lib.call) {
325-
return this.lib.call(fn, args, name);
326-
}
327-
return fn(...args);
328-
}
329324
errorhead(ast) {
330325
return `[mondo ${ast.loc?.join(':') || ''}]`;
331326
}
@@ -357,9 +352,6 @@ export class MondoRunner {
357352
if (arg.type === 'list') {
358353
return this.call(arg, scope);
359354
}
360-
if (!this.lib.leaf) {
361-
throw new Error(`no handler for leaft nodes! add leaf to your lib`);
362-
}
363355

364356
if (arg.type === 'number') {
365357
arg.value = Number(arg.value);
@@ -370,8 +362,6 @@ export class MondoRunner {
370362
});
371363

372364
// look up function in lib
373-
const fn = this.lib[name];
374-
this.assert(fn, `${this.errorhead(first)} unknown function name "${name}"`);
375-
return this.libcall(fn, args, name, scope);
365+
return this.lib.call(name, args, scope);
376366
}
377367
}

packages/mondo/test/mondo.test.mjs

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ const p = (code) => parser.parse(code, -1);
1212

1313
describe('mondo tokenizer', () => {
1414
const parser = new MondoParser();
15-
it('should tokenize with locations', () =>
15+
it('should tokenize with loangleions', () =>
1616
expect(
1717
parser
1818
.tokenize('(one two three)')
1919
.map((t) => t.value + '=' + t.loc.join('-'))
2020
.join(' '),
2121
).toEqual('(=0-1 one=1-4 two=5-8 three=9-14 )=14-15'));
22-
// it('should parse with locations', () => expect(parser.parse('(one two three)')).toEqual());
23-
it('should get locations', () =>
22+
// it('should parse with loangleions', () => expect(parser.parse('(one two three)')).toEqual());
23+
it('should get loangleions', () =>
2424
expect(parser.get_locations('s bd rim')).toEqual([
2525
[2, 4],
2626
[5, 8],
@@ -73,39 +73,41 @@ let desguar = (a) => {
7373
};
7474

7575
describe('mondo sugar', () => {
76-
it('should desugar []', () => expect(desguar('[a b c]')).toEqual('(seq a b c)'));
77-
it('should desugar [] nested', () => expect(desguar('[a [b c] d]')).toEqual('(seq a (seq b c) d)'));
78-
it('should desugar <>', () => expect(desguar('<a b c>')).toEqual('(cat a b c)'));
79-
it('should desugar <> nested', () => expect(desguar('<a <b c> d>')).toEqual('(cat a (cat b c) d)'));
80-
it('should desugar mixed [] <>', () => expect(desguar('[a <b c>]')).toEqual('(seq a (cat b c))'));
81-
it('should desugar mixed <> []', () => expect(desguar('<a [b c]>')).toEqual('(cat a (seq b c))'));
76+
it('should desugar []', () => expect(desguar('[a b c]')).toEqual('(square a b c)'));
77+
it('should desugar [] nested', () => expect(desguar('[a [b c] d]')).toEqual('(square a (square b c) d)'));
78+
it('should desugar <>', () => expect(desguar('<a b c>')).toEqual('(angle a b c)'));
79+
it('should desugar <> nested', () => expect(desguar('<a <b c> d>')).toEqual('(angle a (angle b c) d)'));
80+
it('should desugar mixed [] <>', () => expect(desguar('[a <b c>]')).toEqual('(square a (angle b c))'));
81+
it('should desugar mixed <> []', () => expect(desguar('<a [b c]>')).toEqual('(angle a (square b c))'));
8282

8383
it('should desugar .', () => expect(desguar('s jazz . fast 2')).toEqual('(fast (s jazz) 2)'));
84-
it('should desugar . seq', () => expect(desguar('[bd cp . fast 2]')).toEqual('(fast (seq bd cp) 2)'));
84+
it('should desugar . square', () => expect(desguar('[bd cp . fast 2]')).toEqual('(fast (square bd cp) 2)'));
8585
it('should desugar . twice', () => expect(desguar('s jazz . fast 2 . slow 2')).toEqual('(slow (fast (s jazz) 2) 2)'));
8686
it('should desugar . nested', () => expect(desguar('(s cp . fast 2)')).toEqual('(fast (s cp) 2)'));
87-
it('should desugar . within []', () => expect(desguar('[bd cp . fast 2]')).toEqual('(fast (seq bd cp) 2)'));
87+
it('should desugar . within []', () => expect(desguar('[bd cp . fast 2]')).toEqual('(fast (square bd cp) 2)'));
8888
it('should desugar . within , within []', () =>
89-
expect(desguar('[bd cp . fast 2, x]')).toEqual('(stack (fast (seq bd cp) 2) x)'));
89+
expect(desguar('[bd cp . fast 2, x]')).toEqual('(stack (fast (square bd cp) 2) x)'));
9090

91-
it('should desugar . ()', () => expect(desguar('[jazz hh.(fast 2)]')).toEqual('(seq jazz (fast hh 2))'));
91+
it('should desugar . ()', () => expect(desguar('[jazz hh.(fast 2)]')).toEqual('(square jazz (fast hh 2))'));
9292

93-
it('should desugar , seq', () => expect(desguar('[bd, hh]')).toEqual('(stack bd hh)'));
94-
it('should desugar , seq 2', () => expect(desguar('[bd, hh oh]')).toEqual('(stack bd (seq hh oh))'));
95-
it('should desugar , seq 3', () => expect(desguar('[bd cp, hh oh]')).toEqual('(stack (seq bd cp) (seq hh oh))'));
96-
it('should desugar , cat', () => expect(desguar('<bd, hh>')).toEqual('(stack bd hh)'));
97-
it('should desugar , cat 2', () => expect(desguar('<bd, hh oh>')).toEqual('(stack bd (cat hh oh))'));
98-
it('should desugar , cat 3', () => expect(desguar('<bd cp, hh oh>')).toEqual('(stack (cat bd cp) (cat hh oh))'));
93+
it('should desugar , square', () => expect(desguar('[bd, hh]')).toEqual('(stack bd hh)'));
94+
it('should desugar , square 2', () => expect(desguar('[bd, hh oh]')).toEqual('(stack bd (square hh oh))'));
95+
it('should desugar , square 3', () =>
96+
expect(desguar('[bd cp, hh oh]')).toEqual('(stack (square bd cp) (square hh oh))'));
97+
it('should desugar , angle', () => expect(desguar('<bd, hh>')).toEqual('(stack bd hh)'));
98+
it('should desugar , angle 2', () => expect(desguar('<bd, hh oh>')).toEqual('(stack bd (angle hh oh))'));
99+
it('should desugar , angle 3', () =>
100+
expect(desguar('<bd cp, hh oh>')).toEqual('(stack (angle bd cp) (angle hh oh))'));
99101
it('should desugar , ()', () => expect(desguar('(s bd, s cp)')).toEqual('(stack (s bd) (s cp))'));
100-
it('should desugar * /', () => expect(desguar('[a b*2 c d/3 e]')).toEqual('(seq a (* b 2) c (/ d 3) e)'));
101-
it('should desugar []*x', () => expect(desguar('[a [b c]*3]')).toEqual('(seq a (* (seq b c) 3))'));
102+
it('should desugar * /', () => expect(desguar('[a b*2 c d/3 e]')).toEqual('(square a (* b 2) c (/ d 3) e)'));
103+
it('should desugar []*x', () => expect(desguar('[a [b c]*3]')).toEqual('(square a (* (square b c) 3))'));
102104
it('should desugar x:y', () => expect(desguar('x:y')).toEqual('(: x y)'));
103105
it('should desugar x:y:z', () => expect(desguar('x:y:z')).toEqual('(: (: x y) z)'));
104106
it('should desugar x:y*x', () => expect(desguar('bd:0*2')).toEqual('(* (: bd 0) 2)'));
105107
it('should desugar a..b', () => expect(desguar('0..2')).toEqual('(.. 0 2)'));
106108

107109
it('should desugar README example', () =>
108110
expect(desguar('s [bd hh*2 cp.(crush 4) <mt ht lt>] . speed .8')).toEqual(
109-
'(speed (s (seq bd (* hh 2) (crush cp 4) (cat mt ht lt))) .8)',
111+
'(speed (s (square bd (* hh 2) (crush cp 4) (angle mt ht lt))) .8)',
110112
));
111113
});

packages/mondough/mondough.mjs

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,56 @@
1-
import { strudelScope, reify, fast, slow, seq, stepcat } from '@strudel/core';
1+
import { strudelScope, reify, fast, slow, seq, stepcat, extend, expand, pace } from '@strudel/core';
22
import { registerLanguage } from '@strudel/transpiler';
33
import { MondoRunner } from '../mondo/mondo.mjs';
44

5-
let runner = new MondoRunner(strudelScope);
5+
const tail = (friend, pat) => pat.fmap((a) => (b) => (Array.isArray(a) ? [...a, b] : [a, b])).appLeft(friend);
66

7-
strudelScope.leaf = (token, scope) => {
8-
let { value } = token;
9-
// local scope
10-
if (token.type === 'plain' && scope[value]) {
11-
return reify(scope[value]); // -> local scope has no location
12-
}
13-
const [from, to] = token.loc;
14-
if (token.type === 'plain' && strudelScope[value]) {
15-
// what if we want a string that happens to also be a variable name?
16-
// example: "s sine" -> sine is also a variable
17-
return reify(strudelScope[value]).withLoc(from, to);
18-
}
19-
return reify(value).withLoc(from, to);
20-
};
21-
strudelScope.curly = stepcat;
22-
strudelScope.seq = (...args) => stepcat(...args).setSteps(1);
23-
strudelScope.cat = (...args) => stepcat(...args).pace(1);
24-
25-
strudelScope.call = (fn, args, name) => {
26-
const [pat, ...rest] = args;
27-
if (!['seq', 'cat', 'stack', 'curly', ':', '..', '!', '@', '%'].includes(name)) {
28-
args = [...rest, pat];
29-
}
30-
return fn(...args);
31-
};
32-
33-
strudelScope['*'] = fast;
34-
strudelScope['/'] = slow;
35-
strudelScope['!'] = (pat, n) => pat.extend(n);
36-
strudelScope['@'] = (pat, n) => pat.expand(n);
37-
strudelScope['%'] = (pat, n) => pat.pace(n);
38-
39-
// : operator
40-
const tail = (pat, friend) => pat.fmap((a) => (b) => (Array.isArray(a) ? [...a, b] : [a, b])).appLeft(friend);
41-
strudelScope[':'] = tail;
42-
43-
// .. operator
447
const arrayRange = (start, stop, step = 1) =>
458
Array.from({ length: Math.abs(stop - start) / step + 1 }, (_, index) =>
469
start < stop ? start + index * step : start - index * step,
4710
);
4811
const range = (min, max) => min.squeezeBind((a) => max.bind((b) => seq(...arrayRange(a, b))));
49-
strudelScope['..'] = range;
12+
13+
let lib = {};
14+
lib.curly = stepcat;
15+
lib.square = (...args) => stepcat(...args).setSteps(1);
16+
lib.angle = (...args) => stepcat(...args).pace(1);
17+
lib['*'] = fast;
18+
lib['/'] = slow;
19+
lib['!'] = extend;
20+
lib['@'] = expand;
21+
lib['%'] = pace;
22+
lib[':'] = tail;
23+
lib['..'] = range;
24+
25+
let runner = new MondoRunner({
26+
call(name, args, scope) {
27+
console.log('call', name, args, scope);
28+
const fn = lib[name] || strudelScope[name];
29+
if (!fn) {
30+
throw new Error(`[moundough]: unknown function "${name}"`);
31+
}
32+
if (!['square', 'angle', 'stack', 'curly'].includes(name)) {
33+
// flip args (pat to end)
34+
const [pat, ...rest] = args;
35+
args = [...rest, pat];
36+
}
37+
return fn(...args);
38+
},
39+
leaf(token, scope) {
40+
let { value } = token;
41+
// local scope
42+
if (token.type === 'plain' && scope[value]) {
43+
return reify(scope[value]); // -> local scope has no location
44+
}
45+
const [from, to] = token.loc;
46+
if (token.type === 'plain' && strudelScope[value]) {
47+
// what if we want a string that happens to also be a variable name?
48+
// example: "s sine" -> sine is also a variable
49+
return reify(strudelScope[value]).withLoc(from, to);
50+
}
51+
return reify(value).withLoc(from, to);
52+
},
53+
});
5054

5155
export function mondo(code, offset = 0) {
5256
if (Array.isArray(code)) {

0 commit comments

Comments
 (0)