p5 2.2.2 → 2.2.3-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 +5 -5
- package/dist/accessibility/index.js +5 -5
- package/dist/app.js +5 -5
- package/dist/color/color_conversion.js +5 -5
- package/dist/color/index.js +1 -1
- package/dist/color/setting.js +1 -1
- package/dist/{constants-BxjhKpTv.js → constants-D3ryGa0m.js} +1 -1
- package/dist/core/constants.js +1 -1
- package/dist/core/environment.js +7 -3
- package/dist/core/filterShaders.js +1 -1
- package/dist/core/friendly_errors/fes_core.js +1 -1
- package/dist/core/friendly_errors/file_errors.js +1 -1
- package/dist/core/friendly_errors/index.js +1 -1
- package/dist/core/friendly_errors/param_validator.js +2063 -2014
- package/dist/core/friendly_errors/sketch_verifier.js +1 -1
- package/dist/core/helpers.js +1 -1
- package/dist/core/init.js +5 -5
- package/dist/core/internationalization.js +1 -1
- package/dist/core/legacy.js +5 -5
- package/dist/core/main.js +5 -5
- package/dist/core/p5.Graphics.js +3 -3
- package/dist/core/p5.Renderer.js +2 -2
- package/dist/core/p5.Renderer2D.js +5 -5
- package/dist/core/p5.Renderer3D.js +3 -3
- package/dist/core/rendering.js +3 -3
- package/dist/dom/dom.js +1 -1
- package/dist/dom/index.js +1 -1
- package/dist/dom/p5.Element.js +1 -1
- package/dist/dom/p5.MediaElement.js +11 -4
- package/dist/events/pointer.js +4 -0
- package/dist/image/const.js +1 -1
- package/dist/image/filterRenderer2D.js +4 -4
- package/dist/image/image.js +3 -3
- package/dist/image/index.js +3 -3
- package/dist/image/loading_displaying.js +3 -3
- package/dist/image/p5.Image.js +2 -2
- package/dist/io/files.js +3 -3
- package/dist/io/index.js +3 -3
- package/dist/{ir_builders-w12-GSxu.js → ir_builders-DMfaOLIL.js} +48 -8
- package/dist/{main-DDs4QOnh.js → main-CGwYa9-f.js} +126 -36
- package/dist/math/Matrices/Matrix.js +1 -1
- package/dist/math/Matrices/MatrixNumjs.js +1 -1
- package/dist/math/index.js +1 -1
- package/dist/math/p5.Matrix.js +1 -1
- package/dist/math/p5.Vector.js +1 -1
- package/dist/math/trigonometry.js +1 -1
- package/dist/{p5.Renderer-BSGddFv7.js → p5.Renderer-C0e0XesC.js} +9 -2
- package/dist/{rendering-C9g7uSQ5.js → rendering-4Z2qdE_W.js} +90 -55
- package/dist/shape/2d_primitives.js +1 -1
- package/dist/shape/attributes.js +1 -1
- package/dist/shape/custom_shapes.js +1 -1
- package/dist/shape/index.js +1 -1
- package/dist/strands/ir_builders.js +1 -1
- package/dist/strands/ir_dag.js +32 -2
- package/dist/strands/ir_types.js +18 -11
- package/dist/strands/p5.strands.js +15 -2
- package/dist/strands/strands_api.js +86 -40
- 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_phi_utils.js +27 -9
- package/dist/strands/strands_transpiler.js +1237 -831
- package/dist/type/index.js +2 -2
- package/dist/type/p5.Font.js +7 -5
- package/dist/type/textCore.js +2 -2
- package/dist/webgl/3d_primitives.js +3 -3
- package/dist/webgl/GeometryBuilder.js +1 -1
- package/dist/webgl/ShapeBuilder.js +1 -1
- package/dist/webgl/enums.js +1 -1
- package/dist/webgl/index.js +4 -4
- package/dist/webgl/interaction.js +1 -1
- package/dist/webgl/light.js +3 -3
- package/dist/webgl/loading.js +41 -35
- package/dist/webgl/material.js +3 -3
- package/dist/webgl/p5.Camera.js +3 -3
- package/dist/webgl/p5.Framebuffer.js +3 -3
- package/dist/webgl/p5.Geometry.js +1 -1
- package/dist/webgl/p5.Quat.js +1 -1
- package/dist/webgl/p5.RendererGL.js +4 -4
- package/dist/webgl/p5.Texture.js +3 -3
- package/dist/webgl/strands_glslBackend.js +1 -1
- package/dist/webgl/text.js +3 -3
- package/dist/webgl/utils.js +3 -3
- package/dist/webgpu/index.js +2 -2
- package/dist/webgpu/p5.RendererWebGPU.js +2 -2
- package/dist/webgpu/strands_wgslBackend.js +13 -4
- package/lib/p5.esm.js +3634 -2870
- package/lib/p5.esm.min.js +1 -1
- package/lib/p5.js +3634 -2870
- package/lib/p5.min.js +1 -1
- package/lib/p5.webgpu.esm.js +43 -15
- package/lib/p5.webgpu.js +43 -15
- package/lib/p5.webgpu.min.js +1 -1
- package/package.json +1 -1
- package/types/global.d.ts +805 -805
- package/types/p5.d.ts +415 -415
|
@@ -27,7 +27,7 @@ function replaceBinaryOperator(codeSource) {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
function nodeIsUniform(ancestor) {
|
|
30
|
-
return ancestor.type === 'CallExpression'
|
|
30
|
+
return ancestor && ancestor.type === 'CallExpression'
|
|
31
31
|
&& (
|
|
32
32
|
(
|
|
33
33
|
// Global mode
|
|
@@ -42,7 +42,7 @@ function nodeIsUniform(ancestor) {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
function nodeIsVarying(node) {
|
|
45
|
-
return node
|
|
45
|
+
return node && node.type === 'CallExpression'
|
|
46
46
|
&& (
|
|
47
47
|
(
|
|
48
48
|
// Global mode
|
|
@@ -282,65 +282,103 @@ const ASTCallbacks = {
|
|
|
282
282
|
Identifier(node, _state, ancestors) {
|
|
283
283
|
if (ancestors.some(nodeIsUniform)) { return; }
|
|
284
284
|
if (_state.varyings[node.name]
|
|
285
|
-
&& !ancestors.some(a => a.type === 'AssignmentExpression' && a.left === node)
|
|
286
|
-
|
|
287
|
-
|
|
285
|
+
&& !ancestors.some(a => a.type === 'AssignmentExpression' && a.left === node)
|
|
286
|
+
) {
|
|
287
|
+
node.type = 'CallExpression';
|
|
288
|
+
node.callee = {
|
|
289
|
+
type: 'MemberExpression',
|
|
290
|
+
object: {
|
|
291
|
+
type: 'Identifier',
|
|
292
|
+
name: node.name
|
|
293
|
+
},
|
|
294
|
+
property: {
|
|
295
|
+
type: 'Identifier',
|
|
296
|
+
name: 'getValue'
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
node.arguments = [];
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
// The callbacks for AssignmentExpression and BinaryExpression handle
|
|
303
|
+
// operator overloading including +=, *= assignment expressions
|
|
304
|
+
ArrayExpression(node, _state, ancestors) {
|
|
305
|
+
if (ancestors.some(nodeIsUniform)) { return; }
|
|
306
|
+
const original = JSON.parse(JSON.stringify(node));
|
|
307
|
+
node.type = 'CallExpression';
|
|
308
|
+
node.callee = {
|
|
309
|
+
type: 'Identifier',
|
|
310
|
+
name: '__p5.strandsNode',
|
|
311
|
+
};
|
|
312
|
+
node.arguments = [original];
|
|
313
|
+
},
|
|
314
|
+
AssignmentExpression(node, _state, ancestors) {
|
|
315
|
+
if (ancestors.some(nodeIsUniform)) { return; }
|
|
316
|
+
const unsafeTypes = ['Literal', 'ArrayExpression', 'Identifier'];
|
|
317
|
+
if (node.operator !== '=') {
|
|
318
|
+
const methodName = replaceBinaryOperator(node.operator.replace('=',''));
|
|
319
|
+
const rightReplacementNode = {
|
|
320
|
+
type: 'CallExpression',
|
|
321
|
+
callee: {
|
|
322
|
+
type: 'MemberExpression',
|
|
323
|
+
object: unsafeTypes.includes(node.left.type)
|
|
324
|
+
? {
|
|
325
|
+
type: 'CallExpression',
|
|
326
|
+
callee: {
|
|
327
|
+
type: 'Identifier',
|
|
328
|
+
name: '__p5.strandsNode',
|
|
329
|
+
},
|
|
330
|
+
arguments: [node.left]
|
|
331
|
+
}
|
|
332
|
+
: node.left,
|
|
333
|
+
property: {
|
|
334
|
+
type: 'Identifier',
|
|
335
|
+
name: methodName,
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
arguments: [node.right]
|
|
339
|
+
};
|
|
340
|
+
node.operator = '=';
|
|
341
|
+
node.right = rightReplacementNode;
|
|
342
|
+
}
|
|
343
|
+
// Handle direct varying variable assignment: myVarying = value
|
|
344
|
+
if (_state.varyings[node.left.name]) {
|
|
345
|
+
node.type = 'ExpressionStatement';
|
|
346
|
+
node.expression = {
|
|
347
|
+
type: 'CallExpression',
|
|
348
|
+
callee: {
|
|
288
349
|
type: 'MemberExpression',
|
|
289
350
|
object: {
|
|
290
351
|
type: 'Identifier',
|
|
291
|
-
name: node.name
|
|
352
|
+
name: node.left.name
|
|
292
353
|
},
|
|
293
354
|
property: {
|
|
294
355
|
type: 'Identifier',
|
|
295
|
-
name: '
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
},
|
|
301
|
-
// The callbacks for AssignmentExpression and BinaryExpression handle
|
|
302
|
-
// operator overloading including +=, *= assignment expressions
|
|
303
|
-
ArrayExpression(node, _state, ancestors) {
|
|
304
|
-
if (ancestors.some(nodeIsUniform)) { return; }
|
|
305
|
-
const original = JSON.parse(JSON.stringify(node));
|
|
306
|
-
node.type = 'CallExpression';
|
|
307
|
-
node.callee = {
|
|
308
|
-
type: 'Identifier',
|
|
309
|
-
name: '__p5.strandsNode',
|
|
356
|
+
name: 'bridge',
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
arguments: [node.right],
|
|
310
360
|
};
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
},
|
|
329
|
-
arguments: [node.left]
|
|
330
|
-
}
|
|
331
|
-
: node.left,
|
|
332
|
-
property: {
|
|
333
|
-
type: 'Identifier',
|
|
334
|
-
name: methodName,
|
|
335
|
-
},
|
|
336
|
-
},
|
|
337
|
-
arguments: [node.right]
|
|
338
|
-
};
|
|
339
|
-
node.operator = '=';
|
|
340
|
-
node.right = rightReplacementNode;
|
|
361
|
+
}
|
|
362
|
+
// Handle swizzle assignment to varying variable: myVarying.xyz = value
|
|
363
|
+
// Note: node.left.object might be worldPos.getValue() due to prior Identifier transformation
|
|
364
|
+
else if (node.left.type === 'MemberExpression') {
|
|
365
|
+
let varyingName = null;
|
|
366
|
+
|
|
367
|
+
// Check if it's a direct identifier: myVarying.xyz
|
|
368
|
+
if (node.left.object.type === 'Identifier' && _state.varyings[node.left.object.name]) {
|
|
369
|
+
varyingName = node.left.object.name;
|
|
370
|
+
}
|
|
371
|
+
// Check if it's a getValue() call: myVarying.getValue().xyz
|
|
372
|
+
else if (node.left.object.type === 'CallExpression' &&
|
|
373
|
+
node.left.object.callee?.type === 'MemberExpression' &&
|
|
374
|
+
node.left.object.callee.property?.name === 'getValue' &&
|
|
375
|
+
node.left.object.callee.object?.type === 'Identifier' &&
|
|
376
|
+
_state.varyings[node.left.object.callee.object.name]) {
|
|
377
|
+
varyingName = node.left.object.callee.object.name;
|
|
341
378
|
}
|
|
342
|
-
|
|
343
|
-
if (
|
|
379
|
+
|
|
380
|
+
if (varyingName) {
|
|
381
|
+
const swizzlePattern = node.left.property.name;
|
|
344
382
|
node.type = 'ExpressionStatement';
|
|
345
383
|
node.expression = {
|
|
346
384
|
type: 'CallExpression',
|
|
@@ -348,889 +386,1257 @@ const ASTCallbacks = {
|
|
|
348
386
|
type: 'MemberExpression',
|
|
349
387
|
object: {
|
|
350
388
|
type: 'Identifier',
|
|
351
|
-
name:
|
|
389
|
+
name: varyingName
|
|
352
390
|
},
|
|
353
391
|
property: {
|
|
354
392
|
type: 'Identifier',
|
|
355
|
-
name: '
|
|
393
|
+
name: 'bridgeSwizzle',
|
|
356
394
|
}
|
|
357
395
|
},
|
|
358
|
-
arguments: [
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
// Note: node.left.object might be worldPos.getValue() due to prior Identifier transformation
|
|
363
|
-
else if (node.left.type === 'MemberExpression') {
|
|
364
|
-
let varyingName = null;
|
|
365
|
-
|
|
366
|
-
// Check if it's a direct identifier: myVarying.xyz
|
|
367
|
-
if (node.left.object.type === 'Identifier' && _state.varyings[node.left.object.name]) {
|
|
368
|
-
varyingName = node.left.object.name;
|
|
369
|
-
}
|
|
370
|
-
// Check if it's a getValue() call: myVarying.getValue().xyz
|
|
371
|
-
else if (node.left.object.type === 'CallExpression' &&
|
|
372
|
-
node.left.object.callee?.type === 'MemberExpression' &&
|
|
373
|
-
node.left.object.callee.property?.name === 'getValue' &&
|
|
374
|
-
node.left.object.callee.object?.type === 'Identifier' &&
|
|
375
|
-
_state.varyings[node.left.object.callee.object.name]) {
|
|
376
|
-
varyingName = node.left.object.callee.object.name;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
if (varyingName) {
|
|
380
|
-
const swizzlePattern = node.left.property.name;
|
|
381
|
-
node.type = 'ExpressionStatement';
|
|
382
|
-
node.expression = {
|
|
383
|
-
type: 'CallExpression',
|
|
384
|
-
callee: {
|
|
385
|
-
type: 'MemberExpression',
|
|
386
|
-
object: {
|
|
387
|
-
type: 'Identifier',
|
|
388
|
-
name: varyingName
|
|
389
|
-
},
|
|
390
|
-
property: {
|
|
391
|
-
type: 'Identifier',
|
|
392
|
-
name: 'bridgeSwizzle',
|
|
393
|
-
}
|
|
396
|
+
arguments: [
|
|
397
|
+
{
|
|
398
|
+
type: 'Literal',
|
|
399
|
+
value: swizzlePattern
|
|
394
400
|
},
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
type: 'Literal',
|
|
398
|
-
value: swizzlePattern
|
|
399
|
-
},
|
|
400
|
-
node.right
|
|
401
|
-
],
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
},
|
|
406
|
-
BinaryExpression(node, _state, ancestors) {
|
|
407
|
-
// Don't convert uniform default values to node methods, as
|
|
408
|
-
// they should be evaluated at runtime, not compiled.
|
|
409
|
-
if (ancestors.some(nodeIsUniform)) { return; }
|
|
410
|
-
// If the left hand side of an expression is one of these types,
|
|
411
|
-
// we should construct a node from it.
|
|
412
|
-
const unsafeTypes = ['Literal', 'ArrayExpression', 'Identifier'];
|
|
413
|
-
if (unsafeTypes.includes(node.left.type)) {
|
|
414
|
-
const leftReplacementNode = {
|
|
415
|
-
type: 'CallExpression',
|
|
416
|
-
callee: {
|
|
417
|
-
type: 'Identifier',
|
|
418
|
-
name: '__p5.strandsNode',
|
|
419
|
-
},
|
|
420
|
-
arguments: [node.left]
|
|
401
|
+
node.right
|
|
402
|
+
],
|
|
421
403
|
};
|
|
422
|
-
node.left = leftReplacementNode;
|
|
423
404
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
BinaryExpression(node, _state, ancestors) {
|
|
408
|
+
// Don't convert uniform default values to node methods, as
|
|
409
|
+
// they should be evaluated at runtime, not compiled.
|
|
410
|
+
if (ancestors.some(nodeIsUniform)) { return; }
|
|
411
|
+
// If the left hand side of an expression is one of these types,
|
|
412
|
+
// we should construct a node from it.
|
|
413
|
+
const unsafeTypes = ['Literal', 'ArrayExpression', 'Identifier'];
|
|
414
|
+
if (unsafeTypes.includes(node.left.type)) {
|
|
415
|
+
const leftReplacementNode = {
|
|
416
|
+
type: 'CallExpression',
|
|
417
|
+
callee: {
|
|
431
418
|
type: 'Identifier',
|
|
432
|
-
name:
|
|
419
|
+
name: '__p5.strandsNode',
|
|
433
420
|
},
|
|
421
|
+
arguments: [node.left]
|
|
434
422
|
};
|
|
435
|
-
node.
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
type: '
|
|
460
|
-
|
|
461
|
-
property: {
|
|
423
|
+
node.left = leftReplacementNode;
|
|
424
|
+
}
|
|
425
|
+
// Replace the binary operator with a call expression
|
|
426
|
+
// in other words a call to BaseNode.mult(), .div() etc.
|
|
427
|
+
node.type = 'CallExpression';
|
|
428
|
+
node.callee = {
|
|
429
|
+
type: 'MemberExpression',
|
|
430
|
+
object: node.left,
|
|
431
|
+
property: {
|
|
432
|
+
type: 'Identifier',
|
|
433
|
+
name: replaceBinaryOperator(node.operator),
|
|
434
|
+
},
|
|
435
|
+
};
|
|
436
|
+
node.arguments = [node.right];
|
|
437
|
+
},
|
|
438
|
+
LogicalExpression(node, _state, ancestors) {
|
|
439
|
+
// Don't convert uniform default values to node methods, as
|
|
440
|
+
// they should be evaluated at runtime, not compiled.
|
|
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: {
|
|
462
449
|
type: 'Identifier',
|
|
463
|
-
name:
|
|
450
|
+
name: '__p5.strandsNode',
|
|
464
451
|
},
|
|
452
|
+
arguments: [node.left]
|
|
465
453
|
};
|
|
466
|
-
node.
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
454
|
+
node.left = leftReplacementNode;
|
|
455
|
+
}
|
|
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
|
+
// Transform if statement into strandsIf() call
|
|
472
|
+
// The condition is evaluated directly, not wrapped in a function
|
|
473
|
+
const condition = node.test;
|
|
474
|
+
// Create the then function
|
|
475
|
+
const thenFunction = {
|
|
476
|
+
type: 'ArrowFunctionExpression',
|
|
477
|
+
params: [],
|
|
478
|
+
body: node.consequent.type === 'BlockStatement' ? node.consequent : {
|
|
479
|
+
type: 'BlockStatement',
|
|
480
|
+
body: [node.consequent]
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
// Start building the call chain: __p5.strandsIf(condition, then)
|
|
484
|
+
let callExpression = {
|
|
485
|
+
type: 'CallExpression',
|
|
486
|
+
callee: {
|
|
487
|
+
type: 'Identifier',
|
|
488
|
+
name: '__p5.strandsIf'
|
|
489
|
+
},
|
|
490
|
+
arguments: [condition, thenFunction]
|
|
491
|
+
};
|
|
492
|
+
// Always chain .Else() even if there's no explicit else clause
|
|
493
|
+
// This ensures the conditional completes and returns phi nodes
|
|
494
|
+
let elseFunction;
|
|
495
|
+
if (node.alternate) {
|
|
496
|
+
elseFunction = {
|
|
475
497
|
type: 'ArrowFunctionExpression',
|
|
476
498
|
params: [],
|
|
477
|
-
body: node.
|
|
499
|
+
body: node.alternate.type === 'BlockStatement' ? node.alternate : {
|
|
478
500
|
type: 'BlockStatement',
|
|
479
|
-
body: [node.
|
|
501
|
+
body: [node.alternate]
|
|
480
502
|
}
|
|
481
503
|
};
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
504
|
+
} else {
|
|
505
|
+
// Create an empty else function
|
|
506
|
+
elseFunction = {
|
|
507
|
+
type: 'ArrowFunctionExpression',
|
|
508
|
+
params: [],
|
|
509
|
+
body: {
|
|
510
|
+
type: 'BlockStatement',
|
|
511
|
+
body: []
|
|
512
|
+
}
|
|
490
513
|
};
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
514
|
+
}
|
|
515
|
+
callExpression = {
|
|
516
|
+
type: 'CallExpression',
|
|
517
|
+
callee: {
|
|
518
|
+
type: 'MemberExpression',
|
|
519
|
+
object: callExpression,
|
|
520
|
+
property: {
|
|
521
|
+
type: 'Identifier',
|
|
522
|
+
name: 'Else'
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
arguments: [elseFunction]
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
// Analyze which outer scope variables are assigned in any branch
|
|
529
|
+
const assignedVars = new Set();
|
|
530
|
+
|
|
531
|
+
const analyzeBranch = (functionBody) => {
|
|
532
|
+
// First pass: collect all variable declarations in the branch
|
|
533
|
+
const localVars = new Set();
|
|
534
|
+
ancestor(functionBody, {
|
|
535
|
+
VariableDeclarator(node, ancestors) {
|
|
536
|
+
// Skip if we're inside a block that contains strands control flow
|
|
537
|
+
if (ancestors.some(statementContainsStrandsControlFlow)) return;
|
|
538
|
+
if (node.id.type === 'Identifier') {
|
|
539
|
+
localVars.add(node.id.name);
|
|
501
540
|
}
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// Second pass: find assignments to non-local variables using acorn-walk
|
|
545
|
+
ancestor(functionBody, {
|
|
546
|
+
AssignmentExpression(node, ancestors) {
|
|
547
|
+
// Skip if we're inside a block that contains strands control flow
|
|
548
|
+
if (ancestors.some(statementContainsStrandsControlFlow)) return;
|
|
549
|
+
|
|
550
|
+
const left = node.left;
|
|
551
|
+
if (left.type === 'Identifier') {
|
|
552
|
+
// Direct variable assignment: x = value
|
|
553
|
+
if (!localVars.has(left.name)) {
|
|
554
|
+
assignedVars.add(left.name);
|
|
555
|
+
}
|
|
556
|
+
} else if (left.type === 'MemberExpression') {
|
|
557
|
+
// Property assignment: obj.prop = value or obj.a.b = value
|
|
558
|
+
const propertyPath = buildPropertyPath(left);
|
|
559
|
+
if (propertyPath) {
|
|
560
|
+
const baseName = propertyPath.split('.')[0];
|
|
561
|
+
if (!localVars.has(baseName)) {
|
|
562
|
+
assignedVars.add(propertyPath);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
511
565
|
}
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
// Analyze all branches for assignments to outer scope variables
|
|
571
|
+
analyzeBranch(thenFunction.body);
|
|
572
|
+
analyzeBranch(elseFunction.body);
|
|
573
|
+
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
|
+
});
|
|
522
615
|
}
|
|
523
|
-
|
|
524
|
-
|
|
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
|
+
}
|
|
525
637
|
};
|
|
638
|
+
addCopyingAndReturn(thenFunction.body, assignedVars);
|
|
639
|
+
addCopyingAndReturn(elseFunction.body, assignedVars);
|
|
640
|
+
// Create a block variable to capture the return value
|
|
641
|
+
const blockVar = `__block_${blockVarCounter++}`;
|
|
642
|
+
// Replace with a block statement
|
|
643
|
+
const statements = [];
|
|
644
|
+
// Make sure every assigned variable starts as a node
|
|
645
|
+
for (const varPath of assignedVars) {
|
|
646
|
+
const parts = varPath.split('.');
|
|
647
|
+
|
|
648
|
+
// Build left side: inputs.color or just x
|
|
649
|
+
let leftExpr = { type: 'Identifier', name: parts[0] };
|
|
650
|
+
for (let i = 1; i < parts.length; i++) {
|
|
651
|
+
leftExpr = {
|
|
652
|
+
type: 'MemberExpression',
|
|
653
|
+
object: leftExpr,
|
|
654
|
+
property: { type: 'Identifier', name: parts[i] },
|
|
655
|
+
computed: false
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Build right side - same as left for strandsNode wrapping
|
|
660
|
+
let rightArgExpr = { type: 'Identifier', name: parts[0] };
|
|
661
|
+
for (let i = 1; i < parts.length; i++) {
|
|
662
|
+
rightArgExpr = {
|
|
663
|
+
type: 'MemberExpression',
|
|
664
|
+
object: rightArgExpr,
|
|
665
|
+
property: { type: 'Identifier', name: parts[i] },
|
|
666
|
+
computed: false
|
|
667
|
+
};
|
|
668
|
+
}
|
|
526
669
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
if (node.id.type === 'Identifier') {
|
|
538
|
-
localVars.add(node.id.name);
|
|
670
|
+
statements.push({
|
|
671
|
+
type: 'ExpressionStatement',
|
|
672
|
+
expression: {
|
|
673
|
+
type: 'AssignmentExpression',
|
|
674
|
+
operator: '=',
|
|
675
|
+
left: leftExpr,
|
|
676
|
+
right: {
|
|
677
|
+
type: 'CallExpression',
|
|
678
|
+
callee: { type: 'Identifier', name: '__p5.strandsNode' },
|
|
679
|
+
arguments: [rightArgExpr],
|
|
539
680
|
}
|
|
540
681
|
}
|
|
541
682
|
});
|
|
683
|
+
}
|
|
684
|
+
statements.push({
|
|
685
|
+
type: 'VariableDeclaration',
|
|
686
|
+
declarations: [{
|
|
687
|
+
type: 'VariableDeclarator',
|
|
688
|
+
id: { type: 'Identifier', name: blockVar },
|
|
689
|
+
init: callExpression
|
|
690
|
+
}],
|
|
691
|
+
kind: 'const'
|
|
692
|
+
});
|
|
693
|
+
// 2. Assignments for each modified variable
|
|
694
|
+
for (const varPath of assignedVars) {
|
|
695
|
+
const parts = varPath.split('.');
|
|
696
|
+
|
|
697
|
+
// Build left side: inputs.color or just x
|
|
698
|
+
let leftExpr = { type: 'Identifier', name: parts[0] };
|
|
699
|
+
for (let i = 1; i < parts.length; i++) {
|
|
700
|
+
leftExpr = {
|
|
701
|
+
type: 'MemberExpression',
|
|
702
|
+
object: leftExpr,
|
|
703
|
+
property: { type: 'Identifier', name: parts[i] },
|
|
704
|
+
computed: false
|
|
705
|
+
};
|
|
706
|
+
}
|
|
542
707
|
|
|
543
|
-
//
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
if (propertyPath) {
|
|
559
|
-
const baseName = propertyPath.split('.')[0];
|
|
560
|
-
if (!localVars.has(baseName)) {
|
|
561
|
-
assignedVars.add(propertyPath);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
}
|
|
708
|
+
// Build right side: __block_2['inputs.color'] or __block_2['x']
|
|
709
|
+
const rightExpr = {
|
|
710
|
+
type: 'MemberExpression',
|
|
711
|
+
object: { type: 'Identifier', name: blockVar },
|
|
712
|
+
property: { type: 'Literal', value: varPath },
|
|
713
|
+
computed: true
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
statements.push({
|
|
717
|
+
type: 'ExpressionStatement',
|
|
718
|
+
expression: {
|
|
719
|
+
type: 'AssignmentExpression',
|
|
720
|
+
operator: '=',
|
|
721
|
+
left: leftExpr,
|
|
722
|
+
right: rightExpr
|
|
565
723
|
}
|
|
566
724
|
});
|
|
725
|
+
}
|
|
726
|
+
// Replace the if statement with a block statement
|
|
727
|
+
node.type = 'BlockStatement';
|
|
728
|
+
node.body = statements;
|
|
729
|
+
} else {
|
|
730
|
+
// No assignments, just replace with the call expression
|
|
731
|
+
node.type = 'ExpressionStatement';
|
|
732
|
+
node.expression = callExpression;
|
|
733
|
+
}
|
|
734
|
+
delete node.test;
|
|
735
|
+
delete node.consequent;
|
|
736
|
+
delete node.alternate;
|
|
737
|
+
},
|
|
738
|
+
UpdateExpression(node, _state, ancestors) {
|
|
739
|
+
if (ancestors.some(nodeIsUniform)) { return; }
|
|
740
|
+
|
|
741
|
+
// Transform ++var, var++, --var, var-- into assignment expressions
|
|
742
|
+
let operator;
|
|
743
|
+
if (node.operator === '++') {
|
|
744
|
+
operator = '+';
|
|
745
|
+
} else if (node.operator === '--') {
|
|
746
|
+
operator = '-';
|
|
747
|
+
} else {
|
|
748
|
+
return; // Unknown update operator
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Convert to: var = var + 1 or var = var - 1
|
|
752
|
+
const assignmentExpr = {
|
|
753
|
+
type: 'AssignmentExpression',
|
|
754
|
+
operator: '=',
|
|
755
|
+
left: node.argument,
|
|
756
|
+
right: {
|
|
757
|
+
type: 'BinaryExpression',
|
|
758
|
+
operator: operator,
|
|
759
|
+
left: node.argument,
|
|
760
|
+
right: {
|
|
761
|
+
type: 'Literal',
|
|
762
|
+
value: 1
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
// Replace the update expression with the assignment expression
|
|
768
|
+
Object.assign(node, assignmentExpr);
|
|
769
|
+
delete node.prefix;
|
|
770
|
+
this.BinaryExpression(node.right, _state, [...ancestors, node]);
|
|
771
|
+
this.AssignmentExpression(node, _state, ancestors);
|
|
772
|
+
},
|
|
773
|
+
ForStatement(node, _state, ancestors) {
|
|
774
|
+
if (ancestors.some(nodeIsUniform)) { return; }
|
|
775
|
+
|
|
776
|
+
// Transform for statement into strandsFor() call
|
|
777
|
+
// for (init; test; update) body -> strandsFor(initCb, conditionCb, updateCb, bodyCb, initialVars)
|
|
778
|
+
|
|
779
|
+
// Generate unique loop variable name
|
|
780
|
+
const uniqueLoopVar = `loopVar${loopVarCounter++}`;
|
|
781
|
+
|
|
782
|
+
// Create the initial callback from the for loop's init
|
|
783
|
+
let initialFunction;
|
|
784
|
+
if (node.init && node.init.type === 'VariableDeclaration') {
|
|
785
|
+
// Handle: for (let i = 0; ...)
|
|
786
|
+
const declaration = node.init.declarations[0];
|
|
787
|
+
let initValue = declaration.init;
|
|
788
|
+
|
|
789
|
+
const initAst = { body: [{ type: 'ExpressionStatement', expression: initValue }] };
|
|
790
|
+
initValue = initAst.body[0].expression;
|
|
791
|
+
|
|
792
|
+
initialFunction = {
|
|
793
|
+
type: 'ArrowFunctionExpression',
|
|
794
|
+
params: [],
|
|
795
|
+
body: {
|
|
796
|
+
type: 'BlockStatement',
|
|
797
|
+
body: [{
|
|
798
|
+
type: 'ReturnStatement',
|
|
799
|
+
argument: initValue
|
|
800
|
+
}]
|
|
801
|
+
}
|
|
567
802
|
};
|
|
803
|
+
} else {
|
|
804
|
+
// Handle other cases - return a default value
|
|
805
|
+
initialFunction = {
|
|
806
|
+
type: 'ArrowFunctionExpression',
|
|
807
|
+
params: [],
|
|
808
|
+
body: {
|
|
809
|
+
type: 'BlockStatement',
|
|
810
|
+
body: [{
|
|
811
|
+
type: 'ReturnStatement',
|
|
812
|
+
argument: {
|
|
813
|
+
type: 'Literal',
|
|
814
|
+
value: 0
|
|
815
|
+
}
|
|
816
|
+
}]
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
}
|
|
568
820
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
// Build the member expression for the property path
|
|
585
|
-
let sourceExpr = { type: 'Identifier', name: parts[0] };
|
|
586
|
-
for (let i = 1; i < parts.length; i++) {
|
|
587
|
-
sourceExpr = {
|
|
588
|
-
type: 'MemberExpression',
|
|
589
|
-
object: sourceExpr,
|
|
590
|
-
property: { type: 'Identifier', name: parts[i] },
|
|
591
|
-
computed: false
|
|
592
|
-
};
|
|
593
|
-
}
|
|
821
|
+
// Create the condition callback
|
|
822
|
+
let conditionBody = node.test || { type: 'Literal', value: true };
|
|
823
|
+
// Replace loop variable references with the parameter
|
|
824
|
+
if (node.init?.type === 'VariableDeclaration') {
|
|
825
|
+
const loopVarName = node.init.declarations[0].id.name;
|
|
826
|
+
conditionBody = this.replaceIdentifierReferences(conditionBody, loopVarName, uniqueLoopVar);
|
|
827
|
+
}
|
|
828
|
+
const conditionAst = { body: [{ type: 'ExpressionStatement', expression: conditionBody }] };
|
|
829
|
+
conditionBody = conditionAst.body[0].expression;
|
|
830
|
+
|
|
831
|
+
const conditionFunction = {
|
|
832
|
+
type: 'ArrowFunctionExpression',
|
|
833
|
+
params: [{ type: 'Identifier', name: uniqueLoopVar }],
|
|
834
|
+
body: conditionBody
|
|
835
|
+
};
|
|
594
836
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
837
|
+
// Create the update callback
|
|
838
|
+
let updateFunction;
|
|
839
|
+
if (node.update) {
|
|
840
|
+
let updateExpr = node.update;
|
|
841
|
+
// Replace loop variable references with the parameter
|
|
842
|
+
if (node.init?.type === 'VariableDeclaration') {
|
|
843
|
+
const loopVarName = node.init.declarations[0].id.name;
|
|
844
|
+
updateExpr = this.replaceIdentifierReferences(updateExpr, loopVarName, uniqueLoopVar);
|
|
845
|
+
}
|
|
846
|
+
const updateAst = { body: [{ type: 'ExpressionStatement', expression: updateExpr }] };
|
|
847
|
+
updateExpr = updateAst.body[0].expression;
|
|
848
|
+
|
|
849
|
+
updateFunction = {
|
|
850
|
+
type: 'ArrowFunctionExpression',
|
|
851
|
+
params: [{ type: 'Identifier', name: uniqueLoopVar }],
|
|
852
|
+
body: {
|
|
853
|
+
type: 'BlockStatement',
|
|
854
|
+
body: [{
|
|
855
|
+
type: 'ReturnStatement',
|
|
856
|
+
argument: updateExpr
|
|
857
|
+
}]
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
} else {
|
|
861
|
+
updateFunction = {
|
|
862
|
+
type: 'ArrowFunctionExpression',
|
|
863
|
+
params: [{ type: 'Identifier', name: uniqueLoopVar }],
|
|
864
|
+
body: {
|
|
865
|
+
type: 'BlockStatement',
|
|
866
|
+
body: [{
|
|
867
|
+
type: 'ReturnStatement',
|
|
868
|
+
argument: { type: 'Identifier', name: uniqueLoopVar }
|
|
869
|
+
}]
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Create the body callback
|
|
875
|
+
let bodyBlock = node.body.type === 'BlockStatement' ? node.body : {
|
|
876
|
+
type: 'BlockStatement',
|
|
877
|
+
body: [node.body]
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
// Replace loop variable references in the body
|
|
881
|
+
if (node.init?.type === 'VariableDeclaration') {
|
|
882
|
+
const loopVarName = node.init.declarations[0].id.name;
|
|
883
|
+
bodyBlock = this.replaceIdentifierReferences(bodyBlock, loopVarName, uniqueLoopVar);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
const bodyFunction = {
|
|
887
|
+
type: 'ArrowFunctionExpression',
|
|
888
|
+
params: [
|
|
889
|
+
{ type: 'Identifier', name: uniqueLoopVar },
|
|
890
|
+
{ type: 'Identifier', name: 'vars' }
|
|
891
|
+
],
|
|
892
|
+
body: bodyBlock
|
|
893
|
+
};
|
|
894
|
+
|
|
895
|
+
// Analyze which outer scope variables are assigned in the loop body
|
|
896
|
+
const assignedVars = new Set();
|
|
897
|
+
|
|
898
|
+
// First pass: collect all variable declarations in the body
|
|
899
|
+
const localVars = new Set();
|
|
900
|
+
ancestor(bodyFunction.body, {
|
|
901
|
+
VariableDeclarator(node, ancestors) {
|
|
902
|
+
// Skip if we're inside a block that contains strands control flow
|
|
903
|
+
if (ancestors.some(statementContainsStrandsControlFlow)) return;
|
|
904
|
+
if (node.id.type === 'Identifier') {
|
|
905
|
+
localVars.add(node.id.name);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
// Second pass: find assignments to non-local variables using acorn-walk
|
|
911
|
+
ancestor(bodyFunction.body, {
|
|
912
|
+
AssignmentExpression(node, ancestors) {
|
|
913
|
+
// Skip if we're inside a block that contains strands control flow
|
|
914
|
+
if (ancestors.some(statementContainsStrandsControlFlow)) {
|
|
915
|
+
return
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const left = node.left;
|
|
919
|
+
if (left.type === 'Identifier') {
|
|
920
|
+
// Direct variable assignment: x = value
|
|
921
|
+
if (!localVars.has(left.name)) {
|
|
922
|
+
assignedVars.add(left.name);
|
|
923
|
+
}
|
|
924
|
+
} else if (left.type === 'MemberExpression') {
|
|
925
|
+
// Property assignment: obj.prop = value or obj.a.b = value
|
|
926
|
+
const propertyPath = buildPropertyPath(left);
|
|
927
|
+
if (propertyPath) {
|
|
928
|
+
const baseName = propertyPath.split('.')[0];
|
|
929
|
+
if (!localVars.has(baseName)) {
|
|
930
|
+
assignedVars.add(propertyPath);
|
|
614
931
|
}
|
|
615
|
-
// Apply reference replacement to all statements
|
|
616
|
-
functionBody.body.forEach(node => replaceReferences(node, tempVarMap));
|
|
617
|
-
// Insert copy statements at the beginning
|
|
618
|
-
functionBody.body.unshift(...copyStatements);
|
|
619
|
-
// Add return statement with flat object using property paths as keys
|
|
620
|
-
const returnObj = {
|
|
621
|
-
type: 'ObjectExpression',
|
|
622
|
-
properties: Array.from(varsToReturn).map(varPath => ({
|
|
623
|
-
type: 'Property',
|
|
624
|
-
key: { type: 'Literal', value: varPath },
|
|
625
|
-
value: { type: 'Identifier', name: tempVarMap.get(varPath) },
|
|
626
|
-
kind: 'init',
|
|
627
|
-
computed: false,
|
|
628
|
-
shorthand: false
|
|
629
|
-
}))
|
|
630
|
-
};
|
|
631
|
-
functionBody.body.push({
|
|
632
|
-
type: 'ReturnStatement',
|
|
633
|
-
argument: returnObj
|
|
634
|
-
});
|
|
635
932
|
}
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
// Create a block variable to capture the return value
|
|
640
|
-
const blockVar = `__block_${blockVarCounter++}`;
|
|
641
|
-
// Replace with a block statement
|
|
642
|
-
const statements = [];
|
|
643
|
-
// Make sure every assigned variable starts as a node
|
|
644
|
-
for (const varPath of assignedVars) {
|
|
645
|
-
const parts = varPath.split('.');
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
});
|
|
646
936
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
937
|
+
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
|
+
});
|
|
656
979
|
}
|
|
657
980
|
|
|
658
|
-
|
|
659
|
-
|
|
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
|
+
|
|
1004
|
+
addCopyingAndReturn(bodyFunction.body, assignedVars);
|
|
1005
|
+
|
|
1006
|
+
// Create block variable and assignments similar to if statements
|
|
1007
|
+
const blockVar = `__block_${blockVarCounter++}`;
|
|
1008
|
+
const statements = [];
|
|
1009
|
+
|
|
1010
|
+
const initialVarsObject = {
|
|
1011
|
+
type: 'ObjectExpression',
|
|
1012
|
+
properties: Array.from(assignedVars).map(varPath => {
|
|
1013
|
+
const parts = varPath.split('.');
|
|
1014
|
+
let expr = { type: 'Identifier', name: parts[0] };
|
|
660
1015
|
for (let i = 1; i < parts.length; i++) {
|
|
661
|
-
|
|
1016
|
+
expr = {
|
|
662
1017
|
type: 'MemberExpression',
|
|
663
|
-
object:
|
|
1018
|
+
object: expr,
|
|
664
1019
|
property: { type: 'Identifier', name: parts[i] },
|
|
665
1020
|
computed: false
|
|
666
1021
|
};
|
|
667
1022
|
}
|
|
1023
|
+
const wrappedExpr = {
|
|
1024
|
+
type: 'CallExpression',
|
|
1025
|
+
callee: { type: 'Identifier', name: '__p5.strandsNode' },
|
|
1026
|
+
arguments: [expr]
|
|
1027
|
+
};
|
|
1028
|
+
return {
|
|
1029
|
+
type: 'Property',
|
|
1030
|
+
key: { type: 'Literal', value: varPath },
|
|
1031
|
+
value: wrappedExpr,
|
|
1032
|
+
kind: 'init',
|
|
1033
|
+
computed: false,
|
|
1034
|
+
shorthand: false
|
|
1035
|
+
};
|
|
1036
|
+
})
|
|
1037
|
+
};
|
|
668
1038
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
1039
|
+
// Create the strandsFor call
|
|
1040
|
+
const callExpression = {
|
|
1041
|
+
type: 'CallExpression',
|
|
1042
|
+
callee: {
|
|
1043
|
+
type: 'Identifier',
|
|
1044
|
+
name: '__p5.strandsFor'
|
|
1045
|
+
},
|
|
1046
|
+
arguments: [initialFunction, conditionFunction, updateFunction, bodyFunction, initialVarsObject]
|
|
1047
|
+
};
|
|
1048
|
+
|
|
1049
|
+
statements.push({
|
|
1050
|
+
type: 'VariableDeclaration',
|
|
1051
|
+
declarations: [{
|
|
1052
|
+
type: 'VariableDeclarator',
|
|
1053
|
+
id: { type: 'Identifier', name: blockVar },
|
|
1054
|
+
init: callExpression
|
|
1055
|
+
}],
|
|
1056
|
+
kind: 'const'
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
// Add assignments back to original variables
|
|
1060
|
+
for (const varPath of assignedVars) {
|
|
1061
|
+
const parts = varPath.split('.');
|
|
1062
|
+
|
|
1063
|
+
// Build left side: inputs.color or just x
|
|
1064
|
+
let leftExpr = { type: 'Identifier', name: parts[0] };
|
|
1065
|
+
for (let i = 1; i < parts.length; i++) {
|
|
1066
|
+
leftExpr = {
|
|
1067
|
+
type: 'MemberExpression',
|
|
1068
|
+
object: leftExpr,
|
|
1069
|
+
property: { type: 'Identifier', name: parts[i] },
|
|
1070
|
+
computed: false
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// Build right side: __block_2.inputs.color or __block_2.x
|
|
1075
|
+
let rightExpr = { type: 'Identifier', name: blockVar };
|
|
1076
|
+
for (const part of parts) {
|
|
1077
|
+
rightExpr = {
|
|
1078
|
+
type: 'MemberExpression',
|
|
1079
|
+
object: rightExpr,
|
|
1080
|
+
property: { type: 'Identifier', name: part },
|
|
1081
|
+
computed: false
|
|
1082
|
+
};
|
|
682
1083
|
}
|
|
1084
|
+
|
|
683
1085
|
statements.push({
|
|
684
|
-
type: '
|
|
685
|
-
|
|
686
|
-
type: '
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
1086
|
+
type: 'ExpressionStatement',
|
|
1087
|
+
expression: {
|
|
1088
|
+
type: 'AssignmentExpression',
|
|
1089
|
+
operator: '=',
|
|
1090
|
+
left: leftExpr,
|
|
1091
|
+
right: rightExpr
|
|
1092
|
+
}
|
|
691
1093
|
});
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
node.type = 'BlockStatement';
|
|
1097
|
+
node.body = statements;
|
|
1098
|
+
} else {
|
|
1099
|
+
// No assignments, just replace with call expression
|
|
1100
|
+
node.type = 'ExpressionStatement';
|
|
1101
|
+
node.expression = {
|
|
1102
|
+
type: 'CallExpression',
|
|
1103
|
+
callee: {
|
|
1104
|
+
type: 'Identifier',
|
|
1105
|
+
name: '__p5.strandsFor'
|
|
1106
|
+
},
|
|
1107
|
+
arguments: [initialFunction, conditionFunction, updateFunction, bodyFunction, {
|
|
1108
|
+
type: 'ObjectExpression',
|
|
1109
|
+
properties: []
|
|
1110
|
+
}]
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
695
1113
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
type: 'MemberExpression',
|
|
701
|
-
object: leftExpr,
|
|
702
|
-
property: { type: 'Identifier', name: parts[i] },
|
|
703
|
-
computed: false
|
|
704
|
-
};
|
|
705
|
-
}
|
|
1114
|
+
delete node.init;
|
|
1115
|
+
delete node.test;
|
|
1116
|
+
delete node.update;
|
|
1117
|
+
},
|
|
706
1118
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
object: { type: 'Identifier', name: blockVar },
|
|
711
|
-
property: { type: 'Literal', value: varPath },
|
|
712
|
-
computed: true
|
|
713
|
-
};
|
|
1119
|
+
// Helper method to replace identifier references in AST nodes
|
|
1120
|
+
replaceIdentifierReferences(node, oldName, newName) {
|
|
1121
|
+
if (!node || typeof node !== 'object') return node;
|
|
714
1122
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
left: leftExpr,
|
|
721
|
-
right: rightExpr
|
|
722
|
-
}
|
|
723
|
-
});
|
|
724
|
-
}
|
|
725
|
-
// Replace the if statement with a block statement
|
|
726
|
-
node.type = 'BlockStatement';
|
|
727
|
-
node.body = statements;
|
|
728
|
-
} else {
|
|
729
|
-
// No assignments, just replace with the call expression
|
|
730
|
-
node.type = 'ExpressionStatement';
|
|
731
|
-
node.expression = callExpression;
|
|
732
|
-
}
|
|
733
|
-
delete node.test;
|
|
734
|
-
delete node.consequent;
|
|
735
|
-
delete node.alternate;
|
|
736
|
-
},
|
|
737
|
-
UpdateExpression(node, _state, ancestors) {
|
|
738
|
-
if (ancestors.some(nodeIsUniform)) { return; }
|
|
739
|
-
|
|
740
|
-
// Transform ++var, var++, --var, var-- into assignment expressions
|
|
741
|
-
let operator;
|
|
742
|
-
if (node.operator === '++') {
|
|
743
|
-
operator = '+';
|
|
744
|
-
} else if (node.operator === '--') {
|
|
745
|
-
operator = '-';
|
|
746
|
-
} else {
|
|
747
|
-
return; // Unknown update operator
|
|
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 };
|
|
748
1128
|
}
|
|
749
1129
|
|
|
750
|
-
//
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
left: node.argument,
|
|
759
|
-
right: {
|
|
760
|
-
type: 'Literal',
|
|
761
|
-
value: 1
|
|
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]);
|
|
762
1138
|
}
|
|
763
1139
|
}
|
|
764
|
-
}
|
|
1140
|
+
}
|
|
1141
|
+
return newNode;
|
|
1142
|
+
};
|
|
765
1143
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
1144
|
+
return replaceInNode(node);
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
|
|
1148
|
+
// Helper function to check if a function body contains return statements in control flow
|
|
1149
|
+
function functionHasEarlyReturns(functionNode) {
|
|
1150
|
+
let hasEarlyReturn = false;
|
|
1151
|
+
let inControlFlow = 0;
|
|
1152
|
+
|
|
1153
|
+
const checkForEarlyReturns = {
|
|
1154
|
+
IfStatement(node, state, c) {
|
|
1155
|
+
inControlFlow++;
|
|
1156
|
+
if (node.test) c(node.test, state);
|
|
1157
|
+
if (node.consequent) c(node.consequent, state);
|
|
1158
|
+
if (node.alternate) c(node.alternate, state);
|
|
1159
|
+
inControlFlow--;
|
|
771
1160
|
},
|
|
772
|
-
ForStatement(node,
|
|
773
|
-
|
|
1161
|
+
ForStatement(node, state, c) {
|
|
1162
|
+
inControlFlow++;
|
|
1163
|
+
if (node.init) c(node.init, state);
|
|
1164
|
+
if (node.test) c(node.test, state);
|
|
1165
|
+
if (node.update) c(node.update, state);
|
|
1166
|
+
if (node.body) c(node.body, state);
|
|
1167
|
+
inControlFlow--;
|
|
1168
|
+
},
|
|
1169
|
+
ReturnStatement(node) {
|
|
1170
|
+
if (inControlFlow > 0) {
|
|
1171
|
+
hasEarlyReturn = true;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
774
1175
|
|
|
775
|
-
|
|
776
|
-
|
|
1176
|
+
if (functionNode.body && functionNode.body.type === 'BlockStatement') {
|
|
1177
|
+
recursive(functionNode.body, {}, checkForEarlyReturns);
|
|
1178
|
+
}
|
|
777
1179
|
|
|
778
|
-
|
|
779
|
-
|
|
1180
|
+
return hasEarlyReturn;
|
|
1181
|
+
}
|
|
780
1182
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
1183
|
+
// Helper function to check if a block contains a return anywhere in it
|
|
1184
|
+
function blockContainsReturn(block) {
|
|
1185
|
+
let hasReturn = false;
|
|
1186
|
+
const findReturn = {
|
|
1187
|
+
ReturnStatement() {
|
|
1188
|
+
hasReturn = true;
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
if (block) {
|
|
1192
|
+
recursive(block, {}, findReturn);
|
|
1193
|
+
}
|
|
1194
|
+
return hasReturn;
|
|
1195
|
+
}
|
|
787
1196
|
|
|
788
|
-
|
|
789
|
-
|
|
1197
|
+
// Transform a helper function to use __returnValue pattern instead of early returns.
|
|
1198
|
+
// This is necessary because we evaluate helper function *in javascript* rather than
|
|
1199
|
+
// converting them to functions in GLSL (which is hard because we don't know the types
|
|
1200
|
+
// of function parameters upfront, and they may change from use to use.) So they act
|
|
1201
|
+
// like macros, all contributing to build up a single function overall. An early return
|
|
1202
|
+
// in a helper should not be an early return of the entire hook function. Instead, we
|
|
1203
|
+
// just make sure helper functions always evaluate to a single value.
|
|
1204
|
+
function transformHelperFunction(functionNode) {
|
|
1205
|
+
// 1. Add __returnValue declaration at the start of function body
|
|
1206
|
+
const returnValueDecl = {
|
|
1207
|
+
type: 'VariableDeclaration',
|
|
1208
|
+
declarations: [{
|
|
1209
|
+
type: 'VariableDeclarator',
|
|
1210
|
+
id: { type: 'Identifier', name: '__returnValue' },
|
|
1211
|
+
init: null
|
|
1212
|
+
}],
|
|
1213
|
+
kind: 'let'
|
|
1214
|
+
};
|
|
790
1215
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
body: {
|
|
795
|
-
type: 'BlockStatement',
|
|
796
|
-
body: [{
|
|
797
|
-
type: 'ReturnStatement',
|
|
798
|
-
argument: initValue
|
|
799
|
-
}]
|
|
800
|
-
}
|
|
801
|
-
};
|
|
802
|
-
} else {
|
|
803
|
-
// Handle other cases - return a default value
|
|
804
|
-
initialFunction = {
|
|
805
|
-
type: 'ArrowFunctionExpression',
|
|
806
|
-
params: [],
|
|
807
|
-
body: {
|
|
808
|
-
type: 'BlockStatement',
|
|
809
|
-
body: [{
|
|
810
|
-
type: 'ReturnStatement',
|
|
811
|
-
argument: {
|
|
812
|
-
type: 'Literal',
|
|
813
|
-
value: 0
|
|
814
|
-
}
|
|
815
|
-
}]
|
|
816
|
-
}
|
|
817
|
-
};
|
|
818
|
-
}
|
|
1216
|
+
if (!functionNode.body || functionNode.body.type !== 'BlockStatement') {
|
|
1217
|
+
return; // Can't transform arrow functions with expression bodies
|
|
1218
|
+
}
|
|
819
1219
|
|
|
820
|
-
|
|
821
|
-
let conditionBody = node.test || { type: 'Literal', value: true };
|
|
822
|
-
// Replace loop variable references with the parameter
|
|
823
|
-
if (node.init?.type === 'VariableDeclaration') {
|
|
824
|
-
const loopVarName = node.init.declarations[0].id.name;
|
|
825
|
-
conditionBody = this.replaceIdentifierReferences(conditionBody, loopVarName, uniqueLoopVar);
|
|
826
|
-
}
|
|
827
|
-
const conditionAst = { body: [{ type: 'ExpressionStatement', expression: conditionBody }] };
|
|
828
|
-
conditionBody = conditionAst.body[0].expression;
|
|
1220
|
+
functionNode.body.body.unshift(returnValueDecl);
|
|
829
1221
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
};
|
|
1222
|
+
// 2. Restructure if statements: move siblings after if with return into else block
|
|
1223
|
+
function restructureIfStatements(statements) {
|
|
1224
|
+
for (let i = 0; i < statements.length; i++) {
|
|
1225
|
+
const stmt = statements[i];
|
|
835
1226
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
let updateExpr = node.update;
|
|
840
|
-
// Replace loop variable references with the parameter
|
|
841
|
-
if (node.init?.type === 'VariableDeclaration') {
|
|
842
|
-
const loopVarName = node.init.declarations[0].id.name;
|
|
843
|
-
updateExpr = this.replaceIdentifierReferences(updateExpr, loopVarName, uniqueLoopVar);
|
|
844
|
-
}
|
|
845
|
-
const updateAst = { body: [{ type: 'ExpressionStatement', expression: updateExpr }] };
|
|
846
|
-
updateExpr = updateAst.body[0].expression;
|
|
1227
|
+
if (stmt.type === 'IfStatement' && blockContainsReturn(stmt.consequent) && !stmt.alternate) {
|
|
1228
|
+
// Find all subsequent statements
|
|
1229
|
+
const subsequentStatements = statements.slice(i + 1);
|
|
847
1230
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
body: {
|
|
852
|
-
type: 'BlockStatement',
|
|
853
|
-
body: [{
|
|
854
|
-
type: 'ReturnStatement',
|
|
855
|
-
argument: updateExpr
|
|
856
|
-
}]
|
|
857
|
-
}
|
|
858
|
-
};
|
|
859
|
-
} else {
|
|
860
|
-
updateFunction = {
|
|
861
|
-
type: 'ArrowFunctionExpression',
|
|
862
|
-
params: [{ type: 'Identifier', name: uniqueLoopVar }],
|
|
863
|
-
body: {
|
|
1231
|
+
if (subsequentStatements.length > 0) {
|
|
1232
|
+
// Create else block with subsequent statements
|
|
1233
|
+
stmt.alternate = {
|
|
864
1234
|
type: 'BlockStatement',
|
|
865
|
-
body:
|
|
866
|
-
|
|
867
|
-
argument: { type: 'Identifier', name: uniqueLoopVar }
|
|
868
|
-
}]
|
|
869
|
-
}
|
|
870
|
-
};
|
|
871
|
-
}
|
|
1235
|
+
body: subsequentStatements
|
|
1236
|
+
};
|
|
872
1237
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
type: 'BlockStatement',
|
|
876
|
-
body: [node.body]
|
|
877
|
-
};
|
|
1238
|
+
// Remove the subsequent statements from this level
|
|
1239
|
+
statements.splice(i + 1);
|
|
878
1240
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
bodyBlock = this.replaceIdentifierReferences(bodyBlock, loopVarName, uniqueLoopVar);
|
|
1241
|
+
// Recursively process the new else block
|
|
1242
|
+
restructureIfStatements(stmt.alternate.body);
|
|
1243
|
+
}
|
|
883
1244
|
}
|
|
884
1245
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1246
|
+
// Recursively process nested blocks
|
|
1247
|
+
if (stmt.type === 'IfStatement') {
|
|
1248
|
+
if (stmt.consequent && stmt.consequent.type === 'BlockStatement') {
|
|
1249
|
+
restructureIfStatements(stmt.consequent.body);
|
|
1250
|
+
}
|
|
1251
|
+
if (stmt.alternate && stmt.alternate.type === 'BlockStatement') {
|
|
1252
|
+
restructureIfStatements(stmt.alternate.body);
|
|
1253
|
+
}
|
|
1254
|
+
} else if (stmt.type === 'ForStatement' && stmt.body && stmt.body.type === 'BlockStatement') {
|
|
1255
|
+
restructureIfStatements(stmt.body.body);
|
|
1256
|
+
} else if (stmt.type === 'BlockStatement') {
|
|
1257
|
+
restructureIfStatements(stmt.body);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
restructureIfStatements(functionNode.body.body);
|
|
1263
|
+
|
|
1264
|
+
// 3. Transform all return statements to assignments
|
|
1265
|
+
const transformReturns = {
|
|
1266
|
+
ReturnStatement(node) {
|
|
1267
|
+
// Convert return statement to assignment
|
|
1268
|
+
node.type = 'ExpressionStatement';
|
|
1269
|
+
node.expression = {
|
|
1270
|
+
type: 'AssignmentExpression',
|
|
1271
|
+
operator: '=',
|
|
1272
|
+
left: { type: 'Identifier', name: '__returnValue' },
|
|
1273
|
+
right: node.argument || { type: 'Identifier', name: 'undefined' }
|
|
892
1274
|
};
|
|
1275
|
+
delete node.argument;
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
893
1278
|
|
|
894
|
-
|
|
895
|
-
const assignedVars = new Set();
|
|
1279
|
+
recursive(functionNode.body, {}, transformReturns);
|
|
896
1280
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
if (ancestors.some(statementContainsStrandsControlFlow)) return;
|
|
903
|
-
if (node.id.type === 'Identifier') {
|
|
904
|
-
localVars.add(node.id.name);
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
});
|
|
1281
|
+
// 4. Add final return statement
|
|
1282
|
+
const finalReturn = {
|
|
1283
|
+
type: 'ReturnStatement',
|
|
1284
|
+
argument: { type: 'Identifier', name: '__returnValue' }
|
|
1285
|
+
};
|
|
908
1286
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
AssignmentExpression(node, ancestors) {
|
|
912
|
-
// Skip if we're inside a block that contains strands control flow
|
|
913
|
-
if (ancestors.some(statementContainsStrandsControlFlow)) {
|
|
914
|
-
return
|
|
915
|
-
}
|
|
1287
|
+
functionNode.body.body.push(finalReturn);
|
|
1288
|
+
}
|
|
916
1289
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1290
|
+
// Helper function to check if a function body contains .set() calls in control flow
|
|
1291
|
+
function functionHasSetInControlFlow(functionNode) {
|
|
1292
|
+
let hasSetInControlFlow = false;
|
|
1293
|
+
let inControlFlow = 0;
|
|
1294
|
+
|
|
1295
|
+
const checkForSetCalls = {
|
|
1296
|
+
IfStatement(node, state, c) {
|
|
1297
|
+
inControlFlow++;
|
|
1298
|
+
if (node.test) c(node.test, state);
|
|
1299
|
+
if (node.consequent) c(node.consequent, state);
|
|
1300
|
+
if (node.alternate) c(node.alternate, state);
|
|
1301
|
+
inControlFlow--;
|
|
1302
|
+
},
|
|
1303
|
+
ForStatement(node, state, c) {
|
|
1304
|
+
inControlFlow++;
|
|
1305
|
+
if (node.init) c(node.init, state);
|
|
1306
|
+
if (node.test) c(node.test, state);
|
|
1307
|
+
if (node.update) c(node.update, state);
|
|
1308
|
+
if (node.body) c(node.body, state);
|
|
1309
|
+
inControlFlow--;
|
|
1310
|
+
},
|
|
1311
|
+
CallExpression(node) {
|
|
1312
|
+
// Check if this is a .set() call
|
|
1313
|
+
if (inControlFlow > 0 &&
|
|
1314
|
+
node.callee?.type === 'MemberExpression' &&
|
|
1315
|
+
node.callee?.property?.name === 'set') {
|
|
1316
|
+
hasSetInControlFlow = true;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
};
|
|
935
1320
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
if (functionBody.type === 'BlockStatement') {
|
|
940
|
-
const tempVarMap = new Map();
|
|
941
|
-
const copyStatements = [];
|
|
942
|
-
|
|
943
|
-
for (const varPath of varsToReturn) {
|
|
944
|
-
const parts = varPath.split('.');
|
|
945
|
-
const tempName = `__copy_${parts.join('_')}_${blockVarCounter++}`;
|
|
946
|
-
tempVarMap.set(varPath, tempName);
|
|
947
|
-
|
|
948
|
-
// Build the member expression for vars.propertyPath
|
|
949
|
-
// e.g., vars.inputs.color or vars.x
|
|
950
|
-
let sourceExpr = { type: 'Identifier', name: 'vars' };
|
|
951
|
-
for (const part of parts) {
|
|
952
|
-
sourceExpr = {
|
|
953
|
-
type: 'MemberExpression',
|
|
954
|
-
object: sourceExpr,
|
|
955
|
-
property: { type: 'Identifier', name: part },
|
|
956
|
-
computed: false
|
|
957
|
-
};
|
|
958
|
-
}
|
|
1321
|
+
if (functionNode.body && functionNode.body.type === 'BlockStatement') {
|
|
1322
|
+
recursive(functionNode.body, {}, checkForSetCalls);
|
|
1323
|
+
}
|
|
959
1324
|
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
declarations: [{
|
|
963
|
-
type: 'VariableDeclarator',
|
|
964
|
-
id: { type: 'Identifier', name: tempName },
|
|
965
|
-
init: {
|
|
966
|
-
type: 'CallExpression',
|
|
967
|
-
callee: {
|
|
968
|
-
type: 'MemberExpression',
|
|
969
|
-
object: sourceExpr,
|
|
970
|
-
property: { type: 'Identifier', name: 'copy' },
|
|
971
|
-
computed: false
|
|
972
|
-
},
|
|
973
|
-
arguments: []
|
|
974
|
-
}
|
|
975
|
-
}],
|
|
976
|
-
kind: 'let'
|
|
977
|
-
});
|
|
978
|
-
}
|
|
1325
|
+
return hasSetInControlFlow;
|
|
1326
|
+
}
|
|
979
1327
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
type: 'ObjectExpression',
|
|
986
|
-
properties: Array.from(varsToReturn).map(varPath => ({
|
|
987
|
-
type: 'Property',
|
|
988
|
-
key: { type: 'Literal', value: varPath },
|
|
989
|
-
value: { type: 'Identifier', name: tempVarMap.get(varPath) },
|
|
990
|
-
kind: 'init',
|
|
991
|
-
computed: false,
|
|
992
|
-
shorthand: false
|
|
993
|
-
}))
|
|
994
|
-
};
|
|
1328
|
+
// Transform a function to use __setValue pattern instead of .set() calls in branches/loops
|
|
1329
|
+
function transformFunctionSetCalls(functionNode) {
|
|
1330
|
+
if (!functionNode.body || functionNode.body.type !== 'BlockStatement') {
|
|
1331
|
+
return; // Can't transform arrow functions with expression bodies
|
|
1332
|
+
}
|
|
995
1333
|
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1334
|
+
// Track which hooks have .set() calls, mapping expression string to the actual AST node
|
|
1335
|
+
const hooksWithSetCalls = new Map(); // exprString -> hookObjectNode
|
|
1336
|
+
|
|
1337
|
+
// First pass: find all hooks that have .set() calls in control flow
|
|
1338
|
+
const findSetCalls = {
|
|
1339
|
+
CallExpression(node) {
|
|
1340
|
+
if (node.callee?.type === 'MemberExpression' &&
|
|
1341
|
+
node.callee?.property?.name === 'set' &&
|
|
1342
|
+
node.callee?.object) {
|
|
1343
|
+
// This is something like filterColor.set(...) or myp5.filterColor.set(...)
|
|
1344
|
+
const hookObjectNode = node.callee.object;
|
|
1345
|
+
const exprString = escodegen.generate(hookObjectNode);
|
|
1346
|
+
if (!hooksWithSetCalls.has(exprString)) {
|
|
1347
|
+
hooksWithSetCalls.set(exprString, hookObjectNode);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
};
|
|
1002
1352
|
|
|
1003
|
-
|
|
1353
|
+
recursive(functionNode.body, {}, findSetCalls);
|
|
1004
1354
|
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1355
|
+
if (hooksWithSetCalls.size === 0) {
|
|
1356
|
+
return; // No .set() calls to transform
|
|
1357
|
+
}
|
|
1008
1358
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
arguments: [expr]
|
|
1026
|
-
};
|
|
1027
|
-
return {
|
|
1028
|
-
type: 'Property',
|
|
1029
|
-
key: { type: 'Literal', value: varPath },
|
|
1030
|
-
value: wrappedExpr,
|
|
1031
|
-
kind: 'init',
|
|
1032
|
-
computed: false,
|
|
1033
|
-
shorthand: false
|
|
1034
|
-
};
|
|
1035
|
-
})
|
|
1036
|
-
};
|
|
1359
|
+
// For each hook with .set() calls, add intermediate variable and transform
|
|
1360
|
+
for (const [exprString, hookObjectNode] of hooksWithSetCalls) {
|
|
1361
|
+
// Create a safe variable name from the expression
|
|
1362
|
+
const safeVarName = exprString.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
1363
|
+
const intermediateVarName = `__${safeVarName}_value`;
|
|
1364
|
+
|
|
1365
|
+
// 1. Find the .begin() call and insert intermediate variable right after it
|
|
1366
|
+
const intermediateVarDecl = {
|
|
1367
|
+
type: 'VariableDeclaration',
|
|
1368
|
+
declarations: [{
|
|
1369
|
+
type: 'VariableDeclarator',
|
|
1370
|
+
id: { type: 'Identifier', name: intermediateVarName },
|
|
1371
|
+
init: null
|
|
1372
|
+
}],
|
|
1373
|
+
kind: 'let'
|
|
1374
|
+
};
|
|
1037
1375
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1376
|
+
let beginCallIndex = -1;
|
|
1377
|
+
for (let i = 0; i < functionNode.body.body.length; i++) {
|
|
1378
|
+
const stmt = functionNode.body.body[i];
|
|
1379
|
+
if (stmt.type === 'ExpressionStatement' &&
|
|
1380
|
+
stmt.expression?.type === 'CallExpression' &&
|
|
1381
|
+
stmt.expression?.callee?.type === 'MemberExpression' &&
|
|
1382
|
+
stmt.expression?.callee?.property?.name === 'begin') {
|
|
1383
|
+
const beginExprString = escodegen.generate(stmt.expression.callee.object);
|
|
1384
|
+
if (beginExprString === exprString) {
|
|
1385
|
+
beginCallIndex = i;
|
|
1386
|
+
break;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1047
1390
|
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
}],
|
|
1055
|
-
kind: 'const'
|
|
1056
|
-
});
|
|
1391
|
+
// Insert intermediate variable after .begin() if found, otherwise at the start
|
|
1392
|
+
if (beginCallIndex !== -1) {
|
|
1393
|
+
functionNode.body.body.splice(beginCallIndex + 1, 0, intermediateVarDecl);
|
|
1394
|
+
} else {
|
|
1395
|
+
functionNode.body.body.unshift(intermediateVarDecl);
|
|
1396
|
+
}
|
|
1057
1397
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1398
|
+
// 2. Transform all .set() calls to assignments
|
|
1399
|
+
const transformSetToAssignment = {
|
|
1400
|
+
CallExpression(node, state, ancestors) {
|
|
1401
|
+
// Check if this is a .set() call for this hook
|
|
1402
|
+
if (node.callee?.type === 'MemberExpression' &&
|
|
1403
|
+
node.callee?.property?.name === 'set' &&
|
|
1404
|
+
node.callee?.object) {
|
|
1405
|
+
const currentExprString = escodegen.generate(node.callee.object);
|
|
1406
|
+
if (currentExprString === exprString && node.arguments.length > 0) {
|
|
1407
|
+
// Find the parent statement
|
|
1408
|
+
let parentStmt = null;
|
|
1409
|
+
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
1410
|
+
if (ancestors[i].type === 'ExpressionStatement') {
|
|
1411
|
+
parentStmt = ancestors[i];
|
|
1412
|
+
break;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1061
1415
|
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1416
|
+
if (parentStmt) {
|
|
1417
|
+
// Replace the .set() call with an assignment
|
|
1418
|
+
parentStmt.type = 'ExpressionStatement';
|
|
1419
|
+
parentStmt.expression = {
|
|
1420
|
+
type: 'AssignmentExpression',
|
|
1421
|
+
operator: '=',
|
|
1422
|
+
left: { type: 'Identifier', name: intermediateVarName },
|
|
1423
|
+
right: node.arguments[0]
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1071
1426
|
}
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1072
1430
|
|
|
1073
|
-
|
|
1074
|
-
let rightExpr = { type: 'Identifier', name: blockVar };
|
|
1075
|
-
for (const part of parts) {
|
|
1076
|
-
rightExpr = {
|
|
1077
|
-
type: 'MemberExpression',
|
|
1078
|
-
object: rightExpr,
|
|
1079
|
-
property: { type: 'Identifier', name: part },
|
|
1080
|
-
computed: false
|
|
1081
|
-
};
|
|
1082
|
-
}
|
|
1431
|
+
ancestor(functionNode.body, transformSetToAssignment);
|
|
1083
1432
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
}
|
|
1433
|
+
// 3. Find the .end() call and insert final .set() call right before it
|
|
1434
|
+
const finalSetCall = {
|
|
1435
|
+
type: 'ExpressionStatement',
|
|
1436
|
+
expression: {
|
|
1437
|
+
type: 'CallExpression',
|
|
1438
|
+
callee: {
|
|
1439
|
+
type: 'MemberExpression',
|
|
1440
|
+
object: JSON.parse(JSON.stringify(hookObjectNode)), // Deep copy the original node
|
|
1441
|
+
property: { type: 'Identifier', name: 'set' },
|
|
1442
|
+
computed: false
|
|
1443
|
+
},
|
|
1444
|
+
arguments: [{ type: 'Identifier', name: intermediateVarName }]
|
|
1445
|
+
}
|
|
1446
|
+
};
|
|
1447
|
+
|
|
1448
|
+
// Find the .end() call for this hook
|
|
1449
|
+
let endCallIndex = -1;
|
|
1450
|
+
for (let i = 0; i < functionNode.body.body.length; i++) {
|
|
1451
|
+
const stmt = functionNode.body.body[i];
|
|
1452
|
+
if (stmt.type === 'ExpressionStatement' &&
|
|
1453
|
+
stmt.expression?.type === 'CallExpression' &&
|
|
1454
|
+
stmt.expression?.callee?.type === 'MemberExpression' &&
|
|
1455
|
+
stmt.expression?.callee?.property?.name === 'end') {
|
|
1456
|
+
const endExprString = escodegen.generate(stmt.expression.callee.object);
|
|
1457
|
+
if (endExprString === exprString) {
|
|
1458
|
+
endCallIndex = i;
|
|
1459
|
+
break;
|
|
1093
1460
|
}
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1094
1463
|
|
|
1095
|
-
|
|
1096
|
-
|
|
1464
|
+
// Insert the final .set() call before .end() if found, otherwise at the end
|
|
1465
|
+
if (endCallIndex !== -1) {
|
|
1466
|
+
functionNode.body.body.splice(endCallIndex, 0, finalSetCall);
|
|
1467
|
+
} else {
|
|
1468
|
+
// If no .end() found, insert before return statement or at the end
|
|
1469
|
+
const lastStatement = functionNode.body.body[functionNode.body.body.length - 1];
|
|
1470
|
+
if (lastStatement && lastStatement.type === 'ReturnStatement') {
|
|
1471
|
+
functionNode.body.body.splice(functionNode.body.body.length - 1, 0, finalSetCall);
|
|
1097
1472
|
} else {
|
|
1098
|
-
|
|
1099
|
-
node.type = 'ExpressionStatement';
|
|
1100
|
-
node.expression = {
|
|
1101
|
-
type: 'CallExpression',
|
|
1102
|
-
callee: {
|
|
1103
|
-
type: 'Identifier',
|
|
1104
|
-
name: '__p5.strandsFor'
|
|
1105
|
-
},
|
|
1106
|
-
arguments: [initialFunction, conditionFunction, updateFunction, bodyFunction, {
|
|
1107
|
-
type: 'ObjectExpression',
|
|
1108
|
-
properties: []
|
|
1109
|
-
}]
|
|
1110
|
-
};
|
|
1473
|
+
functionNode.body.body.push(finalSetCall);
|
|
1111
1474
|
}
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1112
1478
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1479
|
+
// Main transformation pass: find and transform functions with .set() calls in control flow
|
|
1480
|
+
function transformSetCallsInControlFlow(ast) {
|
|
1481
|
+
const functionsToTransform = [];
|
|
1482
|
+
|
|
1483
|
+
// Collect functions that have .set() calls in control flow
|
|
1484
|
+
const collectFunctions = {
|
|
1485
|
+
ArrowFunctionExpression(node, ancestors) {
|
|
1486
|
+
if (functionHasSetInControlFlow(node)) {
|
|
1487
|
+
functionsToTransform.push(node);
|
|
1488
|
+
}
|
|
1116
1489
|
},
|
|
1490
|
+
FunctionExpression(node, ancestors) {
|
|
1491
|
+
if (functionHasSetInControlFlow(node)) {
|
|
1492
|
+
functionsToTransform.push(node);
|
|
1493
|
+
}
|
|
1494
|
+
},
|
|
1495
|
+
FunctionDeclaration(node, ancestors) {
|
|
1496
|
+
if (functionHasSetInControlFlow(node)) {
|
|
1497
|
+
functionsToTransform.push(node);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
};
|
|
1117
1501
|
|
|
1118
|
-
|
|
1119
|
-
replaceIdentifierReferences(node, oldName, newName) {
|
|
1120
|
-
if (!node || typeof node !== 'object') return node;
|
|
1502
|
+
ancestor(ast, collectFunctions);
|
|
1121
1503
|
|
|
1122
|
-
|
|
1123
|
-
|
|
1504
|
+
// Transform each collected function
|
|
1505
|
+
for (const funcNode of functionsToTransform) {
|
|
1506
|
+
transformFunctionSetCalls(funcNode);
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1124
1509
|
|
|
1125
|
-
|
|
1126
|
-
|
|
1510
|
+
// Main transformation pass: find and transform helper functions with early returns
|
|
1511
|
+
function transformHelperFunctionEarlyReturns(ast) {
|
|
1512
|
+
const helperFunctionsToTransform = [];
|
|
1513
|
+
|
|
1514
|
+
// Collect helper functions that need transformation
|
|
1515
|
+
const collectHelperFunctions = {
|
|
1516
|
+
VariableDeclarator(node, ancestors) {
|
|
1517
|
+
const init = node.init;
|
|
1518
|
+
if (init && (init.type === 'ArrowFunctionExpression' || init.type === 'FunctionExpression')) {
|
|
1519
|
+
if (functionHasEarlyReturns(init)) {
|
|
1520
|
+
helperFunctionsToTransform.push(init);
|
|
1127
1521
|
}
|
|
1522
|
+
}
|
|
1523
|
+
},
|
|
1524
|
+
FunctionDeclaration(node, ancestors) {
|
|
1525
|
+
if (functionHasEarlyReturns(node)) {
|
|
1526
|
+
helperFunctionsToTransform.push(node);
|
|
1527
|
+
}
|
|
1528
|
+
},
|
|
1529
|
+
// Don't transform functions that are direct arguments to call expressions
|
|
1530
|
+
CallExpression(node, ancestors) {
|
|
1531
|
+
// Arguments to CallExpressions are base callbacks, not helpers
|
|
1532
|
+
// We skip them by not adding them to the transformation list
|
|
1533
|
+
}
|
|
1534
|
+
};
|
|
1128
1535
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
newNode[key] = replaceInNode(n[key]);
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
return newNode;
|
|
1141
|
-
};
|
|
1536
|
+
ancestor(ast, collectHelperFunctions);
|
|
1537
|
+
|
|
1538
|
+
// Transform each collected helper function
|
|
1539
|
+
for (const funcNode of helperFunctionsToTransform) {
|
|
1540
|
+
transformHelperFunction(funcNode);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1142
1543
|
|
|
1143
|
-
|
|
1544
|
+
function transpileStrandsToJS(p5, sourceString, srcLocations, scope) {
|
|
1545
|
+
// Reset counters at the start of each transpilation
|
|
1546
|
+
blockVarCounter = 0;
|
|
1547
|
+
loopVarCounter = 0;
|
|
1548
|
+
|
|
1549
|
+
const ast = parse(sourceString, {
|
|
1550
|
+
ecmaVersion: 2021,
|
|
1551
|
+
locations: srcLocations
|
|
1552
|
+
});
|
|
1553
|
+
|
|
1554
|
+
// First pass: transform .set() calls in control flow to use intermediate variables
|
|
1555
|
+
transformSetCallsInControlFlow(ast);
|
|
1556
|
+
|
|
1557
|
+
// Second pass: transform everything except if/for statements using normal ancestor traversal
|
|
1558
|
+
const nonControlFlowCallbacks = { ...ASTCallbacks };
|
|
1559
|
+
delete nonControlFlowCallbacks.IfStatement;
|
|
1560
|
+
delete nonControlFlowCallbacks.ForStatement;
|
|
1561
|
+
ancestor(ast, nonControlFlowCallbacks, undefined, { varyings: {} });
|
|
1562
|
+
|
|
1563
|
+
// Third pass: transform helper functions with early returns to use __returnValue pattern
|
|
1564
|
+
transformHelperFunctionEarlyReturns(ast);
|
|
1565
|
+
|
|
1566
|
+
// Fourth pass: transform if/for statements in post-order using recursive traversal
|
|
1567
|
+
const postOrderControlFlowTransform = {
|
|
1568
|
+
IfStatement(node, state, c) {
|
|
1569
|
+
state.inControlFlow++;
|
|
1570
|
+
// First recursively process children
|
|
1571
|
+
if (node.test) c(node.test, state);
|
|
1572
|
+
if (node.consequent) c(node.consequent, state);
|
|
1573
|
+
if (node.alternate) c(node.alternate, state);
|
|
1574
|
+
// Then apply the transformation to this node
|
|
1575
|
+
ASTCallbacks.IfStatement(node, state, []);
|
|
1576
|
+
state.inControlFlow--;
|
|
1577
|
+
},
|
|
1578
|
+
ForStatement(node, state, c) {
|
|
1579
|
+
state.inControlFlow++;
|
|
1580
|
+
// First recursively process children
|
|
1581
|
+
if (node.init) c(node.init, state);
|
|
1582
|
+
if (node.test) c(node.test, state);
|
|
1583
|
+
if (node.update) c(node.update, state);
|
|
1584
|
+
if (node.body) c(node.body, state);
|
|
1585
|
+
// Then apply the transformation to this node
|
|
1586
|
+
ASTCallbacks.ForStatement(node, state, []);
|
|
1587
|
+
state.inControlFlow--;
|
|
1588
|
+
},
|
|
1589
|
+
ReturnStatement(node, state, c) {
|
|
1590
|
+
if (!state.inControlFlow) return;
|
|
1591
|
+
// Convert return statement to strandsEarlyReturn call
|
|
1592
|
+
node.type = 'ExpressionStatement';
|
|
1593
|
+
node.expression = {
|
|
1594
|
+
type: 'CallExpression',
|
|
1595
|
+
callee: {
|
|
1596
|
+
type: 'Identifier',
|
|
1597
|
+
name: '__p5.strandsEarlyReturn'
|
|
1598
|
+
},
|
|
1599
|
+
arguments: node.argument ? [node.argument] : []
|
|
1600
|
+
};
|
|
1601
|
+
delete node.argument;
|
|
1144
1602
|
}
|
|
1145
1603
|
};
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
//
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
state.inControlFlow--;
|
|
1182
|
-
},
|
|
1183
|
-
ReturnStatement(node, state, c) {
|
|
1184
|
-
if (!state.inControlFlow) return;
|
|
1185
|
-
// Convert return statement to strandsEarlyReturn call
|
|
1186
|
-
node.type = 'ExpressionStatement';
|
|
1187
|
-
node.expression = {
|
|
1188
|
-
type: 'CallExpression',
|
|
1189
|
-
callee: {
|
|
1190
|
-
type: 'Identifier',
|
|
1191
|
-
name: '__p5.strandsEarlyReturn'
|
|
1192
|
-
},
|
|
1193
|
-
arguments: node.argument ? [node.argument] : []
|
|
1194
|
-
};
|
|
1195
|
-
delete node.argument;
|
|
1196
|
-
}
|
|
1197
|
-
};
|
|
1198
|
-
recursive(ast, { varyings: {}, inControlFlow: 0 }, postOrderControlFlowTransform);
|
|
1199
|
-
const transpiledSource = escodegen.generate(ast);
|
|
1200
|
-
const scopeKeys = Object.keys(scope);
|
|
1201
|
-
const match = /\(?\s*(?:function)?\s*\w*\s*\(([^)]*)\)\s*(?:=>)?\s*{((?:.|\n)*)}\s*;?\s*\)?/
|
|
1202
|
-
.exec(transpiledSource);
|
|
1203
|
-
if (!match) {
|
|
1204
|
-
console.log(transpiledSource);
|
|
1205
|
-
throw new Error('Could not parse p5.strands function!');
|
|
1206
|
-
}
|
|
1207
|
-
const params = match[1].split(/,\s*/).filter(param => !!param.trim());
|
|
1208
|
-
let paramVals, paramNames;
|
|
1209
|
-
if (params.length > 0) {
|
|
1210
|
-
paramNames = params;
|
|
1211
|
-
paramVals = [scope];
|
|
1212
|
-
} else {
|
|
1213
|
-
paramNames = scopeKeys;
|
|
1214
|
-
paramVals = scopeKeys.map(key => scope[key]);
|
|
1215
|
-
}
|
|
1216
|
-
const body = match[2];
|
|
1217
|
-
try {
|
|
1218
|
-
const internalStrandsCallback = new Function(
|
|
1219
|
-
// Create a parameter called __p5, not just p5, because users of instance mode
|
|
1220
|
-
// may pass in a variable called p5 as a scope variable. If we rely on a variable called
|
|
1221
|
-
// p5, then the scope variable called p5 might accidentally override internal function
|
|
1222
|
-
// calls to p5 static methods.
|
|
1223
|
-
'__p5',
|
|
1224
|
-
...paramNames,
|
|
1225
|
-
body,
|
|
1226
|
-
);
|
|
1227
|
-
return () => internalStrandsCallback(p5, ...paramVals);
|
|
1228
|
-
} catch (e) {
|
|
1229
|
-
console.error(e);
|
|
1230
|
-
console.log(paramNames);
|
|
1231
|
-
console.log(body);
|
|
1232
|
-
throw new Error('Error transpiling p5.strands callback!');
|
|
1233
|
-
}
|
|
1604
|
+
recursive(ast, { varyings: {}, inControlFlow: 0 }, postOrderControlFlowTransform);
|
|
1605
|
+
const transpiledSource = escodegen.generate(ast);
|
|
1606
|
+
const scopeKeys = Object.keys(scope);
|
|
1607
|
+
const match = /\(?\s*(?:function)?\s*\w*\s*\(([^)]*)\)\s*(?:=>)?\s*{((?:.|\n)*)}\s*;?\s*\)?/
|
|
1608
|
+
.exec(transpiledSource);
|
|
1609
|
+
if (!match) {
|
|
1610
|
+
console.log(transpiledSource);
|
|
1611
|
+
throw new Error('Could not parse p5.strands function!');
|
|
1612
|
+
}
|
|
1613
|
+
const params = match[1].split(/,\s*/).filter(param => !!param.trim());
|
|
1614
|
+
let paramVals, paramNames;
|
|
1615
|
+
if (params.length > 0) {
|
|
1616
|
+
paramNames = params;
|
|
1617
|
+
paramVals = [scope];
|
|
1618
|
+
} else {
|
|
1619
|
+
paramNames = scopeKeys;
|
|
1620
|
+
paramVals = scopeKeys.map(key => scope[key]);
|
|
1621
|
+
}
|
|
1622
|
+
const body = match[2];
|
|
1623
|
+
try {
|
|
1624
|
+
const internalStrandsCallback = new Function(
|
|
1625
|
+
// Create a parameter called __p5, not just p5, because users of instance mode
|
|
1626
|
+
// may pass in a variable called p5 as a scope variable. If we rely on a variable called
|
|
1627
|
+
// p5, then the scope variable called p5 might accidentally override internal function
|
|
1628
|
+
// calls to p5 static methods.
|
|
1629
|
+
'__p5',
|
|
1630
|
+
...paramNames,
|
|
1631
|
+
body,
|
|
1632
|
+
);
|
|
1633
|
+
return () => internalStrandsCallback(p5, ...paramVals);
|
|
1634
|
+
} catch (e) {
|
|
1635
|
+
console.error(e);
|
|
1636
|
+
console.log(paramNames);
|
|
1637
|
+
console.log(body);
|
|
1638
|
+
throw new Error('Error transpiling p5.strands callback!');
|
|
1234
1639
|
}
|
|
1640
|
+
}
|
|
1235
1641
|
|
|
1236
1642
|
export { transpileStrandsToJS };
|