requested revisions 1 (and fix failing test)
This commit is contained in:
parent
ba7721f53c
commit
1a7f464998
4 changed files with 90 additions and 87 deletions
|
|
@ -44,7 +44,7 @@ export const evalBlock = (strudelMirror) => {
|
||||||
if (block) {
|
if (block) {
|
||||||
// Flash the block being evaluated
|
// Flash the block being evaluated
|
||||||
strudelMirror.flash(200, { from: a, to: b });
|
strudelMirror.flash(200, { from: a, to: b });
|
||||||
strudelMirror.repl.evaluate(block, true, true, true, { range });
|
strudelMirror.repl.evaluate(block, true, true, { range });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -126,76 +126,11 @@ export function repl({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// // Helper function to filter meta (widgets, sliders, miniLocations) by block range
|
|
||||||
// function splitCodeByRange(meta, blockStart, blockEnd) {
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Helper function to extract labels from code with their positions
|
|
||||||
function extractLabelsFromCode(code) {
|
|
||||||
const labels = [];
|
|
||||||
// Regex to find label patterns like "d1:" or "myLabel:"
|
|
||||||
const labelRegex = /^(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*:)/gm;
|
|
||||||
let match;
|
|
||||||
while ((match = labelRegex.exec(code)) !== null) {
|
|
||||||
labels.push({
|
|
||||||
name: match[2],
|
|
||||||
index: match.index,
|
|
||||||
end: match.index + match[1].length,
|
|
||||||
fullMatch: match[1],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also check for 'all()' and treat it as a special label
|
|
||||||
// just for management purposes
|
|
||||||
const allRegex = /all\s*\(\s*([^)]+)\s*\)/;
|
|
||||||
const allMatch = allRegex.exec(code);
|
|
||||||
if (allMatch) {
|
|
||||||
// Check if the argument contains a widget call
|
|
||||||
const argCode = allMatch[1];
|
|
||||||
const activeVisualizer = detectActiveVisualizer(argCode);
|
|
||||||
|
|
||||||
labels.push({
|
|
||||||
name: 'all',
|
|
||||||
index: allMatch.index,
|
|
||||||
end: allMatch.index + allMatch[0].length,
|
|
||||||
fullMatch: allMatch[0],
|
|
||||||
activeVisualizer: activeVisualizer,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to detect non-inline widget calls in code
|
|
||||||
function detectActiveVisualizer(code) {
|
|
||||||
// List of non-inline widgets that need cleanup
|
|
||||||
// These are Pattern.prototype methods that create persistent visualizations
|
|
||||||
// I don't like this approach, feels hacky, but since these methods don't create ids that
|
|
||||||
// can be read through the transpiler, I'm not sure how best to detect them.
|
|
||||||
// Would probably be better to register these methods through some function that would
|
|
||||||
// tag them better
|
|
||||||
const nonInlineWidgets = ['punchcard', 'spiral', 'scope', 'pitchwheel', 'spectrum', 'pianoroll', 'wordfall'];
|
|
||||||
|
|
||||||
for (const widget of nonInlineWidgets) {
|
|
||||||
const widgetRegex = new RegExp(`\\.${widget}\\s*\\(`);
|
|
||||||
if (widgetRegex.test(code)) {
|
|
||||||
return widget;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to handle single label code block storage
|
// Helper function to handle single label code block storage
|
||||||
function handleSingleLabelBlock(label, code, options, meta) {
|
function handleSingleLabelBlock(label, code, options, meta) {
|
||||||
// Detect if this block contains a non-inline widget
|
// Detect if this block contains a non-inline widget
|
||||||
// For 'all' label, widget info is already on the label object from extractLabelsFromCode
|
// The activeVisualizer is now provided by the transpiler for all labels
|
||||||
|
const activeVisualizer = label.activeVisualizer || null;
|
||||||
// As mentioned in detectActiveVisualizer, this is a bad approach to
|
|
||||||
// managing non-inline widgets (or widgets that don't have ids)
|
|
||||||
// and a proper solution would give them widgets in the transpiler, or at least
|
|
||||||
// track where they are so we don't have to futz around with regexes
|
|
||||||
const activeVisualizer =
|
|
||||||
label.activeVisualizer !== undefined ? label.activeVisualizer : detectActiveVisualizer(code);
|
|
||||||
|
|
||||||
if (activeVisualizer !== null) {
|
if (activeVisualizer !== null) {
|
||||||
lastActiveVisualizerLabel = label.name;
|
lastActiveVisualizerLabel = label.name;
|
||||||
|
|
@ -371,7 +306,7 @@ export function repl({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const evaluate = async (code, autostart = true, shouldHush = true, blockBased = false, options = {}) => {
|
const evaluate = async (code, autostart = true, blockBased = false, options = {}) => {
|
||||||
if (!code) {
|
if (!code) {
|
||||||
throw new Error('no code to evaluate');
|
throw new Error('no code to evaluate');
|
||||||
}
|
}
|
||||||
|
|
@ -390,7 +325,7 @@ export function repl({
|
||||||
|
|
||||||
if (!blockBased) {
|
if (!blockBased) {
|
||||||
codeBlocks = {};
|
codeBlocks = {};
|
||||||
shouldHush && hush();
|
hush();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mondo) {
|
if (mondo) {
|
||||||
|
|
@ -403,7 +338,7 @@ export function repl({
|
||||||
let widgetRemoved = false;
|
let widgetRemoved = false;
|
||||||
|
|
||||||
if (blockBased) {
|
if (blockBased) {
|
||||||
const labels = extractLabelsFromCode(code);
|
const labels = meta.labels || [];
|
||||||
|
|
||||||
// Store code blocks in dictionary using labels as keys
|
// Store code blocks in dictionary using labels as keys
|
||||||
if (labels.length > 0) {
|
if (labels.length > 0) {
|
||||||
|
|
@ -526,18 +461,18 @@ export function repl({
|
||||||
|
|
||||||
export const getTrigger =
|
export const getTrigger =
|
||||||
({ getTime, defaultOutput }) =>
|
({ getTime, defaultOutput }) =>
|
||||||
async (hap, deadline, duration, cps, t) => {
|
async (hap, deadline, duration, cps, t) => {
|
||||||
// ^ this signature is different from hap.context.onTrigger, as set by Pattern.onTrigger(onTrigger)
|
// ^ this signature is different from hap.context.onTrigger, as set by Pattern.onTrigger(onTrigger)
|
||||||
// TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004
|
// TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004
|
||||||
try {
|
try {
|
||||||
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
|
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
|
||||||
await defaultOutput(hap, deadline, duration, cps, t);
|
await defaultOutput(hap, deadline, duration, cps, t);
|
||||||
|
}
|
||||||
|
if (hap.context.onTrigger) {
|
||||||
|
// call signature of output / onTrigger is different...
|
||||||
|
await hap.context.onTrigger(hap, getTime(), cps, t);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
errorLogger(err, 'getTrigger');
|
||||||
}
|
}
|
||||||
if (hap.context.onTrigger) {
|
};
|
||||||
// call signature of output / onTrigger is different...
|
|
||||||
await hap.context.onTrigger(hap, getTime(), cps, t);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
errorLogger(err, 'getTrigger');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ export function transpiler(input, options = {}) {
|
||||||
};
|
};
|
||||||
let widgets = [];
|
let widgets = [];
|
||||||
let sliders = [];
|
let sliders = [];
|
||||||
|
let labels = [];
|
||||||
|
|
||||||
walk(ast, {
|
walk(ast, {
|
||||||
enter(node, parent /* , prop, index */) {
|
enter(node, parent /* , prop, index */) {
|
||||||
|
|
@ -146,10 +147,32 @@ export function transpiler(input, options = {}) {
|
||||||
return this.replace(withAwait(node));
|
return this.replace(withAwait(node));
|
||||||
}
|
}
|
||||||
if (isLabelStatement(node)) {
|
if (isLabelStatement(node)) {
|
||||||
|
// Collect label info for block-based evaluation
|
||||||
|
// Store positions WITHOUT offset so repl can slice the transpiler output correctly
|
||||||
|
if (blockBased) {
|
||||||
|
labels.push({
|
||||||
|
name: node.label.name,
|
||||||
|
index: node.start - nodeOffset,
|
||||||
|
end: node.label.end - nodeOffset,
|
||||||
|
fullMatch: input.slice(node.start - nodeOffset, node.label.end - nodeOffset),
|
||||||
|
activeVisualizer: findVisualizerInSubtree(node.body),
|
||||||
|
});
|
||||||
|
}
|
||||||
return this.replace(labelToP(node));
|
return this.replace(labelToP(node));
|
||||||
}
|
}
|
||||||
|
// Detect all() calls as special labels for block management
|
||||||
|
// Store positions WITHOUT offset so repl can slice the transpiler output correctly
|
||||||
|
if (blockBased && isAllCall(node)) {
|
||||||
|
labels.push({
|
||||||
|
name: 'all',
|
||||||
|
index: node.start - nodeOffset,
|
||||||
|
end: node.end - nodeOffset,
|
||||||
|
fullMatch: input.slice(node.start - nodeOffset, node.end - nodeOffset),
|
||||||
|
activeVisualizer: node.arguments[0] ? findVisualizerInSubtree(node.arguments[0]) : null,
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
leave(node, parent, prop, index) {},
|
leave(node, parent, prop, index) { },
|
||||||
});
|
});
|
||||||
|
|
||||||
let { body } = ast;
|
let { body } = ast;
|
||||||
|
|
@ -198,7 +221,7 @@ export function transpiler(input, options = {}) {
|
||||||
if (!emitMiniLocations) {
|
if (!emitMiniLocations) {
|
||||||
return { output };
|
return { output };
|
||||||
}
|
}
|
||||||
return { output, miniLocations, widgets, sliders };
|
return { output, miniLocations, widgets, sliders, labels };
|
||||||
}
|
}
|
||||||
|
|
||||||
function isStringWithDoubleQuotes(node, locations, code) {
|
function isStringWithDoubleQuotes(node, locations, code) {
|
||||||
|
|
@ -301,6 +324,10 @@ function isBareSamplesCall(node, parent) {
|
||||||
return node.type === 'CallExpression' && node.callee.name === 'samples' && parent.type !== 'AwaitExpression';
|
return node.type === 'CallExpression' && node.callee.name === 'samples' && parent.type !== 'AwaitExpression';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isAllCall(node) {
|
||||||
|
return node.type === 'CallExpression' && node.callee.name === 'all';
|
||||||
|
}
|
||||||
|
|
||||||
function withAwait(node) {
|
function withAwait(node) {
|
||||||
return {
|
return {
|
||||||
type: 'AwaitExpression',
|
type: 'AwaitExpression',
|
||||||
|
|
@ -401,6 +428,46 @@ function languageWithLocation(name, value, offset) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List of non-inline widgets that need cleanup
|
||||||
|
// These are Pattern.prototype methods that create persistent visualizations
|
||||||
|
// (should be repalced by a function call producing an actual list of registered widgets)
|
||||||
|
const nonInlineWidgets = ['punchcard', 'spiral', 'scope', 'pitchwheel', 'spectrum', 'pianoroll', 'wordfall'];
|
||||||
|
|
||||||
|
function isVisualizerCall(node) {
|
||||||
|
if (
|
||||||
|
node.type === 'CallExpression' &&
|
||||||
|
node.callee.type === 'MemberExpression' &&
|
||||||
|
nonInlineWidgets.includes(node.callee.property?.name)
|
||||||
|
) {
|
||||||
|
return node.callee.property.name;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findVisualizerInSubtree(node) {
|
||||||
|
if (!node || typeof node !== 'object') return null;
|
||||||
|
|
||||||
|
// Check if this node is a visualizer call
|
||||||
|
const viz = isVisualizerCall(node);
|
||||||
|
if (viz) return viz;
|
||||||
|
|
||||||
|
// Recursively search children
|
||||||
|
for (const key of Object.keys(node)) {
|
||||||
|
if (key === 'parent') continue; // Skip parent references to avoid cycles
|
||||||
|
const child = node[key];
|
||||||
|
if (Array.isArray(child)) {
|
||||||
|
for (const item of child) {
|
||||||
|
const found = findVisualizerInSubtree(item);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
} else if (child && typeof child === 'object' && child.type) {
|
||||||
|
const found = findVisualizerInSubtree(child);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Creates AST nodes for: userDefinedKeys.add('name'); strudelScope.name = name; globalThis.name = name;
|
// Creates AST nodes for: userDefinedKeys.add('name'); strudelScope.name = name; globalThis.name = name;
|
||||||
// Used in block-based evaluation to persist variables/functions across blocks
|
// Used in block-based evaluation to persist variables/functions across blocks
|
||||||
// We add to both strudelScope (for internal lookups) and globalThis (for direct access)
|
// We add to both strudelScope (for internal lookups) and globalThis (for direct access)
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ const skippedExamples = [
|
||||||
'accelerationX',
|
'accelerationX',
|
||||||
'defaultmidimap',
|
'defaultmidimap',
|
||||||
'midimaps',
|
'midimaps',
|
||||||
|
'clearScope'
|
||||||
];
|
];
|
||||||
|
|
||||||
describe('runs examples', () => {
|
describe('runs examples', () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue