(n *OrNode)
| 315 | } |
| 316 | |
| 317 | func optimizeOrNode(n *OrNode) ConditionNode { |
| 318 | // Bottom-up: optimise children first. |
| 319 | left := optimizeNode(n.Left) |
| 320 | right := optimizeNode(n.Right) |
| 321 | |
| 322 | // Flatten OR chains: when either child is already an OrNode or DisjunctionNode, |
| 323 | // collect all terms and delegate to the DisjunctionNode optimiser, which |
| 324 | // performs dedup, false-filtering and true short-circuit across the whole chain. |
| 325 | _, leftIsOr := left.(*OrNode) |
| 326 | _, leftIsDisj := left.(*DisjunctionNode) |
| 327 | _, rightIsOr := right.(*OrNode) |
| 328 | _, rightIsDisj := right.(*DisjunctionNode) |
| 329 | if leftIsOr || leftIsDisj || rightIsOr || rightIsDisj { |
| 330 | terms := append(collectOrTerms(left), collectOrTerms(right)...) |
| 331 | expressionOptimizerLog.Printf("OR flatten: collected %d terms", len(terms)) |
| 332 | return optimizeDisjunctionNode(&DisjunctionNode{Terms: terms}) |
| 333 | } |
| 334 | |
| 335 | // Annihilation: A || true → true |
| 336 | if isBoolLiteral(left, true) || isBoolLiteral(right, true) { |
| 337 | expressionOptimizerLog.Printf("OR annihilation: %s || %s → true", left.Render(), right.Render()) |
| 338 | return &BooleanLiteralNode{Value: true} |
| 339 | } |
| 340 | |
| 341 | // Identity: A || false → A |
| 342 | if isBoolLiteral(right, false) { |
| 343 | expressionOptimizerLog.Printf("OR identity (right false): %s || false → %s", left.Render(), left.Render()) |
| 344 | return left |
| 345 | } |
| 346 | if isBoolLiteral(left, false) { |
| 347 | expressionOptimizerLog.Printf("OR identity (left false): false || %s → %s", right.Render(), right.Render()) |
| 348 | return right |
| 349 | } |
| 350 | |
| 351 | // Skip idempotent / complement rules when status functions are present. |
| 352 | if containsStatusFunc(left) || containsStatusFunc(right) { |
| 353 | return &OrNode{Left: left, Right: right} |
| 354 | } |
| 355 | |
| 356 | // Idempotent: A || A → A |
| 357 | if nodesEqual(left, right) { |
| 358 | expressionOptimizerLog.Printf("OR idempotent: %s || %s → %s", left.Render(), right.Render(), left.Render()) |
| 359 | return left |
| 360 | } |
| 361 | |
| 362 | // Complement: A || !A → true |
| 363 | if isNegationOf(left, right) { |
| 364 | expressionOptimizerLog.Printf("OR complement: %s || %s → true", left.Render(), right.Render()) |
| 365 | return &BooleanLiteralNode{Value: true} |
| 366 | } |
| 367 | |
| 368 | // Absorption (OR): A || (A && B) → A |
| 369 | // If one side is an AND-chain that contains the other side as a conjunct, |
| 370 | // the AND-chain is absorbed by the simpler operand. |
| 371 | for _, pair := range [][2]ConditionNode{{left, right}, {right, left}} { |
| 372 | simple, complex := pair[0], pair[1] |
| 373 | if termSubsumedBy(complex, simple) { |
| 374 | expressionOptimizerLog.Printf("OR absorption: %s || (%s) → %s (absorbed)", |
no test coverage detected