p5 2.2.3 → 2.3.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/accessibility/color_namer.js +9 -11
- package/dist/accessibility/describe.js +0 -1
- package/dist/accessibility/gridOutput.js +0 -1
- package/dist/accessibility/index.js +9 -10
- package/dist/accessibility/outputs.js +0 -1
- package/dist/accessibility/textOutput.js +0 -1
- package/dist/app.js +11 -10
- package/dist/app.node.js +122 -0
- package/dist/color/color_conversion.js +9 -11
- package/dist/color/creating_reading.js +1 -1
- package/dist/color/index.js +2 -2
- package/dist/color/p5.Color.js +1 -1
- package/dist/color/setting.js +25 -12
- package/dist/{constants-BdTiYOQI.js → constants-CYF6mp5_.js} +2 -2
- package/dist/core/States.js +1 -1
- package/dist/core/constants.js +1 -1
- package/dist/core/environment.js +28 -29
- package/dist/core/filterShaders.js +1 -1
- package/dist/core/friendly_errors/fes_core.js +9 -8
- package/dist/core/friendly_errors/file_errors.js +1 -2
- package/dist/core/friendly_errors/index.js +1 -1
- package/dist/core/friendly_errors/param_validator.js +737 -640
- package/dist/core/friendly_errors/sketch_verifier.js +1 -1
- package/dist/core/friendly_errors/stacktrace.js +0 -1
- package/dist/core/helpers.js +3 -4
- package/dist/core/init.js +24 -21
- package/dist/core/internationalization.js +1 -1
- package/dist/core/legacy.js +9 -11
- package/dist/core/main.js +9 -10
- package/dist/core/p5.Graphics.js +5 -5
- package/dist/core/p5.Renderer.js +3 -3
- package/dist/core/p5.Renderer2D.js +9 -10
- package/dist/core/p5.Renderer3D.js +5 -5
- package/dist/core/rendering.js +5 -5
- package/dist/core/structure.js +0 -1
- package/dist/core/transform.js +7 -16
- package/dist/{creating_reading-C7hu6sg1.js → creating_reading-DLkHH80h.js} +11 -8
- package/dist/data/local_storage.js +0 -1
- package/dist/dom/dom.js +2 -3
- package/dist/dom/index.js +2 -2
- package/dist/dom/p5.Element.js +2 -2
- package/dist/dom/p5.MediaElement.js +2 -2
- package/dist/events/acceleration.js +5 -3
- package/dist/events/keyboard.js +0 -1
- package/dist/events/pointer.js +0 -2
- package/dist/image/const.js +1 -1
- package/dist/image/filterRenderer2D.js +19 -12
- package/dist/image/image.js +5 -5
- package/dist/image/index.js +5 -5
- package/dist/image/loading_displaying.js +5 -5
- package/dist/image/p5.Image.js +3 -3
- package/dist/image/pixels.js +0 -1
- package/dist/io/files.js +5 -5
- package/dist/io/index.js +5 -5
- package/dist/io/p5.Table.js +0 -1
- package/dist/io/p5.TableRow.js +0 -1
- package/dist/io/p5.XML.js +0 -1
- package/dist/{ir_builders-Cd6rU9Vm.js → ir_builders-C2ebb6Lu.js} +234 -1
- package/dist/{main-H_nu4eDs.js → main-D2MtO721.js} +107 -136
- package/dist/math/Matrices/Matrix.js +1 -1
- package/dist/math/Matrices/MatrixNumjs.js +1 -1
- package/dist/math/calculation.js +0 -1
- package/dist/math/index.js +3 -1
- package/dist/math/math.js +3 -17
- package/dist/math/noise.js +0 -1
- package/dist/math/p5.Matrix.js +1 -2
- package/dist/math/p5.Vector.js +233 -279
- package/dist/math/patch-vector.js +75 -0
- package/dist/math/random.js +0 -1
- package/dist/math/trigonometry.js +3 -4
- package/dist/{p5.Renderer-BmD2P6Wv.js → p5.Renderer-C0Kzy71d.js} +31 -24
- package/dist/{rendering-CC8JNTwG.js → rendering-CvNr0bB8.js} +732 -44
- package/dist/shape/2d_primitives.js +1 -4
- package/dist/shape/attributes.js +43 -8
- package/dist/shape/curves.js +0 -1
- package/dist/shape/custom_shapes.js +260 -5
- package/dist/shape/index.js +2 -2
- package/dist/shape/vertex.js +0 -2
- package/dist/strands/ir_builders.js +1 -1
- package/dist/strands/ir_types.js +5 -1
- package/dist/strands/p5.strands.js +286 -31
- package/dist/strands/strands_api.js +179 -8
- package/dist/strands/strands_codegen.js +26 -8
- package/dist/strands/strands_conditionals.js +1 -1
- package/dist/strands/strands_for.js +1 -1
- package/dist/strands/strands_node.js +1 -1
- package/dist/strands/strands_ternary.js +56 -0
- package/dist/strands/strands_transpiler.js +416 -251
- package/dist/strands_glslBackend-i-ReKgZo.js +423 -0
- package/dist/type/index.js +3 -3
- package/dist/type/lib/Typr.js +1 -1
- package/dist/type/p5.Font.js +3 -3
- package/dist/type/textCore.js +31 -24
- package/dist/utilities/conversion.js +0 -1
- package/dist/utilities/time_date.js +0 -1
- package/dist/utilities/utility_functions.js +0 -1
- package/dist/webgl/3d_primitives.js +5 -5
- package/dist/webgl/GeometryBuilder.js +1 -1
- package/dist/webgl/ShapeBuilder.js +26 -1
- package/dist/webgl/enums.js +1 -1
- package/dist/webgl/index.js +8 -9
- package/dist/webgl/interaction.js +8 -4
- package/dist/webgl/light.js +5 -5
- package/dist/webgl/loading.js +60 -21
- package/dist/webgl/material.js +5 -5
- package/dist/webgl/p5.Camera.js +5 -5
- package/dist/webgl/p5.Framebuffer.js +5 -5
- package/dist/webgl/p5.Geometry.js +3 -5
- package/dist/webgl/p5.Quat.js +1 -1
- package/dist/webgl/p5.RendererGL.js +17 -21
- package/dist/webgl/p5.Shader.js +129 -36
- package/dist/webgl/p5.Texture.js +5 -5
- package/dist/webgl/strands_glslBackend.js +5 -386
- package/dist/webgl/text.js +5 -5
- package/dist/webgl/utils.js +5 -5
- package/dist/webgl2Compatibility-DA7DLMuq.js +7 -0
- package/dist/webgpu/index.js +7 -3
- package/dist/webgpu/p5.RendererWebGPU.js +1036 -180
- package/dist/webgpu/shaders/color.js +1 -1
- package/dist/webgpu/shaders/compute.js +32 -0
- package/dist/webgpu/shaders/functions/randomComputeWGSL.js +31 -0
- package/dist/webgpu/shaders/functions/randomVertWGSL.js +30 -0
- package/dist/webgpu/shaders/functions/randomWGSL.js +30 -0
- package/dist/webgpu/shaders/line.js +1 -1
- package/dist/webgpu/shaders/material.js +3 -3
- package/dist/webgpu/strands_wgslBackend.js +137 -15
- package/lib/p5.esm.js +4088 -1950
- package/lib/p5.esm.min.js +1 -1
- package/lib/p5.js +4088 -1950
- package/lib/p5.min.js +1 -1
- package/lib/p5.webgpu.esm.js +1638 -306
- package/lib/p5.webgpu.js +1637 -305
- package/lib/p5.webgpu.min.js +1 -1
- package/package.json +6 -1
- package/types/global.d.ts +4137 -2396
- package/types/p5.d.ts +2702 -1658
- package/dist/noise3DGLSL-Bwrdi4gi.js +0 -9
|
@@ -2,6 +2,7 @@ import { parse } from 'acorn';
|
|
|
2
2
|
import { ancestor, recursive } from 'acorn-walk';
|
|
3
3
|
import escodegen from 'escodegen';
|
|
4
4
|
import { UnarySymbolToName } from './ir_types.js';
|
|
5
|
+
import { internalError } from './strands_FES.js';
|
|
5
6
|
|
|
6
7
|
let blockVarCounter = 0;
|
|
7
8
|
let loopVarCounter = 0;
|
|
@@ -41,6 +42,50 @@ function nodeIsUniform(ancestor) {
|
|
|
41
42
|
);
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
function nodeIsUniformCallbackFn(node, names) {
|
|
46
|
+
if (!names?.size) return false;
|
|
47
|
+
if (node.type === 'FunctionDeclaration' && names.has(node.id?.name)) return true;
|
|
48
|
+
if (
|
|
49
|
+
node.type === 'VariableDeclarator' && names.has(node.id?.name) &&
|
|
50
|
+
(node.init?.type === 'FunctionExpression' || node.init?.type === 'ArrowFunctionExpression')
|
|
51
|
+
) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function collectUniformCallbackNames(ast) {
|
|
58
|
+
// Sub-pass 1: collect all named function definitions
|
|
59
|
+
const namedFunctions = new Set();
|
|
60
|
+
ancestor(ast, {
|
|
61
|
+
FunctionDeclaration(node) {
|
|
62
|
+
if (node.id) namedFunctions.add(node.id.name);
|
|
63
|
+
},
|
|
64
|
+
VariableDeclarator(node) {
|
|
65
|
+
if (
|
|
66
|
+
node.id?.type === 'Identifier' &&
|
|
67
|
+
(node.init?.type === 'FunctionExpression' || node.init?.type === 'ArrowFunctionExpression')
|
|
68
|
+
) {
|
|
69
|
+
namedFunctions.add(node.id.name);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
// Sub-pass 2: find which of those names are passed as uniform call arguments
|
|
74
|
+
const names = new Set();
|
|
75
|
+
ancestor(ast, {
|
|
76
|
+
CallExpression(node) {
|
|
77
|
+
if (nodeIsUniform(node)) {
|
|
78
|
+
for (const arg of node.arguments) {
|
|
79
|
+
if (arg.type === 'Identifier' && namedFunctions.has(arg.name)) {
|
|
80
|
+
names.add(arg.name);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return names;
|
|
87
|
+
}
|
|
88
|
+
|
|
44
89
|
function nodeIsVarying(node) {
|
|
45
90
|
return node && node.type === 'CallExpression'
|
|
46
91
|
&& (
|
|
@@ -55,7 +100,64 @@ function nodeIsVarying(node) {
|
|
|
55
100
|
)
|
|
56
101
|
);
|
|
57
102
|
}
|
|
103
|
+
// Convert static member expressions into dotted paths such as
|
|
104
|
+
// `loopProtect.protect` so loop-protection calls can be matched reliably.
|
|
105
|
+
function getMemberExpressionPath(node) {
|
|
106
|
+
if (node?.type === 'Identifier') return node.name;
|
|
107
|
+
|
|
108
|
+
// Computed properties like `obj[prop]` are not safe to match as fixed paths.
|
|
109
|
+
if (node?.type !== 'MemberExpression' || node.computed) return null;
|
|
110
|
+
|
|
111
|
+
const objectPath = getMemberExpressionPath(node.object);
|
|
112
|
+
const propertyName = node.property?.name;
|
|
113
|
+
|
|
114
|
+
return objectPath && propertyName
|
|
115
|
+
? `${objectPath}.${propertyName}`
|
|
116
|
+
: null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Detect calls added by loop protection before Strands tries to transpile them.
|
|
120
|
+
function isLoopProtectionCall(node) {
|
|
121
|
+
if (node?.type !== 'CallExpression') return false;
|
|
122
|
+
|
|
123
|
+
const path = getMemberExpressionPath(node.callee);
|
|
124
|
+
|
|
125
|
+
if (!path) return false;
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
path === 'loopProtect.protect' ||
|
|
129
|
+
path.endsWith('.loopProtect') ||
|
|
130
|
+
path.endsWith('.loopProtect.protect')
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Scan AST for loop-protection injection and throw with `// noprotect` hint.
|
|
135
|
+
function throwIfLoopProtectionInserted(ast) {
|
|
136
|
+
let found = false;
|
|
137
|
+
|
|
138
|
+
ancestor(ast, {
|
|
139
|
+
CallExpression(node) {
|
|
140
|
+
if (isLoopProtectionCall(node)) {
|
|
141
|
+
found = true;
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
LogicalExpression(node) {
|
|
145
|
+
// Loop protection may appear as the right side of a short-circuit check.
|
|
146
|
+
if (
|
|
147
|
+
node.right?.type === 'CallExpression' &&
|
|
148
|
+
isLoopProtectionCall(node.right)
|
|
149
|
+
) {
|
|
150
|
+
found = true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
58
154
|
|
|
155
|
+
if (found) {
|
|
156
|
+
internalError(
|
|
157
|
+
'loop protection error Loop protection code detected. Add `// noprotect` at the top of your sketch and run again.'
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
59
161
|
// Helper function to check if a statement is a variable declaration with strands control flow init
|
|
60
162
|
function statementContainsStrandsControlFlow(stmt) {
|
|
61
163
|
// Check for variable declarations with strands control flow init
|
|
@@ -192,9 +294,137 @@ function replaceReferences(node, tempVarMap) {
|
|
|
192
294
|
internalReplaceReferences(node);
|
|
193
295
|
}
|
|
194
296
|
|
|
297
|
+
function replaceIdentifierReferences(node, oldName, newName) {
|
|
298
|
+
if (!node || typeof node !== 'object') return node;
|
|
299
|
+
|
|
300
|
+
const replaceInNode = (n) => {
|
|
301
|
+
if (!n || typeof n !== 'object') return n;
|
|
302
|
+
if (n.type === 'Identifier' && n.name === oldName) {
|
|
303
|
+
return { ...n, name: newName };
|
|
304
|
+
}
|
|
305
|
+
const newNode = { ...n };
|
|
306
|
+
for (const key in n) {
|
|
307
|
+
if (n.hasOwnProperty(key) && key !== 'parent') {
|
|
308
|
+
if (Array.isArray(n[key])) {
|
|
309
|
+
newNode[key] = n[key].map(replaceInNode);
|
|
310
|
+
} else if (typeof n[key] === 'object') {
|
|
311
|
+
newNode[key] = replaceInNode(n[key]);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return newNode;
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
return replaceInNode(node);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Shared handler for both BinaryExpression and LogicalExpression —
|
|
322
|
+
// both follow the same operator-to-method-call transformation pattern.
|
|
323
|
+
function transformBinaryOrLogical(node, state, ancestors) {
|
|
324
|
+
if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const unsafeTypes = ['Literal', 'ArrayExpression', 'Identifier'];
|
|
328
|
+
if (unsafeTypes.includes(node.left.type)) {
|
|
329
|
+
node.left = {
|
|
330
|
+
type: 'CallExpression',
|
|
331
|
+
callee: {
|
|
332
|
+
type: 'Identifier',
|
|
333
|
+
name: '__p5.strandsNode',
|
|
334
|
+
},
|
|
335
|
+
arguments: [node.left]
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
node.type = 'CallExpression';
|
|
339
|
+
node.callee = {
|
|
340
|
+
type: 'MemberExpression',
|
|
341
|
+
object: node.left,
|
|
342
|
+
property: {
|
|
343
|
+
type: 'Identifier',
|
|
344
|
+
name: replaceBinaryOperator(node.operator),
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
node.arguments = [node.right];
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Shared helper used by both IfStatement and ForStatement handlers.
|
|
351
|
+
// Adds temp variable copies, replaces references, and appends a return
|
|
352
|
+
// statement to a branch/loop function body.
|
|
353
|
+
// sourcePrefix: the root identifier to read from ('vars' for loops,
|
|
354
|
+
// null for if-branches where we read directly from the outer variable).
|
|
355
|
+
function addCopyingAndReturn(functionBody, varsToReturn, sourcePrefix = null) {
|
|
356
|
+
if (functionBody.type !== 'BlockStatement') return;
|
|
357
|
+
|
|
358
|
+
const tempVarMap = new Map();
|
|
359
|
+
const copyStatements = [];
|
|
360
|
+
|
|
361
|
+
for (const varPath of varsToReturn) {
|
|
362
|
+
const parts = varPath.split('.');
|
|
363
|
+
const tempName = `__copy_${parts.join('_')}_${blockVarCounter++}`;
|
|
364
|
+
tempVarMap.set(varPath, tempName);
|
|
365
|
+
|
|
366
|
+
// If sourcePrefix is set (loop case), read from vars.x.y
|
|
367
|
+
// Otherwise (if-branch case), read directly from x.y
|
|
368
|
+
let sourceExpr = sourcePrefix
|
|
369
|
+
? { type: 'Identifier', name: sourcePrefix }
|
|
370
|
+
: { type: 'Identifier', name: parts[0] };
|
|
371
|
+
|
|
372
|
+
const pathParts = sourcePrefix ? parts : parts.slice(1);
|
|
373
|
+
for (const part of pathParts) {
|
|
374
|
+
sourceExpr = {
|
|
375
|
+
type: 'MemberExpression',
|
|
376
|
+
object: sourceExpr,
|
|
377
|
+
property: { type: 'Identifier', name: part },
|
|
378
|
+
computed: false
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
copyStatements.push({
|
|
383
|
+
type: 'VariableDeclaration',
|
|
384
|
+
declarations: [{
|
|
385
|
+
type: 'VariableDeclarator',
|
|
386
|
+
id: { type: 'Identifier', name: tempName },
|
|
387
|
+
init: {
|
|
388
|
+
type: 'CallExpression',
|
|
389
|
+
callee: {
|
|
390
|
+
type: 'MemberExpression',
|
|
391
|
+
object: sourceExpr,
|
|
392
|
+
property: { type: 'Identifier', name: 'copy' },
|
|
393
|
+
computed: false
|
|
394
|
+
},
|
|
395
|
+
arguments: []
|
|
396
|
+
}
|
|
397
|
+
}],
|
|
398
|
+
kind: 'let'
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
functionBody.body.forEach(node => replaceReferences(node, tempVarMap));
|
|
403
|
+
functionBody.body.unshift(...copyStatements);
|
|
404
|
+
|
|
405
|
+
const returnObj = {
|
|
406
|
+
type: 'ObjectExpression',
|
|
407
|
+
properties: Array.from(varsToReturn).map(varPath => ({
|
|
408
|
+
type: 'Property',
|
|
409
|
+
key: { type: 'Literal', value: varPath },
|
|
410
|
+
value: { type: 'Identifier', name: tempVarMap.get(varPath) },
|
|
411
|
+
kind: 'init',
|
|
412
|
+
computed: false,
|
|
413
|
+
shorthand: false
|
|
414
|
+
}))
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
functionBody.body.push({
|
|
418
|
+
type: 'ReturnStatement',
|
|
419
|
+
argument: returnObj
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
195
423
|
const ASTCallbacks = {
|
|
196
|
-
UnaryExpression(node,
|
|
197
|
-
if (ancestors.some(nodeIsUniform)) {
|
|
424
|
+
UnaryExpression(node, state, ancestors) {
|
|
425
|
+
if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
198
428
|
const unaryFnName = UnarySymbolToName[node.operator];
|
|
199
429
|
const standardReplacement = (node) => {
|
|
200
430
|
node.type = 'CallExpression';
|
|
@@ -213,7 +443,7 @@ const ASTCallbacks = {
|
|
|
213
443
|
];
|
|
214
444
|
let isSwizzle = swizzleSets.some(set =>
|
|
215
445
|
[...property].every(char => set.includes(char))
|
|
216
|
-
) && node.argument.type === 'MemberExpression';
|
|
446
|
+
) && node.argument.type === 'MemberExpression' && !node.argument.computed;
|
|
217
447
|
if (isSwizzle) {
|
|
218
448
|
node.type = 'MemberExpression';
|
|
219
449
|
node.object = {
|
|
@@ -237,8 +467,10 @@ const ASTCallbacks = {
|
|
|
237
467
|
delete node.argument;
|
|
238
468
|
delete node.operator;
|
|
239
469
|
},
|
|
240
|
-
BreakStatement(node,
|
|
241
|
-
if (ancestors.some(nodeIsUniform)) {
|
|
470
|
+
BreakStatement(node, state, ancestors) {
|
|
471
|
+
if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
242
474
|
node.callee = {
|
|
243
475
|
type: 'Identifier',
|
|
244
476
|
name: '__p5.break'
|
|
@@ -246,8 +478,39 @@ const ASTCallbacks = {
|
|
|
246
478
|
node.arguments = [];
|
|
247
479
|
node.type = 'CallExpression';
|
|
248
480
|
},
|
|
249
|
-
|
|
250
|
-
if (ancestors.some(nodeIsUniform)) {
|
|
481
|
+
MemberExpression(node, state, ancestors) {
|
|
482
|
+
if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
// Skip sets -- these will be converted to .set() method
|
|
486
|
+
// calls at the AssignmentExpression level
|
|
487
|
+
if (
|
|
488
|
+
ancestors.at(-2)?.type === 'AssignmentExpression' &&
|
|
489
|
+
ancestors.at(-2).left === node
|
|
490
|
+
) {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
if (node.computed) {
|
|
494
|
+
const callee = node.object;
|
|
495
|
+
const member = node.property;
|
|
496
|
+
node.computed = undefined;
|
|
497
|
+
node.object = undefined;
|
|
498
|
+
node.callee = {
|
|
499
|
+
type: 'MemberExpression',
|
|
500
|
+
object: callee,
|
|
501
|
+
property: {
|
|
502
|
+
type: 'Identifier',
|
|
503
|
+
name: 'get',
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
node.arguments = [member];
|
|
507
|
+
node.type = 'CallExpression';
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
VariableDeclarator(node, state, ancestors) {
|
|
511
|
+
if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
251
514
|
if (nodeIsUniform(node.init)) {
|
|
252
515
|
// Only inject the variable name if the first argument isn't already a string
|
|
253
516
|
if (node.init.arguments.length === 0 ||
|
|
@@ -272,16 +535,18 @@ const ASTCallbacks = {
|
|
|
272
535
|
value: node.id.name
|
|
273
536
|
};
|
|
274
537
|
node.init.arguments.unshift(varyingNameLiteral);
|
|
275
|
-
|
|
538
|
+
state.varyings[node.id.name] = varyingNameLiteral;
|
|
276
539
|
} else {
|
|
277
540
|
// Still track it as a varying even if name wasn't injected
|
|
278
|
-
|
|
541
|
+
state.varyings[node.id.name] = node.init.arguments[0];
|
|
279
542
|
}
|
|
280
543
|
}
|
|
281
544
|
},
|
|
282
|
-
Identifier(node,
|
|
283
|
-
if (ancestors.some(nodeIsUniform)) {
|
|
284
|
-
|
|
545
|
+
Identifier(node, state, ancestors) {
|
|
546
|
+
if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) {
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
if (state.varyings[node.name]
|
|
285
550
|
&& !ancestors.some(a => a.type === 'AssignmentExpression' && a.left === node)
|
|
286
551
|
) {
|
|
287
552
|
node.type = 'CallExpression';
|
|
@@ -301,8 +566,10 @@ const ASTCallbacks = {
|
|
|
301
566
|
},
|
|
302
567
|
// The callbacks for AssignmentExpression and BinaryExpression handle
|
|
303
568
|
// operator overloading including +=, *= assignment expressions
|
|
304
|
-
ArrayExpression(node,
|
|
305
|
-
if (ancestors.some(nodeIsUniform)) {
|
|
569
|
+
ArrayExpression(node, state, ancestors) {
|
|
570
|
+
if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
306
573
|
const original = JSON.parse(JSON.stringify(node));
|
|
307
574
|
node.type = 'CallExpression';
|
|
308
575
|
node.callee = {
|
|
@@ -311,8 +578,10 @@ const ASTCallbacks = {
|
|
|
311
578
|
};
|
|
312
579
|
node.arguments = [original];
|
|
313
580
|
},
|
|
314
|
-
AssignmentExpression(node,
|
|
315
|
-
if (ancestors.some(nodeIsUniform)) {
|
|
581
|
+
AssignmentExpression(node, state, ancestors) {
|
|
582
|
+
if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
316
585
|
const unsafeTypes = ['Literal', 'ArrayExpression', 'Identifier'];
|
|
317
586
|
if (node.operator !== '=') {
|
|
318
587
|
const methodName = replaceBinaryOperator(node.operator.replace('=',''));
|
|
@@ -341,7 +610,7 @@ const ASTCallbacks = {
|
|
|
341
610
|
node.right = rightReplacementNode;
|
|
342
611
|
}
|
|
343
612
|
// Handle direct varying variable assignment: myVarying = value
|
|
344
|
-
if (
|
|
613
|
+
if (state.varyings[node.left.name]) {
|
|
345
614
|
node.type = 'ExpressionStatement';
|
|
346
615
|
node.expression = {
|
|
347
616
|
type: 'CallExpression',
|
|
@@ -362,10 +631,31 @@ const ASTCallbacks = {
|
|
|
362
631
|
// Handle swizzle assignment to varying variable: myVarying.xyz = value
|
|
363
632
|
// Note: node.left.object might be worldPos.getValue() due to prior Identifier transformation
|
|
364
633
|
else if (node.left.type === 'MemberExpression') {
|
|
634
|
+
if (node.left.computed) {
|
|
635
|
+
const source = node.left;
|
|
636
|
+
const value = node.right;
|
|
637
|
+
const callee = source.object;
|
|
638
|
+
const member = source.property;
|
|
639
|
+
node.right = undefined;
|
|
640
|
+
node.left = undefined;
|
|
641
|
+
node.operator = undefined;
|
|
642
|
+
node.callee = {
|
|
643
|
+
type: 'MemberExpression',
|
|
644
|
+
object: callee,
|
|
645
|
+
property: {
|
|
646
|
+
type: 'Identifier',
|
|
647
|
+
name: 'set'
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
node.arguments = [member, value];
|
|
651
|
+
node.type = 'CallExpression';
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
|
|
365
655
|
let varyingName = null;
|
|
366
656
|
|
|
367
657
|
// Check if it's a direct identifier: myVarying.xyz
|
|
368
|
-
if (node.left.object.type === 'Identifier' &&
|
|
658
|
+
if (node.left.object.type === 'Identifier' && state.varyings[node.left.object.name]) {
|
|
369
659
|
varyingName = node.left.object.name;
|
|
370
660
|
}
|
|
371
661
|
// Check if it's a getValue() call: myVarying.getValue().xyz
|
|
@@ -373,7 +663,7 @@ const ASTCallbacks = {
|
|
|
373
663
|
node.left.object.callee?.type === 'MemberExpression' &&
|
|
374
664
|
node.left.object.callee.property?.name === 'getValue' &&
|
|
375
665
|
node.left.object.callee.object?.type === 'Identifier' &&
|
|
376
|
-
|
|
666
|
+
state.varyings[node.left.object.callee.object.name]) {
|
|
377
667
|
varyingName = node.left.object.callee.object.name;
|
|
378
668
|
}
|
|
379
669
|
|
|
@@ -404,70 +694,30 @@ const ASTCallbacks = {
|
|
|
404
694
|
}
|
|
405
695
|
}
|
|
406
696
|
},
|
|
407
|
-
BinaryExpression
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (unsafeTypes.includes(node.left.type)) {
|
|
415
|
-
const leftReplacementNode = {
|
|
416
|
-
type: 'CallExpression',
|
|
417
|
-
callee: {
|
|
418
|
-
type: 'Identifier',
|
|
419
|
-
name: '__p5.strandsNode',
|
|
420
|
-
},
|
|
421
|
-
arguments: [node.left]
|
|
422
|
-
};
|
|
423
|
-
node.left = leftReplacementNode;
|
|
697
|
+
BinaryExpression: transformBinaryOrLogical,
|
|
698
|
+
LogicalExpression: transformBinaryOrLogical,
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
ConditionalExpression(node, state, ancestors) {
|
|
702
|
+
if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) {
|
|
703
|
+
return;
|
|
424
704
|
}
|
|
425
|
-
//
|
|
426
|
-
//
|
|
705
|
+
// Transform condition ? consequent : alternate
|
|
706
|
+
// into __p5.strandsTernary(condition, consequent, alternate)
|
|
707
|
+
const test = node.test;
|
|
708
|
+
const consequent = node.consequent;
|
|
709
|
+
const alternate = node.alternate;
|
|
427
710
|
node.type = 'CallExpression';
|
|
428
|
-
node.callee = {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
name: replaceBinaryOperator(node.operator),
|
|
434
|
-
},
|
|
435
|
-
};
|
|
436
|
-
node.arguments = [node.right];
|
|
711
|
+
node.callee = { type: 'Identifier', name: '__p5.strandsTernary' };
|
|
712
|
+
node.arguments = [test, consequent, alternate];
|
|
713
|
+
delete node.test;
|
|
714
|
+
delete node.consequent;
|
|
715
|
+
delete node.alternate;
|
|
437
716
|
},
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
if (ancestors.some(nodeIsUniform)) { return; }
|
|
442
|
-
// If the left hand side of an expression is one of these types,
|
|
443
|
-
// we should construct a node from it.
|
|
444
|
-
const unsafeTypes = ['Literal', 'ArrayExpression', 'Identifier'];
|
|
445
|
-
if (unsafeTypes.includes(node.left.type)) {
|
|
446
|
-
const leftReplacementNode = {
|
|
447
|
-
type: 'CallExpression',
|
|
448
|
-
callee: {
|
|
449
|
-
type: 'Identifier',
|
|
450
|
-
name: '__p5.strandsNode',
|
|
451
|
-
},
|
|
452
|
-
arguments: [node.left]
|
|
453
|
-
};
|
|
454
|
-
node.left = leftReplacementNode;
|
|
717
|
+
IfStatement(node, state, ancestors) {
|
|
718
|
+
if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) {
|
|
719
|
+
return;
|
|
455
720
|
}
|
|
456
|
-
// Replace the logical operator with a call expression
|
|
457
|
-
// in other words a call to BaseNode.or(), .and() etc.
|
|
458
|
-
node.type = 'CallExpression';
|
|
459
|
-
node.callee = {
|
|
460
|
-
type: 'MemberExpression',
|
|
461
|
-
object: node.left,
|
|
462
|
-
property: {
|
|
463
|
-
type: 'Identifier',
|
|
464
|
-
name: replaceBinaryOperator(node.operator),
|
|
465
|
-
},
|
|
466
|
-
};
|
|
467
|
-
node.arguments = [node.right];
|
|
468
|
-
},
|
|
469
|
-
IfStatement(node, _state, ancestors) {
|
|
470
|
-
if (ancestors.some(nodeIsUniform)) { return; }
|
|
471
721
|
// Transform if statement into strandsIf() call
|
|
472
722
|
// The condition is evaluated directly, not wrapped in a function
|
|
473
723
|
const condition = node.test;
|
|
@@ -571,70 +821,6 @@ const ASTCallbacks = {
|
|
|
571
821
|
analyzeBranch(thenFunction.body);
|
|
572
822
|
analyzeBranch(elseFunction.body);
|
|
573
823
|
if (assignedVars.size > 0) {
|
|
574
|
-
// Add copying, reference replacement, and return statements to branch functions
|
|
575
|
-
const addCopyingAndReturn = (functionBody, varsToReturn) => {
|
|
576
|
-
if (functionBody.type === 'BlockStatement') {
|
|
577
|
-
// Create temporary variables and copy statements
|
|
578
|
-
const tempVarMap = new Map(); // property path -> temp name
|
|
579
|
-
const copyStatements = [];
|
|
580
|
-
for (const varPath of varsToReturn) {
|
|
581
|
-
const parts = varPath.split('.');
|
|
582
|
-
const tempName = `__copy_${parts.join('_')}_${blockVarCounter++}`;
|
|
583
|
-
tempVarMap.set(varPath, tempName);
|
|
584
|
-
|
|
585
|
-
// Build the member expression for the property path
|
|
586
|
-
let sourceExpr = { type: 'Identifier', name: parts[0] };
|
|
587
|
-
for (let i = 1; i < parts.length; i++) {
|
|
588
|
-
sourceExpr = {
|
|
589
|
-
type: 'MemberExpression',
|
|
590
|
-
object: sourceExpr,
|
|
591
|
-
property: { type: 'Identifier', name: parts[i] },
|
|
592
|
-
computed: false
|
|
593
|
-
};
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// let tempName = propertyPath.copy()
|
|
597
|
-
copyStatements.push({
|
|
598
|
-
type: 'VariableDeclaration',
|
|
599
|
-
declarations: [{
|
|
600
|
-
type: 'VariableDeclarator',
|
|
601
|
-
id: { type: 'Identifier', name: tempName },
|
|
602
|
-
init: {
|
|
603
|
-
type: 'CallExpression',
|
|
604
|
-
callee: {
|
|
605
|
-
type: 'MemberExpression',
|
|
606
|
-
object: sourceExpr,
|
|
607
|
-
property: { type: 'Identifier', name: 'copy' },
|
|
608
|
-
computed: false
|
|
609
|
-
},
|
|
610
|
-
arguments: []
|
|
611
|
-
}
|
|
612
|
-
}],
|
|
613
|
-
kind: 'let'
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
// Apply reference replacement to all statements
|
|
617
|
-
functionBody.body.forEach(node => replaceReferences(node, tempVarMap));
|
|
618
|
-
// Insert copy statements at the beginning
|
|
619
|
-
functionBody.body.unshift(...copyStatements);
|
|
620
|
-
// Add return statement with flat object using property paths as keys
|
|
621
|
-
const returnObj = {
|
|
622
|
-
type: 'ObjectExpression',
|
|
623
|
-
properties: Array.from(varsToReturn).map(varPath => ({
|
|
624
|
-
type: 'Property',
|
|
625
|
-
key: { type: 'Literal', value: varPath },
|
|
626
|
-
value: { type: 'Identifier', name: tempVarMap.get(varPath) },
|
|
627
|
-
kind: 'init',
|
|
628
|
-
computed: false,
|
|
629
|
-
shorthand: false
|
|
630
|
-
}))
|
|
631
|
-
};
|
|
632
|
-
functionBody.body.push({
|
|
633
|
-
type: 'ReturnStatement',
|
|
634
|
-
argument: returnObj
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
};
|
|
638
824
|
addCopyingAndReturn(thenFunction.body, assignedVars);
|
|
639
825
|
addCopyingAndReturn(elseFunction.body, assignedVars);
|
|
640
826
|
// Create a block variable to capture the return value
|
|
@@ -735,8 +921,10 @@ const ASTCallbacks = {
|
|
|
735
921
|
delete node.consequent;
|
|
736
922
|
delete node.alternate;
|
|
737
923
|
},
|
|
738
|
-
UpdateExpression(node,
|
|
739
|
-
if (ancestors.some(nodeIsUniform)) {
|
|
924
|
+
UpdateExpression(node, state, ancestors) {
|
|
925
|
+
if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) {
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
740
928
|
|
|
741
929
|
// Transform ++var, var++, --var, var-- into assignment expressions
|
|
742
930
|
let operator;
|
|
@@ -767,11 +955,13 @@ const ASTCallbacks = {
|
|
|
767
955
|
// Replace the update expression with the assignment expression
|
|
768
956
|
Object.assign(node, assignmentExpr);
|
|
769
957
|
delete node.prefix;
|
|
770
|
-
|
|
771
|
-
|
|
958
|
+
ASTCallbacks.BinaryExpression(node.right, state, [...ancestors, node]);
|
|
959
|
+
ASTCallbacks.AssignmentExpression(node, state, ancestors);
|
|
772
960
|
},
|
|
773
|
-
ForStatement(node,
|
|
774
|
-
if (ancestors.some(nodeIsUniform)) {
|
|
961
|
+
ForStatement(node, state, ancestors) {
|
|
962
|
+
if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, state.uniformCallbackNames))) {
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
775
965
|
|
|
776
966
|
// Transform for statement into strandsFor() call
|
|
777
967
|
// for (init; test; update) body -> strandsFor(initCb, conditionCb, updateCb, bodyCb, initialVars)
|
|
@@ -823,7 +1013,7 @@ const ASTCallbacks = {
|
|
|
823
1013
|
// Replace loop variable references with the parameter
|
|
824
1014
|
if (node.init?.type === 'VariableDeclaration') {
|
|
825
1015
|
const loopVarName = node.init.declarations[0].id.name;
|
|
826
|
-
conditionBody =
|
|
1016
|
+
conditionBody = replaceIdentifierReferences(conditionBody, loopVarName, uniqueLoopVar);
|
|
827
1017
|
}
|
|
828
1018
|
const conditionAst = { body: [{ type: 'ExpressionStatement', expression: conditionBody }] };
|
|
829
1019
|
conditionBody = conditionAst.body[0].expression;
|
|
@@ -841,7 +1031,7 @@ const ASTCallbacks = {
|
|
|
841
1031
|
// Replace loop variable references with the parameter
|
|
842
1032
|
if (node.init?.type === 'VariableDeclaration') {
|
|
843
1033
|
const loopVarName = node.init.declarations[0].id.name;
|
|
844
|
-
updateExpr =
|
|
1034
|
+
updateExpr = replaceIdentifierReferences(updateExpr, loopVarName, uniqueLoopVar);
|
|
845
1035
|
}
|
|
846
1036
|
const updateAst = { body: [{ type: 'ExpressionStatement', expression: updateExpr }] };
|
|
847
1037
|
updateExpr = updateAst.body[0].expression;
|
|
@@ -880,7 +1070,7 @@ const ASTCallbacks = {
|
|
|
880
1070
|
// Replace loop variable references in the body
|
|
881
1071
|
if (node.init?.type === 'VariableDeclaration') {
|
|
882
1072
|
const loopVarName = node.init.declarations[0].id.name;
|
|
883
|
-
bodyBlock =
|
|
1073
|
+
bodyBlock = replaceIdentifierReferences(bodyBlock, loopVarName, uniqueLoopVar);
|
|
884
1074
|
}
|
|
885
1075
|
|
|
886
1076
|
const bodyFunction = {
|
|
@@ -935,73 +1125,8 @@ const ASTCallbacks = {
|
|
|
935
1125
|
});
|
|
936
1126
|
|
|
937
1127
|
if (assignedVars.size > 0) {
|
|
938
|
-
// Add copying, reference replacement, and return statements similar to if statements
|
|
939
|
-
const addCopyingAndReturn = (functionBody, varsToReturn) => {
|
|
940
|
-
if (functionBody.type === 'BlockStatement') {
|
|
941
|
-
const tempVarMap = new Map();
|
|
942
|
-
const copyStatements = [];
|
|
943
|
-
|
|
944
|
-
for (const varPath of varsToReturn) {
|
|
945
|
-
const parts = varPath.split('.');
|
|
946
|
-
const tempName = `__copy_${parts.join('_')}_${blockVarCounter++}`;
|
|
947
|
-
tempVarMap.set(varPath, tempName);
|
|
948
|
-
|
|
949
|
-
// Build the member expression for vars.propertyPath
|
|
950
|
-
// e.g., vars.inputs.color or vars.x
|
|
951
|
-
let sourceExpr = { type: 'Identifier', name: 'vars' };
|
|
952
|
-
for (const part of parts) {
|
|
953
|
-
sourceExpr = {
|
|
954
|
-
type: 'MemberExpression',
|
|
955
|
-
object: sourceExpr,
|
|
956
|
-
property: { type: 'Identifier', name: part },
|
|
957
|
-
computed: false
|
|
958
|
-
};
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
copyStatements.push({
|
|
962
|
-
type: 'VariableDeclaration',
|
|
963
|
-
declarations: [{
|
|
964
|
-
type: 'VariableDeclarator',
|
|
965
|
-
id: { type: 'Identifier', name: tempName },
|
|
966
|
-
init: {
|
|
967
|
-
type: 'CallExpression',
|
|
968
|
-
callee: {
|
|
969
|
-
type: 'MemberExpression',
|
|
970
|
-
object: sourceExpr,
|
|
971
|
-
property: { type: 'Identifier', name: 'copy' },
|
|
972
|
-
computed: false
|
|
973
|
-
},
|
|
974
|
-
arguments: []
|
|
975
|
-
}
|
|
976
|
-
}],
|
|
977
|
-
kind: 'let'
|
|
978
|
-
});
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
functionBody.body.forEach(node => replaceReferences(node, tempVarMap));
|
|
982
|
-
functionBody.body.unshift(...copyStatements);
|
|
983
|
-
|
|
984
|
-
// Add return statement with flat object using property paths as keys
|
|
985
|
-
const returnObj = {
|
|
986
|
-
type: 'ObjectExpression',
|
|
987
|
-
properties: Array.from(varsToReturn).map(varPath => ({
|
|
988
|
-
type: 'Property',
|
|
989
|
-
key: { type: 'Literal', value: varPath },
|
|
990
|
-
value: { type: 'Identifier', name: tempVarMap.get(varPath) },
|
|
991
|
-
kind: 'init',
|
|
992
|
-
computed: false,
|
|
993
|
-
shorthand: false
|
|
994
|
-
}))
|
|
995
|
-
};
|
|
996
|
-
|
|
997
|
-
functionBody.body.push({
|
|
998
|
-
type: 'ReturnStatement',
|
|
999
|
-
argument: returnObj
|
|
1000
|
-
});
|
|
1001
|
-
}
|
|
1002
|
-
};
|
|
1003
1128
|
|
|
1004
|
-
addCopyingAndReturn(bodyFunction.body, assignedVars);
|
|
1129
|
+
addCopyingAndReturn(bodyFunction.body, assignedVars, 'vars');
|
|
1005
1130
|
|
|
1006
1131
|
// Create block variable and assignments similar to if statements
|
|
1007
1132
|
const blockVar = `__block_${blockVarCounter++}`;
|
|
@@ -1116,33 +1241,8 @@ const ASTCallbacks = {
|
|
|
1116
1241
|
delete node.update;
|
|
1117
1242
|
},
|
|
1118
1243
|
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
if (!node || typeof node !== 'object') return node;
|
|
1122
|
-
|
|
1123
|
-
const replaceInNode = (n) => {
|
|
1124
|
-
if (!n || typeof n !== 'object') return n;
|
|
1125
|
-
|
|
1126
|
-
if (n.type === 'Identifier' && n.name === oldName) {
|
|
1127
|
-
return { ...n, name: newName };
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
// Create a copy and recursively process properties
|
|
1131
|
-
const newNode = { ...n };
|
|
1132
|
-
for (const key in n) {
|
|
1133
|
-
if (n.hasOwnProperty(key) && key !== 'parent') {
|
|
1134
|
-
if (Array.isArray(n[key])) {
|
|
1135
|
-
newNode[key] = n[key].map(replaceInNode);
|
|
1136
|
-
} else if (typeof n[key] === 'object') {
|
|
1137
|
-
newNode[key] = replaceInNode(n[key]);
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
return newNode;
|
|
1142
|
-
};
|
|
1143
|
-
|
|
1144
|
-
return replaceInNode(node);
|
|
1145
|
-
}
|
|
1244
|
+
|
|
1245
|
+
|
|
1146
1246
|
};
|
|
1147
1247
|
|
|
1148
1248
|
// Helper function to check if a function body contains return statements in control flow
|
|
@@ -1477,22 +1577,31 @@ function transformFunctionSetCalls(functionNode) {
|
|
|
1477
1577
|
}
|
|
1478
1578
|
|
|
1479
1579
|
// Main transformation pass: find and transform functions with .set() calls in control flow
|
|
1480
|
-
function transformSetCallsInControlFlow(ast) {
|
|
1580
|
+
function transformSetCallsInControlFlow(ast, names) {
|
|
1481
1581
|
const functionsToTransform = [];
|
|
1482
1582
|
|
|
1483
1583
|
// Collect functions that have .set() calls in control flow
|
|
1484
1584
|
const collectFunctions = {
|
|
1485
1585
|
ArrowFunctionExpression(node, ancestors) {
|
|
1586
|
+
if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, names))) {
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1486
1589
|
if (functionHasSetInControlFlow(node)) {
|
|
1487
1590
|
functionsToTransform.push(node);
|
|
1488
1591
|
}
|
|
1489
1592
|
},
|
|
1490
1593
|
FunctionExpression(node, ancestors) {
|
|
1594
|
+
if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, names))) {
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1491
1597
|
if (functionHasSetInControlFlow(node)) {
|
|
1492
1598
|
functionsToTransform.push(node);
|
|
1493
1599
|
}
|
|
1494
1600
|
},
|
|
1495
1601
|
FunctionDeclaration(node, ancestors) {
|
|
1602
|
+
if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, names))) {
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1496
1605
|
if (functionHasSetInControlFlow(node)) {
|
|
1497
1606
|
functionsToTransform.push(node);
|
|
1498
1607
|
}
|
|
@@ -1508,12 +1617,15 @@ function transformSetCallsInControlFlow(ast) {
|
|
|
1508
1617
|
}
|
|
1509
1618
|
|
|
1510
1619
|
// Main transformation pass: find and transform helper functions with early returns
|
|
1511
|
-
function transformHelperFunctionEarlyReturns(ast) {
|
|
1620
|
+
function transformHelperFunctionEarlyReturns(ast, names) {
|
|
1512
1621
|
const helperFunctionsToTransform = [];
|
|
1513
1622
|
|
|
1514
1623
|
// Collect helper functions that need transformation
|
|
1515
1624
|
const collectHelperFunctions = {
|
|
1516
1625
|
VariableDeclarator(node, ancestors) {
|
|
1626
|
+
if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, names))) {
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1517
1629
|
const init = node.init;
|
|
1518
1630
|
if (init && (init.type === 'ArrowFunctionExpression' || init.type === 'FunctionExpression')) {
|
|
1519
1631
|
if (functionHasEarlyReturns(init)) {
|
|
@@ -1522,6 +1634,9 @@ function transformHelperFunctionEarlyReturns(ast) {
|
|
|
1522
1634
|
}
|
|
1523
1635
|
},
|
|
1524
1636
|
FunctionDeclaration(node, ancestors) {
|
|
1637
|
+
if (ancestors.some(a => nodeIsUniform(a) || nodeIsUniformCallbackFn(a, names))) {
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1525
1640
|
if (functionHasEarlyReturns(node)) {
|
|
1526
1641
|
helperFunctionsToTransform.push(node);
|
|
1527
1642
|
}
|
|
@@ -1541,6 +1656,33 @@ function transformHelperFunctionEarlyReturns(ast) {
|
|
|
1541
1656
|
}
|
|
1542
1657
|
}
|
|
1543
1658
|
|
|
1659
|
+
/**
|
|
1660
|
+
* Transpiles a p5.strands callback into executable JavaScript by applying
|
|
1661
|
+
* a multi-pass AST transformation pipeline.
|
|
1662
|
+
*
|
|
1663
|
+
* Pipeline stages:
|
|
1664
|
+
*
|
|
1665
|
+
* 1. Collect uniform callback names
|
|
1666
|
+
* - Identifies functions passed into uniform() so they are excluded from transformation
|
|
1667
|
+
*
|
|
1668
|
+
* 2. transformSetCallsInControlFlow
|
|
1669
|
+
* - Rewrites `.set()` calls inside control flow into intermediate variable assignments
|
|
1670
|
+
*
|
|
1671
|
+
* 3. Non-control-flow transformations
|
|
1672
|
+
* - Applies ASTCallbacks to transform expressions, assignments, etc.
|
|
1673
|
+
* - Skips IfStatement and ForStatement (handled later)
|
|
1674
|
+
*
|
|
1675
|
+
* 4. transformHelperFunctionEarlyReturns
|
|
1676
|
+
* - Converts early returns in helper functions into a single return value pattern
|
|
1677
|
+
*
|
|
1678
|
+
* 5. Control flow transformation (post-order)
|
|
1679
|
+
* - Transforms IfStatement → __p5.strandsIf
|
|
1680
|
+
* - Transforms ForStatement → __p5.strandsFor
|
|
1681
|
+
* - Handles variable propagation across branches/loops
|
|
1682
|
+
*
|
|
1683
|
+
* This staged approach ensures correct ordering and avoids transformation conflicts.
|
|
1684
|
+
*/
|
|
1685
|
+
|
|
1544
1686
|
function transpileStrandsToJS(p5, sourceString, srcLocations, scope) {
|
|
1545
1687
|
// Reset counters at the start of each transpilation
|
|
1546
1688
|
blockVarCounter = 0;
|
|
@@ -1551,20 +1693,43 @@ function transpileStrandsToJS(p5, sourceString, srcLocations, scope) {
|
|
|
1551
1693
|
locations: srcLocations
|
|
1552
1694
|
});
|
|
1553
1695
|
|
|
1696
|
+
throwIfLoopProtectionInserted(ast);
|
|
1697
|
+
|
|
1698
|
+
// Pre-pass: collect names of functions passed by reference as uniform callbacks
|
|
1699
|
+
const uniformCallbackNames = collectUniformCallbackNames(ast);
|
|
1700
|
+
|
|
1554
1701
|
// First pass: transform .set() calls in control flow to use intermediate variables
|
|
1555
|
-
transformSetCallsInControlFlow(ast);
|
|
1702
|
+
transformSetCallsInControlFlow(ast, uniformCallbackNames);
|
|
1556
1703
|
|
|
1557
1704
|
// Second pass: transform everything except if/for statements using normal ancestor traversal
|
|
1558
1705
|
const nonControlFlowCallbacks = { ...ASTCallbacks };
|
|
1559
1706
|
delete nonControlFlowCallbacks.IfStatement;
|
|
1560
1707
|
delete nonControlFlowCallbacks.ForStatement;
|
|
1561
|
-
ancestor(ast, nonControlFlowCallbacks, undefined, { varyings: {} });
|
|
1708
|
+
ancestor(ast, nonControlFlowCallbacks, undefined, { varyings: {}, uniformCallbackNames });
|
|
1562
1709
|
|
|
1563
1710
|
// Third pass: transform helper functions with early returns to use __returnValue pattern
|
|
1564
|
-
transformHelperFunctionEarlyReturns(ast);
|
|
1711
|
+
transformHelperFunctionEarlyReturns(ast, uniformCallbackNames);
|
|
1565
1712
|
|
|
1566
1713
|
// Fourth pass: transform if/for statements in post-order using recursive traversal
|
|
1567
1714
|
const postOrderControlFlowTransform = {
|
|
1715
|
+
CallExpression(node, state, c) {
|
|
1716
|
+
if (nodeIsUniform(node)) { return; }
|
|
1717
|
+
if (node.callee) c(node.callee, state);
|
|
1718
|
+
for (const arg of node.arguments) c(arg, state);
|
|
1719
|
+
},
|
|
1720
|
+
FunctionDeclaration(node, state, c) {
|
|
1721
|
+
if (state.uniformCallbackNames?.has(node.id?.name)) return;
|
|
1722
|
+
if (node.body) c(node.body, state);
|
|
1723
|
+
},
|
|
1724
|
+
VariableDeclarator(node, state, c) {
|
|
1725
|
+
if (
|
|
1726
|
+
state.uniformCallbackNames?.has(node.id?.name) &&
|
|
1727
|
+
(node.init?.type === 'FunctionExpression' || node.init?.type === 'ArrowFunctionExpression')
|
|
1728
|
+
) {
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
if (node.init) c(node.init, state);
|
|
1732
|
+
},
|
|
1568
1733
|
IfStatement(node, state, c) {
|
|
1569
1734
|
state.inControlFlow++;
|
|
1570
1735
|
// First recursively process children
|
|
@@ -1601,7 +1766,7 @@ function transpileStrandsToJS(p5, sourceString, srcLocations, scope) {
|
|
|
1601
1766
|
delete node.argument;
|
|
1602
1767
|
}
|
|
1603
1768
|
};
|
|
1604
|
-
recursive(ast, { varyings: {}, inControlFlow: 0 }, postOrderControlFlowTransform);
|
|
1769
|
+
recursive(ast, { varyings: {}, inControlFlow: 0, uniformCallbackNames }, postOrderControlFlowTransform);
|
|
1605
1770
|
const transpiledSource = escodegen.generate(ast);
|
|
1606
1771
|
const scopeKeys = Object.keys(scope);
|
|
1607
1772
|
const match = /\(?\s*(?:function)?\s*\w*\s*\(([^)]*)\)\s*(?:=>)?\s*{((?:.|\n)*)}\s*;?\s*\)?/
|