import escodegen from 'escodegen'; import { parse } from 'acorn'; import { walk } from 'estree-walker'; import { isNoteWithOctave } from '@strudel.cycles/core'; export function transpiler(input, options = {}) { const { wrapAsync = false, addReturn = true, simpleLocs = false } = options; let ast = parse(input, { ecmaVersion: 2022, allowAwaitOutsideFunction: true, locations: true, }); walk(ast, { enter(node, parent, prop, index) { if (isBackTickString(node, parent)) { const { quasis, start, end } = node; const { raw } = quasis[0].value; this.skip(); return this.replace(miniWithLocation(raw, node, simpleLocs)); } if (isStringWithDoubleQuotes(node)) { const { value, start, end } = node; this.skip(); return this.replace(miniWithLocation(value, node, simpleLocs)); } // TODO: remove pseudo note variables? if (node.type === 'Identifier' && isNoteWithOctave(node.name)) { this.skip(); return this.replace({ type: 'Literal', value: node.name }); } }, leave(node, parent, prop, index) {}, }); const { body } = ast; if (!body?.[body.length - 1]?.expression) { throw new Error('unexpected ast format without body expression'); } // add return to last statement if (addReturn) { const { expression } = body[body.length - 1]; body[body.length - 1] = { type: 'ReturnStatement', argument: expression, }; } const output = escodegen.generate(ast); if (wrapAsync) { return `(async ()=>{${output}})()`; } return output; } function isStringWithDoubleQuotes(node, locations, code) { const { raw, type } = node; if (type !== 'Literal') { return false; } return raw[0] === '"'; } function isBackTickString(node, parent) { return node.type === 'TemplateLiteral' && parent.type !== 'TaggedTemplateExpression'; } function miniWithLocation(value, node, simpleLocs) { let locs; const { start: fromOffset, end: toOffset } = node; if (simpleLocs) { locs = [ { type: 'Literal', value: fromOffset, }, { type: 'Literal', value: toOffset, }, ]; } else { const { loc: { start: { line: fromLine, column: fromColumn }, end: { line: toLine, column: toColumn }, }, } = node; locs = [ { type: 'ArrayExpression', elements: [fromLine, fromColumn, fromOffset].map((value) => ({ type: 'Literal', value, })), }, { type: 'ArrayExpression', elements: [toLine, toColumn, toOffset].map((value) => ({ type: 'Literal', value, })), }, ]; } // with location return { type: 'CallExpression', callee: { type: 'MemberExpression', object: { type: 'CallExpression', callee: { type: 'Identifier', name: 'mini', }, arguments: [{ type: 'Literal', value }], optional: false, }, property: { type: 'Identifier', name: 'withMiniLocation', }, }, arguments: locs, optional: false, }; }