resolveBoundWithSubst resolves a temporal bound, substituting variables and handling 'now'.
(bound ast.TemporalBound, subst unionfind.UnionFind, evalTime time.Time)
| 517 | |
| 518 | // resolveBoundWithSubst resolves a temporal bound, substituting variables and handling 'now'. |
| 519 | func resolveBoundWithSubst(bound ast.TemporalBound, subst unionfind.UnionFind, evalTime time.Time) (ast.TemporalBound, error) { |
| 520 | switch bound.Type { |
| 521 | case ast.TimestampBound: |
| 522 | return bound, nil |
| 523 | |
| 524 | case ast.VariableBound: |
| 525 | // Look up the variable in the substitution |
| 526 | term := subst.Get(bound.Variable) |
| 527 | if term == nil { |
| 528 | return ast.TemporalBound{}, fmt.Errorf("variable %v not bound in substitution", bound.Variable.Symbol) |
| 529 | } |
| 530 | |
| 531 | // The term should be a constant (either a number representing nanoseconds or a pair for an interval) |
| 532 | switch t := term.(type) { |
| 533 | case ast.Constant: |
| 534 | // If it's a number, interpret as nanoseconds since epoch |
| 535 | if t.Type == ast.NumberType { |
| 536 | nano, err := t.NumberValue() |
| 537 | if err != nil { |
| 538 | return ast.TemporalBound{}, fmt.Errorf("failed to get number value: %w", err) |
| 539 | } |
| 540 | return ast.TemporalBound{Type: ast.TimestampBound, Timestamp: nano}, nil |
| 541 | } |
| 542 | if t.Type == ast.TimeType { |
| 543 | nano, err := t.TimeValue() |
| 544 | if err != nil { |
| 545 | return ast.TemporalBound{}, fmt.Errorf("failed to get time value: %w", err) |
| 546 | } |
| 547 | return ast.TemporalBound{Type: ast.TimestampBound, Timestamp: nano}, nil |
| 548 | } |
| 549 | return ast.TemporalBound{}, fmt.Errorf("expected number or time constant for temporal bound, got %v", t.Type) |
| 550 | case ast.Variable: |
| 551 | // Variable is still unbound |
| 552 | return ast.TemporalBound{}, fmt.Errorf("variable %v not fully resolved", t.Symbol) |
| 553 | default: |
| 554 | return ast.TemporalBound{}, fmt.Errorf("unexpected term type for temporal bound: %T", term) |
| 555 | } |
| 556 | |
| 557 | case ast.NegativeInfinityBound, ast.PositiveInfinityBound: |
| 558 | return bound, nil |
| 559 | |
| 560 | case ast.NowBound: |
| 561 | // 'now' resolves to the current evaluation time |
| 562 | return ast.NewTimestampBound(evalTime), nil |
| 563 | |
| 564 | default: |
| 565 | return ast.TemporalBound{}, fmt.Errorf("unknown bound type: %v", bound.Type) |
| 566 | } |
| 567 | } |
| 568 | |
| 569 | // DerivedTemporalFact represents a temporal fact derived from a rule. |
| 570 | type DerivedTemporalFact struct { |
no test coverage detected