(n *NotNode)
| 381 | } |
| 382 | |
| 383 | func optimizeNotNode(n *NotNode) ConditionNode { |
| 384 | // Bottom-up: optimise child first. |
| 385 | child := optimizeNode(n.Child) |
| 386 | |
| 387 | // Constant folding: !true → false, !false → true |
| 388 | if lit, ok := child.(*BooleanLiteralNode); ok { |
| 389 | expressionOptimizerLog.Printf("NOT constant folding: !%v → %v", lit.Value, !lit.Value) |
| 390 | return &BooleanLiteralNode{Value: !lit.Value} |
| 391 | } |
| 392 | |
| 393 | // Double negation: !!A → A |
| 394 | if notChild, ok := child.(*NotNode); ok { |
| 395 | expressionOptimizerLog.Printf("NOT double negation: !!%s → %s", notChild.Child.Render(), notChild.Child.Render()) |
| 396 | // Recurse so that the result of eliminating the double negation is |
| 397 | // itself a candidate for further simplification. |
| 398 | return optimizeNode(notChild.Child) |
| 399 | } |
| 400 | |
| 401 | // De Morgan: !(A && B) → !A || !B |
| 402 | // Only applied when neither operand contains a status function, since |
| 403 | // rearranging status functions changes execution semantics. |
| 404 | if andChild, ok := child.(*AndNode); ok && !containsStatusFunc(andChild) { |
| 405 | expressionOptimizerLog.Printf("NOT De Morgan (AND): !(%s && %s) → !%s || !%s", |
| 406 | andChild.Left.Render(), andChild.Right.Render(), |
| 407 | andChild.Left.Render(), andChild.Right.Render()) |
| 408 | return optimizeNode(&OrNode{ |
| 409 | Left: &NotNode{Child: andChild.Left}, |
| 410 | Right: &NotNode{Child: andChild.Right}, |
| 411 | }) |
| 412 | } |
| 413 | |
| 414 | // De Morgan: !(A || B) → !A && !B |
| 415 | if orChild, ok := child.(*OrNode); ok && !containsStatusFunc(orChild) { |
| 416 | expressionOptimizerLog.Printf("NOT De Morgan (OR): !(%s || %s) → !%s && !%s", |
| 417 | orChild.Left.Render(), orChild.Right.Render(), |
| 418 | orChild.Left.Render(), orChild.Right.Render()) |
| 419 | return optimizeNode(&AndNode{ |
| 420 | Left: &NotNode{Child: orChild.Left}, |
| 421 | Right: &NotNode{Child: orChild.Right}, |
| 422 | }) |
| 423 | } |
| 424 | |
| 425 | // De Morgan: !(A || B || ...) → !A && !B && ... (DisjunctionNode form) |
| 426 | // Move the empty-terms guard before the containsStatusFunc call to avoid |
| 427 | // an unnecessary tree walk when the disjunction is empty. |
| 428 | if disjChild, ok := child.(*DisjunctionNode); ok { |
| 429 | if len(disjChild.Terms) == 0 { |
| 430 | return &NotNode{Child: child} |
| 431 | } |
| 432 | if !containsStatusFunc(disjChild) { |
| 433 | expressionOptimizerLog.Printf("NOT De Morgan (Disjunction): !(disjunction[%d]) → AND chain of negations", len(disjChild.Terms)) |
| 434 | negations := make([]ConditionNode, len(disjChild.Terms)) |
| 435 | for i, term := range disjChild.Terms { |
| 436 | negations[i] = &NotNode{Child: term} |
| 437 | } |
| 438 | return optimizeNode(rebuildAndChain(negations)) |
| 439 | } |
| 440 | } |
no test coverage detected