1313use function count ;
1414use function max ;
1515use PhpParser \Node \Expr ;
16- use PhpParser \Node \Expr \Assign ;
17- use PhpParser \Node \Expr \AssignOp ;
18- use PhpParser \Node \Expr \BinaryOp ;
1916use PhpParser \Node \Expr \BinaryOp \BooleanAnd ;
20- use PhpParser \Node \Expr \BinaryOp \BooleanOr ;
21- use PhpParser \Node \Expr \BinaryOp \Coalesce ;
22- use PhpParser \Node \Expr \BinaryOp \LogicalAnd ;
23- use PhpParser \Node \Expr \BinaryOp \LogicalOr ;
24- use PhpParser \Node \Expr \BooleanNot ;
25- use PhpParser \Node \Expr \Cast ;
26- use PhpParser \Node \Expr \Match_ ;
27- use PhpParser \Node \Expr \Ternary ;
28- use PhpParser \Node \Expr \UnaryMinus ;
29- use PhpParser \Node \Expr \UnaryPlus ;
3017use PhpParser \Node \Name ;
3118use PhpParser \Node \Stmt ;
3219use PhpParser \Node \Stmt \Block ;
@@ -84,7 +71,7 @@ private function statements(array $statements, int $ft, int $st): array
8471 private function statement (Stmt $ stmt , int $ ft , int $ st ): array
8572 {
8673 if ($ stmt instanceof Expression) {
87- $ p = $ this -> expressionPaths ($ stmt ->expr )['p ' ];
74+ $ p = ExpressionPathAnalyzer:: expressionPaths ($ stmt ->expr )['p ' ];
8875
8976 return ['ft ' => $ p * $ ft , 'bp ' => 0 , 'cp ' => 0 , 'rp ' => 0 ];
9077 }
@@ -94,7 +81,7 @@ private function statement(Stmt $stmt, int $ft, int $st): array
9481 return ['ft ' => 0 , 'bp ' => 0 , 'cp ' => 0 , 'rp ' => $ ft ];
9582 }
9683
97- $ p = $ this -> expressionPaths ($ stmt ->expr )['p ' ];
84+ $ p = ExpressionPathAnalyzer:: expressionPaths ($ stmt ->expr )['p ' ];
9885
9986 return ['ft ' => 0 , 'bp ' => 0 , 'cp ' => 0 , 'rp ' => $ p * $ ft ];
10087 }
@@ -148,7 +135,7 @@ private function statement(Stmt $stmt, int $ft, int $st): array
148135 */
149136 private function processIf (If_ $ stmt , int $ ft , int $ st ): array
150137 {
151- ['t ' => $ t , 'f ' => $ f , 'p ' => $ p ] = $ this -> expressionPaths ($ stmt ->cond );
138+ ['t ' => $ t , 'f ' => $ f , 'p ' => $ p ] = ExpressionPathAnalyzer:: expressionPaths ($ stmt ->cond );
152139
153140 if ($ stmt ->elseifs !== []) {
154141 // Desugar elseif chains to nested if/else
@@ -200,7 +187,7 @@ private function processIf(If_ $stmt, int $ft, int $st): array
200187 */
201188 private function processSwitch (Switch_ $ stmt , int $ ft , int $ st ): array
202189 {
203- $ p = $ this -> expressionPaths ($ stmt ->cond )['p ' ];
190+ $ p = ExpressionPathAnalyzer:: expressionPaths ($ stmt ->cond )['p ' ];
204191 $ switchSt = $ p * $ ft ;
205192 $ hasDefault = false ;
206193
@@ -256,8 +243,8 @@ private function processSwitchBody(array $cases, int $ft, int $st): array
256243 */
257244 private function processWhile (While_ $ stmt , int $ ft , int $ st ): array
258245 {
259- ['t ' => $ t , 'f ' => $ f , 'p ' => $ p ] = $ this -> expressionPaths ($ stmt ->cond );
260- ['tf ' => $ tf ] = $ this -> expressionPathsDouble ($ stmt ->cond );
246+ ['t ' => $ t , 'f ' => $ f , 'p ' => $ p ] = ExpressionPathAnalyzer:: expressionPaths ($ stmt ->cond );
247+ ['tf ' => $ tf ] = ExpressionPathAnalyzer:: expressionPathsDouble ($ stmt ->cond );
261248 ['ft ' => $ ftS , 'bp ' => $ bpS , 'cp ' => $ cpS , 'rp ' => $ rpS ] = $ this ->statements ($ stmt ->stmts , $ ft , $ st );
262249
263250 $ ftOut = $ f * $ ft + $ bpS * $ t + ($ ftS + $ cpS ) * $ tf ;
@@ -270,7 +257,7 @@ private function processWhile(While_ $stmt, int $ft, int $st): array
270257 */
271258 private function processDo (Do_ $ stmt , int $ ft , int $ st ): array
272259 {
273- ['f ' => $ f ] = $ this -> expressionPaths ($ stmt ->cond );
260+ ['f ' => $ f ] = ExpressionPathAnalyzer:: expressionPaths ($ stmt ->cond );
274261 ['ft ' => $ ftS , 'bp ' => $ bpS , 'cp ' => $ cpS , 'rp ' => $ rpS ] = $ this ->statements ($ stmt ->stmts , $ ft , $ st );
275262
276263 $ ftOut = $ f * $ ftS + $ bpS ;
@@ -286,7 +273,7 @@ private function processFor(For_ $stmt, int $ft, int $st): array
286273 // Desugar: for(E1; E2; E3) S => E1; while(E2) { S; E3; }
287274 // Process init expressions
288275 foreach ($ stmt ->init as $ initExpr ) {
289- $ ft = $ this -> expressionPaths ($ initExpr )['p ' ] * $ ft ;
276+ $ ft = ExpressionPathAnalyzer:: expressionPaths ($ initExpr )['p ' ] * $ ft ;
290277 }
291278
292279 // Build the condition: use first cond expression, or treat as leaf if empty
@@ -308,8 +295,8 @@ private function processFor(For_ $stmt, int $ft, int $st): array
308295 $ bodyStmts [] = new Expression ($ loopExpr );
309296 }
310297
311- ['t ' => $ t , 'f ' => $ f , 'p ' => $ p ] = $ this -> expressionPaths ($ condExpr );
312- ['tf ' => $ tf ] = $ this -> expressionPathsDouble ($ condExpr );
298+ ['t ' => $ t , 'f ' => $ f , 'p ' => $ p ] = ExpressionPathAnalyzer:: expressionPaths ($ condExpr );
299+ ['tf ' => $ tf ] = ExpressionPathAnalyzer:: expressionPathsDouble ($ condExpr );
313300 ['ft ' => $ ftS , 'bp ' => $ bpS , 'cp ' => $ cpS , 'rp ' => $ rpS ] = $ this ->statements ($ bodyStmts , $ ft , $ st );
314301
315302 $ ftOut = $ f * $ ft + $ bpS * $ t + ($ ftS + $ cpS ) * $ tf ;
@@ -362,203 +349,4 @@ private function processTryCatch(TryCatch $stmt, int $ft, int $st): array
362349
363350 return ['ft ' => $ ftOut , 'bp ' => $ bp , 'cp ' => $ cp , 'rp ' => $ rp ];
364351 }
365-
366- /**
367- * Returns {t, f, p} for an expression.
368- *
369- * t = paths that evaluate to true
370- * f = paths that evaluate to false
371- * p = total paths (t + f for boolean, or just paths for non-boolean)
372- *
373- * @return array{t: int, f: int, p: int}
374- */
375- private function expressionPaths (Expr $ expr ): array
376- {
377- if ($ expr instanceof BooleanNot) {
378- ['t ' => $ t , 'f ' => $ f , 'p ' => $ p ] = $ this ->expressionPaths ($ expr ->expr );
379-
380- return ['t ' => $ f , 'f ' => $ t , 'p ' => $ p ];
381- }
382-
383- if ($ expr instanceof BooleanAnd || $ expr instanceof LogicalAnd) {
384- ['t ' => $ t1 , 'f ' => $ f1 , 'p ' => $ p1 ] = $ this ->expressionPaths ($ expr ->left );
385- ['t ' => $ t2 , 'f ' => $ f2 , 'p ' => $ p2 ] = $ this ->expressionPaths ($ expr ->right );
386-
387- return [
388- 't ' => $ t1 * $ t2 ,
389- 'f ' => $ f1 + $ t1 * $ f2 ,
390- 'p ' => $ f1 + $ t1 * $ p2 ,
391- ];
392- }
393-
394- if ($ expr instanceof BooleanOr || $ expr instanceof LogicalOr) {
395- ['t ' => $ t1 , 'f ' => $ f1 , 'p ' => $ p1 ] = $ this ->expressionPaths ($ expr ->left );
396- ['t ' => $ t2 , 'f ' => $ f2 , 'p ' => $ p2 ] = $ this ->expressionPaths ($ expr ->right );
397-
398- return [
399- 't ' => $ t1 + $ f1 * $ t2 ,
400- 'f ' => $ f1 * $ f2 ,
401- 'p ' => $ t1 + $ f1 * $ p2 ,
402- ];
403- }
404-
405- if ($ expr instanceof Ternary) {
406- if ($ expr ->if === null ) {
407- // Elvis operator (E1 ?: E2) - same as ||
408- ['t ' => $ t1 , 'f ' => $ f1 , 'p ' => $ p1 ] = $ this ->expressionPaths ($ expr ->cond );
409- ['t ' => $ t2 , 'f ' => $ f2 , 'p ' => $ p2 ] = $ this ->expressionPaths ($ expr ->else );
410-
411- return [
412- 't ' => $ t1 + $ f1 * $ t2 ,
413- 'f ' => $ f1 * $ f2 ,
414- 'p ' => $ t1 + $ f1 * $ p2 ,
415- ];
416- }
417-
418- ['t ' => $ t1 , 'f ' => $ f1 , 'p ' => $ p1 ] = $ this ->expressionPaths ($ expr ->cond );
419- ['t ' => $ t2 , 'f ' => $ f2 , 'p ' => $ p2 ] = $ this ->expressionPaths ($ expr ->if );
420- ['t ' => $ t3 , 'f ' => $ f3 , 'p ' => $ p3 ] = $ this ->expressionPaths ($ expr ->else );
421-
422- return [
423- 't ' => $ t1 * $ t2 + $ f1 * $ t3 ,
424- 'f ' => $ t1 * $ f2 + $ f1 * $ f3 ,
425- 'p ' => $ t1 * $ p2 + $ f1 * $ p3 ,
426- ];
427- }
428-
429- if ($ expr instanceof Coalesce) {
430- // Same as ||
431- ['t ' => $ t1 , 'f ' => $ f1 , 'p ' => $ p1 ] = $ this ->expressionPaths ($ expr ->left );
432- ['t ' => $ t2 , 'f ' => $ f2 , 'p ' => $ p2 ] = $ this ->expressionPaths ($ expr ->right );
433-
434- return [
435- 't ' => $ t1 + $ f1 * $ t2 ,
436- 'f ' => $ f1 * $ f2 ,
437- 'p ' => $ t1 + $ f1 * $ p2 ,
438- ];
439- }
440-
441- if ($ expr instanceof Match_) {
442- $ totalP = 0 ;
443-
444- foreach ($ expr ->arms as $ arm ) {
445- if ($ arm ->conds !== null ) {
446- foreach ($ arm ->conds as $ cond ) {
447- $ totalP += $ this ->expressionPaths ($ cond )['p ' ];
448- }
449- }
450-
451- $ totalP += $ this ->expressionPaths ($ arm ->body )['p ' ];
452- }
453-
454- return ['t ' => $ totalP , 'f ' => $ totalP , 'p ' => $ totalP ];
455- }
456-
457- if ($ expr instanceof Assign || $ expr instanceof AssignOp) {
458- $ pVar = $ this ->expressionPaths ($ expr ->var )['p ' ];
459- $ pExpr = $ this ->expressionPaths ($ expr ->expr )['p ' ];
460- $ p = $ pVar * $ pExpr ;
461-
462- return ['t ' => $ p , 'f ' => $ p , 'p ' => $ p ];
463- }
464-
465- if ($ expr instanceof BinaryOp) {
466- $ p1 = $ this ->expressionPaths ($ expr ->left )['p ' ];
467- $ p2 = $ this ->expressionPaths ($ expr ->right )['p ' ];
468- $ p = $ p1 * $ p2 ;
469-
470- return ['t ' => $ p , 'f ' => $ p , 'p ' => $ p ];
471- }
472-
473- if ($ expr instanceof Cast || $ expr instanceof UnaryMinus || $ expr instanceof UnaryPlus) {
474- $ p = $ this ->expressionPaths ($ expr ->expr )['p ' ];
475-
476- return ['t ' => $ p , 'f ' => $ p , 'p ' => $ p ];
477- }
478-
479- // Leaf expression
480- return ['t ' => 1 , 'f ' => 1 , 'p ' => 1 ];
481- }
482-
483- /**
484- * Returns {tt, tf, ff, pp} for double-traversal path counting.
485- *
486- * Needed for while/do-while/foreach loop conditions.
487- *
488- * @return array{tt: int, tf: int, ff: int, pp: int}
489- */
490- private function expressionPathsDouble (Expr $ expr ): array
491- {
492- if ($ expr instanceof BooleanNot) {
493- ['tt ' => $ tt , 'tf ' => $ tf , 'ff ' => $ ff , 'pp ' => $ pp ] = $ this ->expressionPathsDouble ($ expr ->expr );
494-
495- return ['tt ' => $ ff , 'tf ' => $ tf , 'ff ' => $ tt , 'pp ' => $ pp ];
496- }
497-
498- if ($ expr instanceof BooleanAnd || $ expr instanceof LogicalAnd) {
499- ['t ' => $ t1 , 'f ' => $ f1 , 'p ' => $ p1 ] = $ this ->expressionPaths ($ expr ->left );
500- ['t ' => $ t2 , 'f ' => $ f2 , 'p ' => $ p2 ] = $ this ->expressionPaths ($ expr ->right );
501- ['tt ' => $ tt1 , 'tf ' => $ tf1 , 'ff ' => $ ff1 , 'pp ' => $ pp1 ] = $ this ->expressionPathsDouble ($ expr ->left );
502- ['tt ' => $ tt2 , 'tf ' => $ tf2 , 'ff ' => $ ff2 , 'pp ' => $ pp2 ] = $ this ->expressionPathsDouble ($ expr ->right );
503-
504- return [
505- 'tt ' => $ tt1 * $ tt2 ,
506- 'tf ' => $ tf1 * $ t2 + $ tt1 * $ tf2 ,
507- 'ff ' => $ ff1 + 2 * $ tf1 * $ f2 + $ tt1 * $ ff2 ,
508- 'pp ' => $ ff1 + 2 * $ tf1 * $ p2 + $ tt1 * $ pp2 ,
509- ];
510- }
511-
512- if ($ expr instanceof BooleanOr || $ expr instanceof LogicalOr) {
513- ['t ' => $ t1 , 'f ' => $ f1 , 'p ' => $ p1 ] = $ this ->expressionPaths ($ expr ->left );
514- ['t ' => $ t2 , 'f ' => $ f2 , 'p ' => $ p2 ] = $ this ->expressionPaths ($ expr ->right );
515- ['tt ' => $ tt1 , 'tf ' => $ tf1 , 'ff ' => $ ff1 , 'pp ' => $ pp1 ] = $ this ->expressionPathsDouble ($ expr ->left );
516- ['tt ' => $ tt2 , 'tf ' => $ tf2 , 'ff ' => $ ff2 , 'pp ' => $ pp2 ] = $ this ->expressionPathsDouble ($ expr ->right );
517-
518- return [
519- 'tt ' => $ tt1 + 2 * $ tf1 * $ t2 + $ ff1 * $ tt2 ,
520- 'tf ' => $ tf1 * $ f2 + $ ff1 * $ tf2 ,
521- 'ff ' => $ ff1 * $ ff2 ,
522- 'pp ' => $ tt1 + 2 * $ tf1 * $ p2 + $ ff1 * $ pp2 ,
523- ];
524- }
525-
526- if ($ expr instanceof Ternary) {
527- if ($ expr ->if === null ) {
528- // Elvis operator - same as ||
529- ['t ' => $ t1 , 'f ' => $ f1 , 'p ' => $ p1 ] = $ this ->expressionPaths ($ expr ->cond );
530- ['t ' => $ t2 , 'f ' => $ f2 , 'p ' => $ p2 ] = $ this ->expressionPaths ($ expr ->else );
531- ['tt ' => $ tt1 , 'tf ' => $ tf1 , 'ff ' => $ ff1 , 'pp ' => $ pp1 ] = $ this ->expressionPathsDouble ($ expr ->cond );
532- ['tt ' => $ tt2 , 'tf ' => $ tf2 , 'ff ' => $ ff2 , 'pp ' => $ pp2 ] = $ this ->expressionPathsDouble ($ expr ->else );
533-
534- return [
535- 'tt ' => $ tt1 + 2 * $ tf1 * $ t2 + $ ff1 * $ tt2 ,
536- 'tf ' => $ tf1 * $ f2 + $ ff1 * $ tf2 ,
537- 'ff ' => $ ff1 * $ ff2 ,
538- 'pp ' => $ tt1 + 2 * $ tf1 * $ p2 + $ ff1 * $ pp2 ,
539- ];
540- }
541-
542- ['t ' => $ t1 , 'f ' => $ f1 , 'p ' => $ p1 ] = $ this ->expressionPaths ($ expr ->cond );
543- ['t ' => $ t2 , 'f ' => $ f2 , 'p ' => $ p2 ] = $ this ->expressionPaths ($ expr ->if );
544- ['t ' => $ t3 , 'f ' => $ f3 , 'p ' => $ p3 ] = $ this ->expressionPaths ($ expr ->else );
545- ['tt ' => $ tt1 , 'tf ' => $ tf1 , 'ff ' => $ ff1 , 'pp ' => $ pp1 ] = $ this ->expressionPathsDouble ($ expr ->cond );
546- ['tt ' => $ tt2 , 'tf ' => $ tf2 , 'ff ' => $ ff2 , 'pp ' => $ pp2 ] = $ this ->expressionPathsDouble ($ expr ->if );
547- ['tt ' => $ tt3 , 'tf ' => $ tf3 , 'ff ' => $ ff3 , 'pp ' => $ pp3 ] = $ this ->expressionPathsDouble ($ expr ->else );
548-
549- return [
550- 'tt ' => $ tt1 * $ tt2 + 2 * $ tf1 * $ t2 * $ t3 + $ ff1 * $ tt3 ,
551- 'tf ' => $ tt1 * $ tf2 + $ ff1 * $ tf3 + $ tf1 * ($ t2 * $ f3 + $ f2 * $ t3 ),
552- 'ff ' => $ tt1 * $ ff2 + 2 * $ tf1 * $ f2 * $ f3 + $ ff1 * $ ff3 ,
553- 'pp ' => $ tt1 * $ pp2 + 2 * $ tf1 * $ p2 * $ p3 + $ ff1 * $ pp3 ,
554- ];
555- }
556-
557- // For non-boolean expressions (comparisons, arithmetic, assignments, etc.),
558- // use single-traversal path count as tf. This correctly handles comparisons
559- // like $i < 10 used as while conditions.
560- $ p = $ this ->expressionPaths ($ expr )['p ' ];
561-
562- return ['tt ' => 0 , 'tf ' => $ p , 'ff ' => 0 , 'pp ' => 0 ];
563- }
564352}
0 commit comments