mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-11 21:58:31 +00:00
208 lines
5.9 KiB
JavaScript
208 lines
5.9 KiB
JavaScript
import { getLeafLocations } from '@strudel/mini';
|
|
import { parse } from 'acorn';
|
|
import escodegen from 'escodegen';
|
|
import { walk } from 'estree-walker';
|
|
|
|
let widgetMethods = [];
|
|
export function registerWidgetType(type) {
|
|
widgetMethods.push(type);
|
|
}
|
|
|
|
export function transpiler(input, options = {}) {
|
|
const { wrapAsync = false, addReturn = true, emitMiniLocations = true, emitWidgets = true } = options;
|
|
|
|
let ast = parse(input, {
|
|
ecmaVersion: 2022,
|
|
allowAwaitOutsideFunction: true,
|
|
locations: true,
|
|
});
|
|
|
|
let miniLocations = [];
|
|
const collectMiniLocations = (value, node) => {
|
|
const leafLocs = getLeafLocations(`"${value}"`, node.start, input);
|
|
miniLocations = miniLocations.concat(leafLocs);
|
|
};
|
|
let widgets = [];
|
|
|
|
walk(ast, {
|
|
enter(node, parent /* , prop, index */) {
|
|
if (isBackTickString(node, parent)) {
|
|
const { quasis } = node;
|
|
const { raw } = quasis[0].value;
|
|
this.skip();
|
|
emitMiniLocations && collectMiniLocations(raw, node);
|
|
return this.replace(miniWithLocation(raw, node));
|
|
}
|
|
if (isStringWithDoubleQuotes(node)) {
|
|
const { value } = node;
|
|
this.skip();
|
|
emitMiniLocations && collectMiniLocations(value, node);
|
|
return this.replace(miniWithLocation(value, node));
|
|
}
|
|
if (isSliderFunction(node)) {
|
|
emitWidgets &&
|
|
widgets.push({
|
|
from: node.arguments[0].start,
|
|
to: node.arguments[0].end,
|
|
value: node.arguments[0].raw, // don't use value!
|
|
min: node.arguments[1]?.value ?? 0,
|
|
max: node.arguments[2]?.value ?? 1,
|
|
step: node.arguments[3]?.value,
|
|
type: 'slider',
|
|
});
|
|
return this.replace(sliderWithLocation(node));
|
|
}
|
|
if (isWidgetMethod(node)) {
|
|
const widgetConfig = {
|
|
to: node.end,
|
|
index: widgets.length,
|
|
type: node.callee.property.name,
|
|
};
|
|
emitWidgets && widgets.push(widgetConfig);
|
|
return this.replace(widgetWithLocation(node, widgetConfig));
|
|
}
|
|
if (isBareSamplesCall(node, parent)) {
|
|
return this.replace(withAwait(node));
|
|
}
|
|
if (isLabelStatement(node)) {
|
|
return this.replace(labelToP(node));
|
|
}
|
|
},
|
|
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,
|
|
};
|
|
}
|
|
let output = escodegen.generate(ast);
|
|
if (wrapAsync) {
|
|
output = `(async ()=>{${output}})()`;
|
|
}
|
|
if (!emitMiniLocations) {
|
|
return { output };
|
|
}
|
|
return { output, miniLocations, widgets };
|
|
}
|
|
|
|
function isStringWithDoubleQuotes(node, locations, code) {
|
|
if (node.type !== 'Literal') {
|
|
return false;
|
|
}
|
|
return node.raw[0] === '"';
|
|
}
|
|
|
|
function isBackTickString(node, parent) {
|
|
return node.type === 'TemplateLiteral' && parent.type !== 'TaggedTemplateExpression';
|
|
}
|
|
|
|
function miniWithLocation(value, node) {
|
|
const { start: fromOffset } = node;
|
|
return {
|
|
type: 'CallExpression',
|
|
callee: {
|
|
type: 'Identifier',
|
|
name: 'm',
|
|
},
|
|
arguments: [
|
|
{ type: 'Literal', value },
|
|
{ type: 'Literal', value: fromOffset },
|
|
],
|
|
optional: false,
|
|
};
|
|
}
|
|
|
|
// these functions are connected to @strudel/codemirror -> slider.mjs
|
|
// maybe someday there will be pluggable transpiler functions, then move this there
|
|
function isSliderFunction(node) {
|
|
return node.type === 'CallExpression' && node.callee.name === 'slider';
|
|
}
|
|
|
|
function isWidgetMethod(node) {
|
|
return node.type === 'CallExpression' && widgetMethods.includes(node.callee.property?.name);
|
|
}
|
|
|
|
function sliderWithLocation(node) {
|
|
const id = 'slider_' + node.arguments[0].start; // use loc of first arg for id
|
|
// add loc as identifier to first argument
|
|
// the sliderWithID function is assumed to be sliderWithID(id, value, min?, max?)
|
|
node.arguments.unshift({
|
|
type: 'Literal',
|
|
value: id,
|
|
raw: id,
|
|
});
|
|
node.callee.name = 'sliderWithID';
|
|
return node;
|
|
}
|
|
|
|
export function getWidgetID(widgetConfig) {
|
|
// the widget id is used as id for the dom element + as key for eventual resources
|
|
// for example, for each scope widget, a new analyser + buffer (large) is created
|
|
// that means, if we use the index index of line position as id, less garbage is generated
|
|
// return `widget_${widgetConfig.to}`; // more gargabe
|
|
//return `widget_${widgetConfig.index}_${widgetConfig.to}`; // also more garbage
|
|
return `widget_${widgetConfig.type}_${widgetConfig.index}`; // less garbage
|
|
}
|
|
|
|
function widgetWithLocation(node, widgetConfig) {
|
|
const id = getWidgetID(widgetConfig);
|
|
// add loc as identifier to first argument
|
|
// the sliderWithID function is assumed to be sliderWithID(id, value, min?, max?)
|
|
node.arguments.unshift({
|
|
type: 'Literal',
|
|
value: id,
|
|
raw: id,
|
|
});
|
|
return node;
|
|
}
|
|
|
|
function isBareSamplesCall(node, parent) {
|
|
return node.type === 'CallExpression' && node.callee.name === 'samples' && parent.type !== 'AwaitExpression';
|
|
}
|
|
|
|
function withAwait(node) {
|
|
return {
|
|
type: 'AwaitExpression',
|
|
argument: node,
|
|
};
|
|
}
|
|
|
|
function isLabelStatement(node) {
|
|
return node.type === 'LabeledStatement';
|
|
}
|
|
|
|
// converts label expressions to p calls: "x: y" to "y.p('x')"
|
|
// see https://github.com/tidalcycles/strudel/issues/990
|
|
function labelToP(node) {
|
|
return {
|
|
type: 'ExpressionStatement',
|
|
expression: {
|
|
type: 'CallExpression',
|
|
callee: {
|
|
type: 'MemberExpression',
|
|
object: node.body.expression,
|
|
property: {
|
|
type: 'Identifier',
|
|
name: 'p',
|
|
},
|
|
},
|
|
arguments: [
|
|
{
|
|
type: 'Literal',
|
|
value: node.label.name,
|
|
raw: `'${node.label.name}'`,
|
|
},
|
|
],
|
|
},
|
|
};
|
|
}
|