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: /^/, - open_seq: /^\[/, // todo: rename square - close_seq: /^\]/, + open_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)) {