@@ -130,22 +130,19 @@ const ASTCallbacks = {
130130 if ( ancestors . some ( nodeIsUniform ) ) { return ; }
131131 if ( _state . varyings [ node . name ]
132132 && ! ancestors . some ( a => a . type === 'AssignmentExpression' && a . left === node ) ) {
133- node . type = 'ExpressionStatement' ;
134- node . expression = {
135- type : 'CallExpression' ,
136- callee : {
137- type : 'MemberExpression' ,
138- object : {
139- type : 'Identifier' ,
140- name : node . name
141- } ,
142- property : {
143- type : 'Identifier' ,
144- name : 'getValue'
145- } ,
133+ node . type = 'CallExpression' ;
134+ node . callee = {
135+ type : 'MemberExpression' ,
136+ object : {
137+ type : 'Identifier' ,
138+ name : node . name
146139 } ,
147- arguments : [ ] ,
148- }
140+ property : {
141+ type : 'Identifier' ,
142+ name : 'getValue'
143+ } ,
144+ } ;
145+ node . arguments = [ ] ;
149146 }
150147 } ,
151148 // The callbacks for AssignmentExpression and BinaryExpression handle
@@ -208,13 +205,12 @@ const ASTCallbacks = {
208205 varyingName = node . left . object . name ;
209206 }
210207 // Check if it's a getValue() call: myVarying.getValue().xyz
211- else if ( node . left . object . type === 'ExpressionStatement' &&
212- node . left . object . expression ?. type === 'CallExpression' &&
213- node . left . object . expression . callee ?. type === 'MemberExpression' &&
214- node . left . object . expression . callee . property ?. name === 'getValue' &&
215- node . left . object . expression . callee . object ?. type === 'Identifier' &&
216- _state . varyings [ node . left . object . expression . callee . object . name ] ) {
217- varyingName = node . left . object . expression . callee . object . name ;
208+ else if ( node . left . object . type === 'CallExpression' &&
209+ node . left . object . callee ?. type === 'MemberExpression' &&
210+ node . left . object . callee . property ?. name === 'getValue' &&
211+ node . left . object . callee . object ?. type === 'Identifier' &&
212+ _state . varyings [ node . left . object . callee . object . name ] ) {
213+ varyingName = node . left . object . callee . object . name ;
218214 }
219215
220216 if ( varyingName ) {
@@ -595,7 +591,7 @@ const ASTCallbacks = {
595591
596592 // Transform for statement into strandsFor() call
597593 // for (init; test; update) body -> strandsFor(initCb, conditionCb, updateCb, bodyCb, initialVars)
598-
594+
599595 // Generate unique loop variable name
600596 const uniqueLoopVar = `loopVar${ loopVarCounter ++ } ` ;
601597
@@ -714,46 +710,71 @@ const ASTCallbacks = {
714710
715711 // Analyze which outer scope variables are assigned in the loop body
716712 const assignedVars = new Set ( ) ;
717- const analyzeBlock = ( body , parentLocalVars = new Set ( ) ) => {
718- if ( body . type !== 'BlockStatement' ) return ;
719713
720- // First pass: collect variable declarations within this block
721- const localVars = new Set ( [ ...parentLocalVars ] ) ;
722- for ( const stmt of body . body ) {
714+ // Helper function to check if a block contains strands control flow calls as immediate children
715+ const blockContainsStrandsControlFlow = ( node ) => {
716+ if ( node . type !== 'BlockStatement' ) return false ;
717+ return node . body . some ( stmt => {
718+ // Check for variable declarations with strands control flow init
723719 if ( stmt . type === 'VariableDeclaration' ) {
724- for ( const decl of stmt . declarations ) {
725- if ( decl . id . type === 'Identifier' ) {
726- localVars . add ( decl . id . name ) ;
727- }
728- }
720+ const match = stmt . declarations . some ( decl =>
721+ decl . init ?. type === 'CallExpression' &&
722+ (
723+ (
724+ decl . init ?. callee ?. type === 'MemberExpression' &&
725+ decl . init ?. callee ?. object ?. type === 'Identifier' &&
726+ decl . init ?. callee ?. object ?. name === '__p5' &&
727+ ( decl . init ?. callee ?. property ?. name === 'strandsFor' ||
728+ decl . init ?. callee ?. property ?. name === 'strandsIf' )
729+ ) ||
730+ (
731+ decl . init ?. callee ?. type === 'Identifier' &&
732+ ( decl . init ?. callee ?. name === '__p5.strandsFor' ||
733+ decl . init ?. callee ?. name === '__p5.strandsIf' )
734+ )
735+ )
736+ ) ;
737+ return match
738+ }
739+ return false ;
740+ } ) ;
741+ } ;
742+
743+ // First pass: collect all variable declarations in the body
744+ const localVars = new Set ( ) ;
745+ ancestor ( bodyFunction . body , {
746+ VariableDeclarator ( node , ancestors ) {
747+ // Skip if we're inside a block that contains strands control flow
748+ if ( ancestors . some ( blockContainsStrandsControlFlow ) ) return ;
749+ if ( node . id . type === 'Identifier' ) {
750+ localVars . add ( node . id . name ) ;
729751 }
730752 }
753+ } ) ;
731754
732- // Second pass: find assignments to non-local variables
733- for ( const stmt of body . body ) {
734- if ( stmt . type === 'ExpressionStatement' &&
735- stmt . expression . type === 'AssignmentExpression' ) {
736- const left = stmt . expression . left ;
737- if ( left . type === 'Identifier' ) {
738- // Direct variable assignment: x = value
739- if ( ! localVars . has ( left . name ) ) {
740- assignedVars . add ( left . name ) ;
741- }
742- } else if ( left . type === 'MemberExpression' &&
743- left . object . type === 'Identifier' ) {
744- // Property assignment: obj.prop = value (includes swizzles)
745- if ( ! localVars . has ( left . object . name ) ) {
746- assignedVars . add ( left . object . name ) ;
747- }
755+ // Second pass: find assignments to non-local variables using acorn-walk
756+ ancestor ( bodyFunction . body , {
757+ AssignmentExpression ( node , ancestors ) {
758+ // Skip if we're inside a block that contains strands control flow
759+ if ( ancestors . some ( blockContainsStrandsControlFlow ) ) {
760+ return
761+ }
762+
763+ const left = node . left ;
764+ if ( left . type === 'Identifier' ) {
765+ // Direct variable assignment: x = value
766+ if ( ! localVars . has ( left . name ) ) {
767+ assignedVars . add ( left . name ) ;
768+ }
769+ } else if ( left . type === 'MemberExpression' &&
770+ left . object . type === 'Identifier' ) {
771+ // Property assignment: obj.prop = value (includes swizzles)
772+ if ( ! localVars . has ( left . object . name ) ) {
773+ assignedVars . add ( left . object . name ) ;
748774 }
749- } else if ( stmt . type === 'BlockStatement' ) {
750- // Recursively analyze nested block statements, passing down local vars
751- analyzeBlock ( stmt , localVars ) ;
752775 }
753776 }
754- } ;
755-
756- analyzeBlock ( bodyFunction . body ) ;
777+ } ) ;
757778
758779 if ( assignedVars . size > 0 ) {
759780 // Add copying, reference replacement, and return statements similar to if statements
@@ -959,7 +980,7 @@ const ASTCallbacks = {
959980 // Reset counters at the start of each transpilation
960981 blockVarCounter = 0 ;
961982 loopVarCounter = 0 ;
962-
983+
963984 const ast = parse ( sourceString , {
964985 ecmaVersion : 2021 ,
965986 locations : srcLocations
@@ -992,18 +1013,37 @@ const ASTCallbacks = {
9921013 recursive ( ast , { varyings : { } } , postOrderControlFlowTransform ) ;
9931014 const transpiledSource = escodegen . generate ( ast ) ;
9941015 const scopeKeys = Object . keys ( scope ) ;
995- const internalStrandsCallback = new Function (
996- // Create a parameter called __p5, not just p5, because users of instance mode
997- // may pass in a variable called p5 as a scope variable. If we rely on a variable called
998- // p5, then the scope variable called p5 might accidentally override internal function
999- // calls to p5 static methods.
1000- '__p5' ,
1001- ...scopeKeys ,
1002- transpiledSource
1003- . slice (
1004- transpiledSource . indexOf ( '{' ) + 1 ,
1005- transpiledSource . lastIndexOf ( '}' )
1006- ) . replaceAll ( ';' , '' )
1007- ) ;
1008- return ( ) => internalStrandsCallback ( p5 , ...scopeKeys . map ( key => scope [ key ] ) ) ;
1016+ const match = / \( ? \s * (?: f u n c t i o n ) ? \s * \( ( [ ^ ) ] * ) \) \s * (?: = > ) ? \s * { ( (?: .| \n ) * ) } \s * ; ? \s * \) ? /
1017+ . exec ( transpiledSource ) ;
1018+ if ( ! match ) {
1019+ console . log ( transpiledSource ) ;
1020+ throw new Error ( 'Could not parse p5.strands function!' ) ;
1021+ }
1022+ const params = match [ 1 ] . split ( / , \s * / ) . filter ( param => ! ! param . trim ( ) ) ;
1023+ let paramVals , paramNames ;
1024+ if ( params . length > 0 ) {
1025+ paramNames = params ;
1026+ paramVals = [ scope ] ;
1027+ } else {
1028+ paramNames = scopeKeys ;
1029+ paramVals = scopeKeys . map ( key => scope [ key ] ) ;
1030+ }
1031+ const body = match [ 2 ] ;
1032+ try {
1033+ const internalStrandsCallback = new Function (
1034+ // Create a parameter called __p5, not just p5, because users of instance mode
1035+ // may pass in a variable called p5 as a scope variable. If we rely on a variable called
1036+ // p5, then the scope variable called p5 might accidentally override internal function
1037+ // calls to p5 static methods.
1038+ '__p5' ,
1039+ ...paramNames ,
1040+ body ,
1041+ ) ;
1042+ return ( ) => internalStrandsCallback ( p5 , ...paramVals ) ;
1043+ } catch ( e ) {
1044+ console . error ( e ) ;
1045+ console . log ( paramNames ) ;
1046+ console . log ( body ) ;
1047+ throw new Error ( 'Error transpiling p5.strands callback!' ) ;
1048+ }
10091049 }
0 commit comments