mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-11 21:58:31 +00:00
267 lines
8.4 KiB
JavaScript
267 lines
8.4 KiB
JavaScript
import { parseScriptWithLocation } from './shift-parser/index.js'; // npm module does not work in the browser
|
|
import traverser from './shift-traverser/index.js'; // npm module does not work in the browser
|
|
const { replace } = traverser;
|
|
import { LiteralStringExpression, IdentifierExpression, CallExpression, StaticMemberExpression } from '../_snowpack/pkg/shift-ast.js';
|
|
import codegen from '../_snowpack/pkg/shift-codegen.js';
|
|
import * as strudel from '../_snowpack/link/strudel.js';
|
|
|
|
const { Pattern } = strudel;
|
|
|
|
const isNote = (name) => /^[a-gC-G][bs]?[0-9]$/.test(name);
|
|
|
|
const addLocations = true;
|
|
export const addMiniLocations = true;
|
|
|
|
export default (code) => {
|
|
const ast = parseScriptWithLocation(code);
|
|
const nodesWithLocation = [];
|
|
const parents = [];
|
|
const shifted = replace(ast.tree, {
|
|
enter(node, parent) {
|
|
parents.push(parent);
|
|
const isSynthetic = parents.some((p) => nodesWithLocation.includes(p));
|
|
if (isSynthetic) {
|
|
return node;
|
|
}
|
|
const grandparent = parents[parents.length - 2];
|
|
const isTimeCat = parent?.type === 'ArrayExpression' && isPatternFactory(grandparent);
|
|
const isMarkable = isPatternFactory(parent) || isTimeCat;
|
|
// operator overloading => still not done
|
|
const operators = {
|
|
'*': 'fast',
|
|
'/': 'slow',
|
|
'&': 'stack',
|
|
'&&': 'append',
|
|
};
|
|
if (
|
|
node.type === 'BinaryExpression' &&
|
|
operators[node.operator] &&
|
|
['LiteralNumericExpression', 'LiteralStringExpression', 'IdentifierExpression'].includes(node.right?.type) &&
|
|
canBeOverloaded(node.left)
|
|
) {
|
|
let arg = node.left;
|
|
if (node.left.type === 'IdentifierExpression') {
|
|
arg = wrapReify(node.left);
|
|
}
|
|
return new CallExpression({
|
|
callee: new StaticMemberExpression({
|
|
property: operators[node.operator],
|
|
object: wrapReify(arg),
|
|
}),
|
|
arguments: [node.right],
|
|
});
|
|
}
|
|
// replace pseudo note variables
|
|
if (node.type === 'IdentifierExpression') {
|
|
if (isNote(node.name)) {
|
|
const value = node.name[1] === 's' ? node.name.replace('s', '#') : node.name;
|
|
if (addLocations && isMarkable) {
|
|
return reifyWithLocation(value, node, ast.locations, nodesWithLocation);
|
|
}
|
|
return new LiteralStringExpression({ value });
|
|
}
|
|
if (node.name === 'r') {
|
|
return new IdentifierExpression({ name: 'silence' });
|
|
}
|
|
}
|
|
if (addLocations && node.type === 'LiteralStringExpression' && isMarkable) {
|
|
// console.log('add', node);
|
|
return reifyWithLocation(node.value, node, ast.locations, nodesWithLocation);
|
|
}
|
|
if (!addMiniLocations) {
|
|
return node;
|
|
}
|
|
// mini notation location handling
|
|
const miniFunctions = ['mini', 'm'];
|
|
const isAlreadyWrapped = parent?.type === 'CallExpression' && parent.callee.name === 'withLocationOffset';
|
|
if (node.type === 'CallExpression' && miniFunctions.includes(node.callee.name) && !isAlreadyWrapped) {
|
|
// mini('c3')
|
|
if (node.arguments.length > 1) {
|
|
// TODO: transform mini(...args) to cat(...args.map(mini)) ?
|
|
console.warn('multi arg mini locations not supported yet...');
|
|
return node;
|
|
}
|
|
return wrapLocationOffset(node, node.arguments, ast.locations, nodesWithLocation);
|
|
}
|
|
if (node.type === 'StaticMemberExpression' && miniFunctions.includes(node.property) && !isAlreadyWrapped) {
|
|
// 'c3'.mini or 'c3'.m
|
|
return wrapLocationOffset(node, node.object, ast.locations, nodesWithLocation);
|
|
}
|
|
return node;
|
|
},
|
|
leave() {
|
|
parents.pop();
|
|
},
|
|
});
|
|
return codegen(shifted);
|
|
};
|
|
|
|
function wrapReify(node) {
|
|
return new CallExpression({
|
|
callee: new IdentifierExpression({
|
|
name: 'reify',
|
|
}),
|
|
arguments: [node],
|
|
});
|
|
}
|
|
|
|
function isPatternFactory(node) {
|
|
return node?.type === 'CallExpression' && Object.keys(Pattern.prototype.factories).includes(node.callee.name);
|
|
}
|
|
|
|
function canBeOverloaded(node) {
|
|
return (node.type === 'IdentifierExpression' && isNote(node.name)) || isPatternFactory(node);
|
|
// TODO: support sequence(c3).transpose(3).x.y.z
|
|
}
|
|
|
|
// turn node into withLocationOffset(node, location)
|
|
function wrapLocationOffset(node, stringNode, locations, nodesWithLocation) {
|
|
// console.log('wrapppp', stringNode);
|
|
const expression = {
|
|
type: 'CallExpression',
|
|
callee: {
|
|
type: 'IdentifierExpression',
|
|
name: 'withLocationOffset',
|
|
},
|
|
arguments: [node, getLocationObject(stringNode, locations)],
|
|
};
|
|
nodesWithLocation.push(expression);
|
|
// console.log('wrapped', codegen(expression));
|
|
return expression;
|
|
}
|
|
|
|
// turns node in reify(value).withLocation(location), where location is the node's location in the source code
|
|
// with this, the reified pattern can pass its location to the event, to know where to highlight when it's active
|
|
function reifyWithLocation(value, node, locations, nodesWithLocation) {
|
|
// console.log('reifyWithLocation', value, node);
|
|
const withLocation = new CallExpression({
|
|
callee: new StaticMemberExpression({
|
|
object: new CallExpression({
|
|
callee: new IdentifierExpression({
|
|
name: 'reify',
|
|
}),
|
|
arguments: [new LiteralStringExpression({ value })],
|
|
}),
|
|
property: 'withLocation',
|
|
}),
|
|
arguments: [getLocationObject(node, locations)],
|
|
});
|
|
nodesWithLocation.push(withLocation);
|
|
return withLocation;
|
|
}
|
|
|
|
// returns ast for source location object
|
|
function getLocationObject(node, locations) {
|
|
/*const locationAST = parseScript(
|
|
"x=" + JSON.stringify(ast.locations.get(node))
|
|
).statements[0].expression.expression;
|
|
|
|
console.log("locationAST", locationAST);*/
|
|
|
|
/*const callAST = parseScript(
|
|
`reify(${node.name}).withLocation(${JSON.stringify(
|
|
ast.locations.get(node)
|
|
)})`
|
|
).statements[0].expression;*/
|
|
const loc = locations.get(node);
|
|
return {
|
|
type: 'ObjectExpression',
|
|
properties: [
|
|
{
|
|
type: 'DataProperty',
|
|
name: {
|
|
type: 'StaticPropertyName',
|
|
value: 'start',
|
|
},
|
|
expression: {
|
|
type: 'ObjectExpression',
|
|
properties: [
|
|
{
|
|
type: 'DataProperty',
|
|
name: {
|
|
type: 'StaticPropertyName',
|
|
value: 'line',
|
|
},
|
|
expression: {
|
|
type: 'LiteralNumericExpression',
|
|
value: loc.start.line,
|
|
},
|
|
},
|
|
{
|
|
type: 'DataProperty',
|
|
name: {
|
|
type: 'StaticPropertyName',
|
|
value: 'column',
|
|
},
|
|
expression: {
|
|
type: 'LiteralNumericExpression',
|
|
value: loc.start.column,
|
|
},
|
|
},
|
|
{
|
|
type: 'DataProperty',
|
|
name: {
|
|
type: 'StaticPropertyName',
|
|
value: 'offset',
|
|
},
|
|
expression: {
|
|
type: 'LiteralNumericExpression',
|
|
value: loc.start.offset,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
type: 'DataProperty',
|
|
name: {
|
|
type: 'StaticPropertyName',
|
|
value: 'end',
|
|
},
|
|
expression: {
|
|
type: 'ObjectExpression',
|
|
properties: [
|
|
{
|
|
type: 'DataProperty',
|
|
name: {
|
|
type: 'StaticPropertyName',
|
|
value: 'line',
|
|
},
|
|
expression: {
|
|
type: 'LiteralNumericExpression',
|
|
value: loc.end.line,
|
|
},
|
|
},
|
|
{
|
|
type: 'DataProperty',
|
|
name: {
|
|
type: 'StaticPropertyName',
|
|
value: 'column',
|
|
},
|
|
expression: {
|
|
type: 'LiteralNumericExpression',
|
|
value: loc.end.column,
|
|
},
|
|
},
|
|
{
|
|
type: 'DataProperty',
|
|
name: {
|
|
type: 'StaticPropertyName',
|
|
value: 'offset',
|
|
},
|
|
expression: {
|
|
type: 'LiteralNumericExpression',
|
|
value: loc.end.offset,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
// TODO: turn x.groove['[~ x]*2'] into x.groove('[~ x]*2'.m)
|
|
// and ['c1*2'].xx into 'c1*2'.m.xx ??
|
|
// or just all templated strings?? x.groove(`[~ x]*2`)
|