@@ -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 ) {
@@ -275,6 +271,37 @@ const ASTCallbacks = {
275271 } ;
276272 node . arguments = [ node . right ] ;
277273 } ,
274+ LogicalExpression ( node , _state , ancestors ) {
275+ // Don't convert uniform default values to node methods, as
276+ // they should be evaluated at runtime, not compiled.
277+ if ( ancestors . some ( nodeIsUniform ) ) { return ; }
278+ // If the left hand side of an expression is one of these types,
279+ // we should construct a node from it.
280+ const unsafeTypes = [ 'Literal' , 'ArrayExpression' , 'Identifier' ] ;
281+ if ( unsafeTypes . includes ( node . left . type ) ) {
282+ const leftReplacementNode = {
283+ type : 'CallExpression' ,
284+ callee : {
285+ type : 'Identifier' ,
286+ name : '__p5.strandsNode' ,
287+ } ,
288+ arguments : [ node . left ]
289+ }
290+ node . left = leftReplacementNode ;
291+ }
292+ // Replace the logical operator with a call expression
293+ // in other words a call to BaseNode.or(), .and() etc.
294+ node . type = 'CallExpression' ;
295+ node . callee = {
296+ type : 'MemberExpression' ,
297+ object : node . left ,
298+ property : {
299+ type : 'Identifier' ,
300+ name : replaceBinaryOperator ( node . operator ) ,
301+ } ,
302+ } ;
303+ node . arguments = [ node . right ] ;
304+ } ,
278305 IfStatement ( node , _state , ancestors ) {
279306 if ( ancestors . some ( nodeIsUniform ) ) { return ; }
280307 // Transform if statement into strandsIf() call
@@ -564,7 +591,7 @@ const ASTCallbacks = {
564591
565592 // Transform for statement into strandsFor() call
566593 // for (init; test; update) body -> strandsFor(initCb, conditionCb, updateCb, bodyCb, initialVars)
567-
594+
568595 // Generate unique loop variable name
569596 const uniqueLoopVar = `loopVar${ loopVarCounter ++ } ` ;
570597
@@ -683,46 +710,71 @@ const ASTCallbacks = {
683710
684711 // Analyze which outer scope variables are assigned in the loop body
685712 const assignedVars = new Set ( ) ;
686- const analyzeBlock = ( body , parentLocalVars = new Set ( ) ) => {
687- if ( body . type !== 'BlockStatement' ) return ;
688713
689- // First pass: collect variable declarations within this block
690- const localVars = new Set ( [ ...parentLocalVars ] ) ;
691- 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
692719 if ( stmt . type === 'VariableDeclaration' ) {
693- for ( const decl of stmt . declarations ) {
694- if ( decl . id . type === 'Identifier' ) {
695- localVars . add ( decl . id . name ) ;
696- }
697- }
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 ) ;
698751 }
699752 }
753+ } ) ;
700754
701- // Second pass: find assignments to non-local variables
702- for ( const stmt of body . body ) {
703- if ( stmt . type === 'ExpressionStatement' &&
704- stmt . expression . type === 'AssignmentExpression' ) {
705- const left = stmt . expression . left ;
706- if ( left . type === 'Identifier' ) {
707- // Direct variable assignment: x = value
708- if ( ! localVars . has ( left . name ) ) {
709- assignedVars . add ( left . name ) ;
710- }
711- } else if ( left . type === 'MemberExpression' &&
712- left . object . type === 'Identifier' ) {
713- // Property assignment: obj.prop = value (includes swizzles)
714- if ( ! localVars . has ( left . object . name ) ) {
715- assignedVars . add ( left . object . name ) ;
716- }
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 ) ;
717774 }
718- } else if ( stmt . type === 'BlockStatement' ) {
719- // Recursively analyze nested block statements, passing down local vars
720- analyzeBlock ( stmt , localVars ) ;
721775 }
722776 }
723- } ;
724-
725- analyzeBlock ( bodyFunction . body ) ;
777+ } ) ;
726778
727779 if ( assignedVars . size > 0 ) {
728780 // Add copying, reference replacement, and return statements similar to if statements
@@ -928,7 +980,7 @@ const ASTCallbacks = {
928980 // Reset counters at the start of each transpilation
929981 blockVarCounter = 0 ;
930982 loopVarCounter = 0 ;
931-
983+
932984 const ast = parse ( sourceString , {
933985 ecmaVersion : 2021 ,
934986 locations : srcLocations
@@ -961,18 +1013,37 @@ const ASTCallbacks = {
9611013 recursive ( ast , { varyings : { } } , postOrderControlFlowTransform ) ;
9621014 const transpiledSource = escodegen . generate ( ast ) ;
9631015 const scopeKeys = Object . keys ( scope ) ;
964- const internalStrandsCallback = new Function (
965- // Create a parameter called __p5, not just p5, because users of instance mode
966- // may pass in a variable called p5 as a scope variable. If we rely on a variable called
967- // p5, then the scope variable called p5 might accidentally override internal function
968- // calls to p5 static methods.
969- '__p5' ,
970- ...scopeKeys ,
971- transpiledSource
972- . slice (
973- transpiledSource . indexOf ( '{' ) + 1 ,
974- transpiledSource . lastIndexOf ( '}' )
975- ) . replaceAll ( ';' , '' )
976- ) ;
977- 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+ }
9781049 }
0 commit comments