UNPKG

mercury-lang

Version:

Parser for the mercury live coding language

364 lines (342 loc) 10.1 kB
//==================================================================== // Mercury Intermediate Representation // written by Timo Hoogland (c) www.timohoogland.com // // Returns results for the parsing tree when parsing a line of code // Inspired by the SEMA language Intermediate Representation by // Chris Kiefer, Thor Magnuson & Francesco Bernardo //==================================================================== // const bind = require('./bind-functions.gen.json'); // total-serialism library functions const tsIR = require('./totalSerialismIR.js').functionMap; // mercury IR const keyBind = require('./mercuryIR.js').keyBind; // included instrument/object defaults const instruments = require('../data/instrument-defaults.js').instrumentDefaults; // mini language, use single characters for keywords and functions // const miniLang = require('../data/mini-functions.json'); // code accepted global parameters const globals = 'tempo signature amp scale root randomSeed highPass lowPass silence sound crossFade'.split(' '); // code defaults and parsetree let code = { 'global' : { // 'tempo' : [ 90 ], // 'volume' : [ 0.8 ], // 'scale' : [ 'chromatic', 'c' ], // 'root' : [ 'c' ], // 'signature' : [ '4/4' ], 'randomSeed' : [ 0 ], 'highPass' : [ 5, 0 ], 'lowPass' : [ 18000, 0 ], 'silence' : false, }, 'variables' : {}, 'objects' : {}, 'groups' : { 'all' : [] }, 'print' : [], 'display' : [], 'comments' : [], 'errors' : [], 'warnings' : [] } function deepCopy(o){ return JSON.parse(JSON.stringify(o)); } function traverseTreeIR(tree){ // deepcopy the syntax tree let tmp = deepCopy(tree); // deepcopy the code template let ccode = deepCopy(code); tmp.map((t) => { // console.log('@tree', t); tmp = traverseTree(t, ccode); }) return ccode; } function traverseTree(tree, code, level, obj){ // console.log(`traversing`, tree); let map = { '@global' : (el, ccode) => { // if global code (comments, numbers, functions) // console.log({'global =>':el}); return traverseTree(el, ccode, '@setting'); }, '@comment' : (el, ccode) => { // console.table({ '@comment' : el }); // if a comment, just return ccode.comments.push(el); return ccode; }, '@print' : (el, ccode) => { // console.log({'print =>':el}); el.map((e) => { Object.keys(e).forEach((k) => { let p = map[k](e[k], ccode); ccode.print.push(p); }); }); return ccode; }, '@display' : (el, ccode) => { ccode.display.push(traverseTree(el, ccode)); return ccode; }, '@settings' : (el, ccode) => { // console.log('@settings', traverseTree(el, ccode)); let name = keyBind(traverseTree(el, ccode)); if (globals.includes(name)){ ccode.global[name] = true; } else { ccode.warnings.push(`Warning: Unkown setting name: ${name}`); } return ccode; }, '@list' : (el, ccode) => { // if list/ring/array is instantiated store in variables // console.log({'list =>':el}); let r = traverseTree(el['@params'], ccode, '@list'); ccode.variables[el['@name']] = r; return ccode; }, '@object' : (el, ccode) => { // if object is instantiated or set (new/make, set/apply) // console.log({'@object =>':el}); return traverseTree(el, ccode); }, '@new' : (el, ccode) => { // when new instrument check for instrument // console.log({'@new =>':el}); let inst = map['@inst'](el['@inst'], ccode); delete el['@inst']; // add the line number and code to the object for later use inst.line = map['@line'](el['@line']); delete el['@line']; // inst.code = map['@code'](el['@code']); // delete el['@code']; // generate unique ID name for object before checking the name() // this ID is used for groups if there are any inst.functions.name = [ uniqueID(8) ]; Object.keys(el).forEach((k) => { inst = map[k](el[k], ccode, '@object', inst); }); // add the name to the all group ccode.groups.all.push(inst.functions.name[0]); // generate unique ID name for object if no name() // if (!inst.functions.name){ // inst.functions.name = [ uniqueID(8) ]; // } // console.log('code', ccode); // add object to complete code ccode.objects[inst.functions.name] = inst; return ccode; }, '@set' : (el, ccode) => { // set instrument, all or global parameters // console.log({'set =>':el}); let name = keyBind(el['@name']); delete el['@name']; if (ccode.objects[name]){ // if part of current instrument objects let inst = ccode.objects[name]; Object.keys(el).forEach((k) => { inst = map[k](el[k], ccode, '@object', inst); }); ccode.objects[inst.functions.name] = inst; } else if (code.groups[name]){ //name === 'all' // if set all, set all instrument objects code.groups[name].forEach((o) => { let inst = ccode.objects[o]; if (inst){ Object.keys(el).forEach((k) => { inst = map[k](el[k], ccode, '@object', inst); }); ccode.objects[inst.functions.name] = inst; } }); // console.log(ccode.objects); // Object.keys(ccode.objects).forEach((o) => { // let inst = ccode.objects[o]; // Object.keys(el).forEach((k) => { // inst = map[k](el[k], ccode, '@object', inst); // }); // ccode.objects[inst.functions.name] = inst; // }); } else { // if name is part of global settings let args; Object.keys(el).forEach((k) => { args = map[k](el[k], ccode, '@setting', args); }); // if name is a total-serialism function if (tsIR[name]){ if (args){ tsIR[name](...args); } else { tsIR[name](); } } if (!globals.includes(name)){ ccode.warnings.push(`Warning: Unkown instrument or setting: ${name}`); } ccode.global[name] = args; } // else { // } return ccode; }, '@inst' : (el, ccode) => { // check instruments for name and then deepcopy to output // if not a valid instrument return empty instrument // console.log({'@inst =>': el}); let inst; if (!instruments[el]){ inst = deepCopy(instruments['empty']); inst.type = el; ccode.warnings.push(`Warning: Unknown instrument type: ${el}`); } else { inst = deepCopy(instruments[el]); } inst.object = el; return inst; }, '@type' : (el, ccode, level, inst) => { // return the value of the type, can be identifier, string, array // console.log({'@type':el, '@inst':inst}); inst.type = traverseTree(el, ccode); return inst; }, '@functions' : (el, ccode, level, inst) => { // add all functions to object or parse for settings // console.log({'@functions =>':el, '@l':level, '@i':inst}); if (level === '@setting'){ // set arguments from global settings let args = []; el.map((e) => { Object.keys(e).map((k) => { args.push(map[k](e[k], ccode)); }); }); return args; } let funcs = inst.functions; // for every function in functions list el.map((e) => { Object.keys(e).map((k) => { funcs = map[k](e[k], ccode, '@object', funcs); }); }); inst.functions = funcs; return inst; }, '@function' : (el, ccode, level, funcs) => { // for every function check if the keyword maps to other // function keyword from keyword bindings. // if function is part of ts library execute and parse results // console.log({'@f':el, '@l':level, '@fs':funcs}); let args = []; let func = keyBind(el['@name']); if (el['@args'] !== null){ // fill arguments if not null el['@args'].map((e) => { Object.keys(e).map((k) => { args.push(map[k](e[k], ccode, '@list')); }); }); } if (tsIR[func] && level !== '@object'){ // if function is part of TS and not in @object level try { if (args){ return tsIR[func](...args); } return tsIR[func](); } catch (e) { ccode.errors.push(`Error in arguments for function: ${func}`) return [0]; } } else if (level === '@list'){ // if not part of TS and in @list level ccode.errors.push(`Unknown list function: ${func}`); return [0]; } else if (level === '@object'){ // if in @object level ignore TS functions if (func === 'add_fx'){ funcs[func].push(args); } else { // if (func === 'name'){ // ccode.groups.all.push(...args); // } else if (func === 'group'){ args.forEach((a) => { // add empty array if the group doesn't exist yet if (!ccode.groups[a]) { ccode.groups[a] = []; } // add the name of the inst to the group array ccode.groups[a].push(funcs.name[0]); }); } funcs[func] = args; } return funcs; } else { el['@args'] = args; return el; } }, '@array' : (el, ccode) => { // console.log({'@array':el}); let arr = []; // if not an empty array parse all items if (el){ el.map((e) => { Object.keys(e).map((k) => { arr.push(map[k](e[k], ccode)); }); }); } return arr; }, '@identifier' : (el, ccode) => { // if identifier is variable return the content if (ccode.variables[el]){ return ccode.variables[el]; } return el; }, '@string' : (el) => { return el; }, '@number' : (el) => { return el; }, '@division' : (el) => { return el; }, '@note' : (el) => { return el; }, '@signal' : (el) => { return el; }, '@line' : (el) => { return el; }, '@code' : (el) => { return el; } } if (Array.isArray(tree)) { // console.log('array process of', tree); tree.map((el) => { Object.keys(el).map((k) => { code = map[k](el[k], code, level, obj); }); }); } else { // console.log('object process of', tree); if (tree){ Object.keys(tree).map((k) => { code = map[k](tree[k], code, level, obj); }); } } return code; } function uniqueID(length){ let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'.split(''); let s = ''; for (let l=0; l<length; l++){ s += chars[Math.floor(Math.random() * chars.length)]; } return s; } module.exports = { traverseTreeIR };