diff --git a/analysis/temporal.go b/analysis/temporal.go new file mode 100644 index 0000000..6538649 --- /dev/null +++ b/analysis/temporal.go @@ -0,0 +1,217 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package analysis + +import ( + "fmt" + + "github.com/google/mangle/ast" +) + +// TemporalWarning represents a warning about potentially problematic temporal rules. +type TemporalWarning struct { + // Predicate is the predicate that may cause issues. + Predicate ast.PredicateSym + // Message describes the potential problem. + Message string + // Severity indicates how serious the warning is. + Severity WarningSeverity +} + +// WarningSeverity indicates the severity of a warning. +type WarningSeverity int + +const ( + // SeverityInfo is for informational warnings that may not indicate a problem. + SeverityInfo WarningSeverity = iota + // SeverityWarning is for warnings that may cause performance issues or unexpected behavior. + SeverityWarning + // SeverityCritical is for warnings that are likely to cause non-termination or errors. + SeverityCritical +) + +func (s WarningSeverity) String() string { + switch s { + case SeverityInfo: + return "info" + case SeverityWarning: + return "warning" + case SeverityCritical: + return "critical" + default: + return "unknown" + } +} + +func (w TemporalWarning) String() string { + return fmt.Sprintf("[%s] %s: %s", w.Severity, w.Predicate.Symbol, w.Message) +} + +// CheckTemporalRecursion analyzes a program for potentially problematic recursive temporal rules. +// Returns a list of warnings about patterns that may cause non-termination or performance issues. +func CheckTemporalRecursion(programInfo *ProgramInfo) []TemporalWarning { + var warnings []TemporalWarning + + // Build a set of temporal predicates + temporalPreds := make(map[ast.PredicateSym]bool) + for pred, decl := range programInfo.Decls { + if decl.IsTemporal() { + temporalPreds[pred] = true + } + } + + if len(temporalPreds) == 0 { + return nil // No temporal predicates, nothing to check + } + + // Build dependency graph using existing depGraph type from stratification.go + dep := buildTemporalDepGraph(programInfo) + + // Find strongly connected components using existing sccs() method + sccs := dep.sccs() + + // Check each SCC for problematic patterns + for _, scc := range sccs { + if len(scc) == 1 { + // Single predicate - check for self-recursion + for pred := range scc { + // Check if there's a self-loop (edge from pred to itself) + if edges, ok := dep[pred]; ok { + if _, hasSelfLoop := edges[pred]; hasSelfLoop { + // Self-recursive temporal predicate + if temporalPreds[pred] { + warnings = append(warnings, TemporalWarning{ + Predicate: pred, + Message: "self-recursive temporal predicate may cause interval explosion; ensure coalescing or use interval limits", + Severity: SeverityWarning, + }) + } + } + } + } + } else { + // Multi-predicate SCC - mutual recursion + hasTemporalPred := false + for pred := range scc { + if temporalPreds[pred] { + hasTemporalPred = true + break + } + } + if hasTemporalPred { + // Get first predicate for the warning message + var firstPred ast.PredicateSym + for pred := range scc { + firstPred = pred + break + } + warnings = append(warnings, TemporalWarning{ + Predicate: firstPred, + Message: fmt.Sprintf("mutual recursion through temporal predicates may cause non-termination; %d predicates in cycle", len(scc)), + Severity: SeverityCritical, + }) + } + } + } + + // Check for future operators in recursive rules (especially problematic) + for _, rule := range programInfo.Rules { + headPred := rule.Head.Predicate + if !temporalPreds[headPred] { + continue + } + + for _, premise := range rule.Premises { + if tl, ok := premise.(ast.TemporalLiteral); ok { + if tl.Operator != nil { + // Check if this is a future operator + if tl.Operator.Type == ast.DiamondPlus || tl.Operator.Type == ast.BoxPlus { + // Check if the literal references a predicate in the same SCC + var litPred ast.PredicateSym + switch lit := tl.Literal.(type) { + case ast.Atom: + litPred = lit.Predicate + case ast.NegAtom: + litPred = lit.Atom.Predicate + } + if litPred.Symbol != "" && isInSameSCC(headPred, litPred, sccs) { + warnings = append(warnings, TemporalWarning{ + Predicate: headPred, + Message: "future operator (<+ or [+) in recursive temporal rule may cause unbounded fact generation", + Severity: SeverityCritical, + }) + } + } + } + } + } + } + + return warnings +} + +// buildTemporalDepGraph builds a dependency graph for the program using the existing depGraph type. +func buildTemporalDepGraph(programInfo *ProgramInfo) depGraph { + dep := make(depGraph) + + // Initialize nodes for all IDB predicates + for pred := range programInfo.IdbPredicates { + dep.initNode(pred) + } + + // Build edges from rule dependencies + for _, rule := range programInfo.Rules { + headPred := rule.Head.Predicate + dep.initNode(headPred) + + for _, premise := range rule.Premises { + var bodyPred ast.PredicateSym + switch p := premise.(type) { + case ast.Atom: + bodyPred = p.Predicate + case ast.NegAtom: + bodyPred = p.Atom.Predicate + case ast.TemporalLiteral: + switch lit := p.Literal.(type) { + case ast.Atom: + bodyPred = lit.Predicate + case ast.NegAtom: + bodyPred = lit.Atom.Predicate + } + default: + continue + } + + // Only track dependencies to IDB predicates + if _, isIDB := programInfo.IdbPredicates[bodyPred]; isIDB { + dep.addEdge(headPred, bodyPred, false) + } + } + } + + return dep +} + +// isInSameSCC checks if two predicates are in the same strongly connected component. +func isInSameSCC(pred1, pred2 ast.PredicateSym, sccs []Nodeset) bool { + for _, scc := range sccs { + _, has1 := scc[pred1] + _, has2 := scc[pred2] + if has1 && has2 { + return true + } + } + return false +} diff --git a/analysis/temporal_test.go b/analysis/temporal_test.go new file mode 100644 index 0000000..81dcdeb --- /dev/null +++ b/analysis/temporal_test.go @@ -0,0 +1,125 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package analysis + +import ( + "strings" + "testing" + + "github.com/google/mangle/parse" +) + +func TestCheckTemporalRecursion(t *testing.T) { + tests := []struct { + name string + program string + wantWarnings int + wantSeverity WarningSeverity + wantMsgContains string + }{ + { + name: "no temporal predicates", + program: ` + foo(X) :- bar(X). + bar(1). + `, + wantWarnings: 0, + }, + { + name: "non-recursive temporal predicate", + program: ` + Decl active(X) temporal bound [/name]. + Decl base_active(X) bound [/name]. + active(X) :- base_active(X). + base_active(/alice). + `, + wantWarnings: 0, + }, + { + name: "self-recursive temporal predicate", + program: ` + Decl derived(X) temporal bound [/name]. + Decl some_condition(X) bound [/name]. + derived(X) :- derived(X), some_condition(X). + some_condition(/alice). + `, + wantWarnings: 1, + wantSeverity: SeverityWarning, + wantMsgContains: "self-recursive", + }, + { + name: "mutual recursion with temporal", + program: ` + Decl foo(X) temporal bound [/name]. + Decl bar(X) temporal bound [/name]. + foo(X) :- bar(X). + bar(X) :- foo(X). + bar(/alice). + `, + wantWarnings: 1, + wantSeverity: SeverityCritical, + wantMsgContains: "mutual recursion", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + unit, err := parse.Unit(strings.NewReader(tt.program)) + if err != nil { + t.Fatalf("parse error: %v", err) + } + + programInfo, err := AnalyzeOneUnit(unit, nil) + if err != nil { + t.Fatalf("analysis error: %v", err) + } + + warnings := CheckTemporalRecursion(programInfo) + + if len(warnings) != tt.wantWarnings { + t.Errorf("got %d warnings, want %d", len(warnings), tt.wantWarnings) + for _, w := range warnings { + t.Logf("warning: %v", w) + } + } + + if tt.wantWarnings > 0 && len(warnings) > 0 { + if warnings[0].Severity != tt.wantSeverity { + t.Errorf("got severity %v, want %v", warnings[0].Severity, tt.wantSeverity) + } + if tt.wantMsgContains != "" && !strings.Contains(warnings[0].Message, tt.wantMsgContains) { + t.Errorf("warning message %q does not contain %q", warnings[0].Message, tt.wantMsgContains) + } + } + }) + } +} + +func TestWarningSeverityString(t *testing.T) { + tests := []struct { + severity WarningSeverity + want string + }{ + {SeverityInfo, "info"}, + {SeverityWarning, "warning"}, + {SeverityCritical, "critical"}, + } + + for _, tt := range tests { + if got := tt.severity.String(); got != tt.want { + t.Errorf("WarningSeverity(%d).String() = %q, want %q", tt.severity, got, tt.want) + } + } +} diff --git a/ast/ast.go b/ast/ast.go index d0c3079..30900d1 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -1238,15 +1238,21 @@ func (e Ineq) ApplySubst(s Subst) Term { // Clause represents a clause (a rule of the form "A." or "A :- B1, ..., Bn."). // When a clause has a body, the resulting relation can be transformed. +// Clauses may optionally have a temporal annotation on the head. type Clause struct { Head Atom + HeadTime *Interval // Optional temporal annotation (nil means eternal/timeless) Premises []Term Transform *Transform } func (c Clause) String() string { + headStr := c.Head.String() + if c.HeadTime != nil && !c.HeadTime.IsEternal() { + headStr += c.HeadTime.String() + } if c.Premises == nil { - return fmt.Sprintf("%s.", c.Head.String()) + return fmt.Sprintf("%s.", headStr) } var premises strings.Builder for i, p := range c.Premises { @@ -1256,9 +1262,9 @@ func (c Clause) String() string { premises.WriteString(p.String()) } if c.Transform == nil { - return fmt.Sprintf("%s :- %s.", c.Head.String(), premises.String()) + return fmt.Sprintf("%s :- %s.", headStr, premises.String()) } - return fmt.Sprintf("%s :- %s |> %s.", c.Head.String(), premises.String(), c.Transform.String()) + return fmt.Sprintf("%s :- %s |> %s.", headStr, premises.String(), c.Transform.String()) } func (t Transform) String() string { @@ -1295,12 +1301,17 @@ func (c Clause) ReplaceWildcards() Clause { // Wildcards in the rule head are a programmer mistake, // there is no way a wildcard can be bound. This is caught // by validation. - return Clause{c.Head, newPremises, c.Transform} + return Clause{c.Head, c.HeadTime, newPremises, c.Transform} } // NewClause constructs a new clause. func NewClause(head Atom, premises []Term) Clause { - return Clause{head, premises, nil} + return Clause{head, nil, premises, nil} +} + +// NewTemporalClause constructs a new clause with a temporal annotation. +func NewTemporalClause(head Atom, headTime *Interval, premises []Term) Clause { + return Clause{head, headTime, premises, nil} } func hashTerm(s string, args []BaseTerm) uint64 { diff --git a/ast/decl.go b/ast/decl.go index bfe1aad..f62954d 100644 --- a/ast/decl.go +++ b/ast/decl.go @@ -46,6 +46,9 @@ const ( DescrMergePredicate = "merge" // DescrDeferredPredicate is a descriptor for a deferred predicate. DescrDeferredPredicate = "deferred" + // DescrTemporal is a descriptor marking a predicate as temporal. + // Temporal predicates have facts with validity intervals. + DescrTemporal = "temporal" ) // Decl is a declaration. @@ -109,6 +112,12 @@ func (d Decl) IsExtensional() bool { return d.findDescr(DescrExtensional, nil) } +// IsTemporal returns true if decl is for a temporal predicate. +// Temporal predicates have facts with validity intervals. +func (d Decl) IsTemporal() bool { + return d.findDescr(DescrTemporal, nil) +} + // IsDesugared returns true if decl has been desugared. func (d Decl) IsDesugared() bool { return d.findDescr(DescrDesugared, nil) diff --git a/ast/temporal.go b/ast/temporal.go new file mode 100644 index 0000000..3c7c7df --- /dev/null +++ b/ast/temporal.go @@ -0,0 +1,580 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ast + +import ( + "fmt" + "strings" + "sync" + "time" +) + +// defaultTimezone is the timezone used by Date, DateTime, and DateInterval helpers. +// Defaults to UTC. Use SetDefaultTimezone to change it. +var ( + defaultTimezone = time.UTC + defaultTimezoneMu sync.RWMutex +) + +// SetTimezone sets the timezone used by Date, DateTime, DateTimeSec, +// DateInterval, and related helper functions. Defaults to UTC. +// +// Accepts timezone names (IANA format), common abbreviations, or special values: +// - "UTC", "utc" - Coordinated Universal Time +// - "Local", "local" - System local timezone +// - "America/New_York", "America/Los_Angeles", etc. - IANA timezone names +// - "EST", "PST", "CET", etc. - Common abbreviations (mapped to IANA names) +// +// Call this once at program startup to ensure consistent timezone handling. +// +// Example: +// +// ast.SetTimezone("UTC") // Default +// ast.SetTimezone("Local") // System timezone +// ast.SetTimezone("America/New_York") // IANA name +// ast.SetTimezone("PST") // Abbreviation +func SetTimezone(tz string) error { + loc, err := parseTimezone(tz) + if err != nil { + return err + } + defaultTimezoneMu.Lock() + defaultTimezone = loc + defaultTimezoneMu.Unlock() + return nil +} + +// MustSetTimezone is like SetTimezone but panics on error. +// Useful for program initialization where invalid timezone is a fatal error. +func MustSetTimezone(tz string) { + if err := SetTimezone(tz); err != nil { + panic(fmt.Sprintf("ast.MustSetTimezone(%q): %v", tz, err)) + } +} + +// SetDefaultTimezone sets the timezone using a *time.Location directly. +// Prefer SetTimezone(string) for simpler usage. +func SetDefaultTimezone(loc *time.Location) { + if loc == nil { + loc = time.UTC + } + defaultTimezoneMu.Lock() + defaultTimezone = loc + defaultTimezoneMu.Unlock() +} + +// GetDefaultTimezone returns the currently configured default timezone. +func GetDefaultTimezone() *time.Location { + defaultTimezoneMu.RLock() + defer defaultTimezoneMu.RUnlock() + return defaultTimezone +} + +// Common timezone abbreviation mappings +var timezoneAbbreviations = map[string]string{ + // US timezones + "EST": "America/New_York", + "EDT": "America/New_York", + "CST": "America/Chicago", + "CDT": "America/Chicago", + "MST": "America/Denver", + "MDT": "America/Denver", + "PST": "America/Los_Angeles", + "PDT": "America/Los_Angeles", + "AKST": "America/Anchorage", + "AKDT": "America/Anchorage", + "HST": "Pacific/Honolulu", + // European timezones + "GMT": "Europe/London", + "BST": "Europe/London", + "CET": "Europe/Paris", + "CEST": "Europe/Paris", + "EET": "Europe/Helsinki", + "EEST": "Europe/Helsinki", + // Asian timezones + "JST": "Asia/Tokyo", + "KST": "Asia/Seoul", + "CST_CHINA": "Asia/Shanghai", + "IST": "Asia/Kolkata", + // Australian timezones + "AEST": "Australia/Sydney", + "AEDT": "Australia/Sydney", + "AWST": "Australia/Perth", +} + +func parseTimezone(tz string) (*time.Location, error) { + switch strings.ToLower(tz) { + case "utc", "": + return time.UTC, nil + case "local": + return time.Local, nil + } + + // Check abbreviations first + if ianaName, ok := timezoneAbbreviations[strings.ToUpper(tz)]; ok { + return time.LoadLocation(ianaName) + } + + // Try as IANA name directly + return time.LoadLocation(tz) +} + +// TemporalBoundType indicates the kind of temporal bound. +type TemporalBoundType int + +const ( + // TimestampBound is a concrete point in time. + TimestampBound TemporalBoundType = iota + // VariableBound is a variable to be bound during evaluation. + VariableBound + // UnboundedBound represents positive or negative infinity. + UnboundedBound + // NowBound represents the current evaluation time. + NowBound +) + +// TemporalBound represents a point in time, which can be: +// - A concrete timestamp +// - A variable (to be bound during evaluation) +// - Unbounded (positive or negative infinity) +type TemporalBound struct { + Type TemporalBoundType + + // For TimestampBound: the concrete time value. + // Stored as Unix nanoseconds for precision. + Timestamp int64 + + // For VariableBound: the variable name. + Variable Variable + + // For UnboundedBound: true if positive infinity, false if negative infinity. + IsPositiveInf bool +} + +// NewTimestampBound creates a bound from a time.Time value. +func NewTimestampBound(t time.Time) TemporalBound { + return TemporalBound{ + Type: TimestampBound, + Timestamp: t.UnixNano(), + } +} + +// NewVariableBound creates a bound from a variable. +func NewVariableBound(v Variable) TemporalBound { + return TemporalBound{ + Type: VariableBound, + Variable: v, + } +} + +// NegativeInfinity returns a bound representing negative infinity. +func NegativeInfinity() TemporalBound { + return TemporalBound{ + Type: UnboundedBound, + IsPositiveInf: false, + } +} + +// PositiveInfinity returns a bound representing positive infinity. +func PositiveInfinity() TemporalBound { + return TemporalBound{ + Type: UnboundedBound, + IsPositiveInf: true, + } +} + +// Now returns a bound representing the current evaluation time. +func Now() TemporalBound { + return TemporalBound{ + Type: NowBound, + } +} + +// Time returns the time.Time value for a TimestampBound. +// Returns zero time for non-timestamp bounds. +func (tb TemporalBound) Time() time.Time { + if tb.Type != TimestampBound { + return time.Time{} + } + return time.Unix(0, tb.Timestamp) +} + +// Date creates a time.Time for the given year, month, and day at midnight. +// Uses the default timezone (UTC unless changed via SetDefaultTimezone). +func Date(year int, month time.Month, day int) time.Time { + return time.Date(year, month, day, 0, 0, 0, 0, GetDefaultTimezone()) +} + +// DateTime creates a time.Time for the given date and time. +// Uses the default timezone (UTC unless changed via SetDefaultTimezone). +func DateTime(year int, month time.Month, day, hour, min int) time.Time { + return time.Date(year, month, day, hour, min, 0, 0, GetDefaultTimezone()) +} + +// DateTimeSec creates a time.Time for the given date and time with seconds. +// Uses the default timezone (UTC unless changed via SetDefaultTimezone). +func DateTimeSec(year int, month time.Month, day, hour, min, sec int) time.Time { + return time.Date(year, month, day, hour, min, sec, 0, GetDefaultTimezone()) +} + +// DateIn creates a time.Time for the given year, month, and day in a specific timezone. +// Accepts timezone names like "America/New_York", "PST", "UTC", "Local". +// Use this when you need a different timezone than the default for a specific value. +func DateIn(year int, month time.Month, day int, tz string) time.Time { + loc, err := parseTimezone(tz) + if err != nil { + loc = GetDefaultTimezone() + } + return time.Date(year, month, day, 0, 0, 0, 0, loc) +} + +// DateTimeIn creates a time.Time for the given date and time in a specific timezone. +// Accepts timezone names like "America/New_York", "PST", "UTC", "Local". +// Use this when you need a different timezone than the default for a specific value. +func DateTimeIn(year int, month time.Month, day, hour, min int, tz string) time.Time { + loc, err := parseTimezone(tz) + if err != nil { + loc = GetDefaultTimezone() + } + return time.Date(year, month, day, hour, min, 0, 0, loc) +} + +// TimeInterval creates an interval from two time.Time values. +// This is a convenience function for creating intervals without calling NewTimestampBound. +func TimeInterval(start, end time.Time) Interval { + return Interval{ + Start: NewTimestampBound(start), + End: NewTimestampBound(end), + } +} + +// DateInterval creates an interval from two dates (year, month, day). +// This is the most concise way to create a date-based interval. +func DateInterval(startYear int, startMonth time.Month, startDay int, endYear int, endMonth time.Month, endDay int) Interval { + return TimeInterval( + Date(startYear, startMonth, startDay), + Date(endYear, endMonth, endDay), + ) +} + +// String returns a string representation of the temporal bound. +func (tb TemporalBound) String() string { + switch tb.Type { + case TimestampBound: + t := time.Unix(0, tb.Timestamp).UTC() + // Use ISO 8601 format + return t.Format("2006-01-02T15:04:05Z") + case VariableBound: + return tb.Variable.String() + case UnboundedBound: + return "_" + case NowBound: + return "now" + default: + return "?" + } +} + +// Equals returns true if two temporal bounds are equal. +func (tb TemporalBound) Equals(other TemporalBound) bool { + if tb.Type != other.Type { + return false + } + switch tb.Type { + case TimestampBound: + return tb.Timestamp == other.Timestamp + case VariableBound: + return tb.Variable == other.Variable + case UnboundedBound: + return tb.IsPositiveInf == other.IsPositiveInf + case NowBound: + return true // All 'now' bounds are equal + } + return false +} + +// Interval represents a time interval [Start, End]. +// Both endpoints are inclusive. +type Interval struct { + Start TemporalBound + End TemporalBound +} + +// NewInterval creates an interval from two bounds. +func NewInterval(start, end TemporalBound) Interval { + return Interval{Start: start, End: end} +} + +// NewPointInterval creates an interval representing a single point in time. +func NewPointInterval(t time.Time) Interval { + bound := NewTimestampBound(t) + return Interval{Start: bound, End: bound} +} + +// EternalInterval returns an interval representing all time (negative infinity to positive infinity). +// This is used for facts without temporal annotations. +func EternalInterval() Interval { + return Interval{ + Start: NegativeInfinity(), + End: PositiveInfinity(), + } +} + +// IsEternal returns true if this interval represents all time. +func (i Interval) IsEternal() bool { + return i.Start.Type == UnboundedBound && !i.Start.IsPositiveInf && + i.End.Type == UnboundedBound && i.End.IsPositiveInf +} + +// IsPoint returns true if this interval represents a single point in time. +func (i Interval) IsPoint() bool { + return i.Start.Type == TimestampBound && + i.End.Type == TimestampBound && + i.Start.Timestamp == i.End.Timestamp +} + +// String returns a string representation of the interval. +func (i Interval) String() string { + if i.IsEternal() { + return "" // Eternal intervals have no annotation + } + if i.IsPoint() { + return fmt.Sprintf("@[%s]", i.Start.String()) + } + return fmt.Sprintf("@[%s, %s]", i.Start.String(), i.End.String()) +} + +// Equals returns true if two intervals are equal. +func (i Interval) Equals(other Interval) bool { + return i.Start.Equals(other.Start) && i.End.Equals(other.End) +} + +// Contains returns true if time t is within this interval. +// Only works for intervals with concrete timestamp bounds. +func (i Interval) Contains(t time.Time) bool { + tNano := t.UnixNano() + + // Check start bound + switch i.Start.Type { + case TimestampBound: + if tNano < i.Start.Timestamp { + return false + } + case UnboundedBound: + if i.Start.IsPositiveInf { + return false // Start is +inf, nothing can be after it + } + // Start is -inf, all times are after it + case VariableBound: + return false // Can't evaluate with unbound variable + } + + // Check end bound + switch i.End.Type { + case TimestampBound: + if tNano > i.End.Timestamp { + return false + } + case UnboundedBound: + if !i.End.IsPositiveInf { + return false // End is -inf, nothing can be before it + } + // End is +inf, all times are before it + case VariableBound: + return false // Can't evaluate with unbound variable + } + + return true +} + +// Overlaps returns true if this interval overlaps with another. +// Only works for intervals with concrete timestamp bounds. +func (i Interval) Overlaps(other Interval) bool { + // Two intervals overlap if neither ends before the other starts + + // Check if i ends before other starts + if i.End.Type == TimestampBound && other.Start.Type == TimestampBound { + if i.End.Timestamp < other.Start.Timestamp { + return false + } + } + + // Check if other ends before i starts + if other.End.Type == TimestampBound && i.Start.Type == TimestampBound { + if other.End.Timestamp < i.Start.Timestamp { + return false + } + } + + return true +} + +// TemporalOperatorType represents the type of temporal operator. +type TemporalOperatorType int + +const ( + // DiamondMinus: true at some point in the past interval + // Syntax: <-[duration] + DiamondMinus TemporalOperatorType = iota + + // BoxMinus: true continuously throughout the past interval + // Syntax: [-[duration] + BoxMinus + + // DiamondPlus: true at some point in the future interval + // Syntax: <+[duration] + DiamondPlus + + // BoxPlus: true continuously throughout the future interval + // Syntax: [+[duration] + BoxPlus +) + +// TemporalOperator represents a metric temporal operator applied to a literal. +type TemporalOperator struct { + Type TemporalOperatorType + Interval Interval +} + +// String returns the string representation of a temporal operator. +func (op TemporalOperator) String() string { + var prefix string + switch op.Type { + case DiamondMinus: + prefix = "<-" + case BoxMinus: + prefix = "[-" + case DiamondPlus: + prefix = "<+" + case BoxPlus: + prefix = "[+" + } + return fmt.Sprintf("%s[%s, %s]", prefix, op.Interval.Start.String(), op.Interval.End.String()) +} + +// TemporalAtom wraps an Atom with an optional temporal annotation. +// This is used in clause heads and premises to represent temporally-annotated facts. +type TemporalAtom struct { + Atom Atom + Interval *Interval // nil means eternal (no temporal annotation) +} + +func (ta TemporalAtom) isTerm() {} + +// String returns a string representation of the temporal atom. +func (ta TemporalAtom) String() string { + if ta.Interval == nil || ta.Interval.IsEternal() { + return ta.Atom.String() + } + return ta.Atom.String() + ta.Interval.String() +} + +// Equals returns true if two temporal atoms are equal. +func (ta TemporalAtom) Equals(u Term) bool { + other, ok := u.(TemporalAtom) + if !ok { + return false + } + if !ta.Atom.Equals(other.Atom) { + return false + } + // Both nil = equal + if ta.Interval == nil && other.Interval == nil { + return true + } + // One nil, one not = not equal + if ta.Interval == nil || other.Interval == nil { + return false + } + return ta.Interval.Equals(*other.Interval) +} + +// ApplySubst applies a substitution to the temporal atom. +// Note: Interval bounds with VariableBound type are resolved at query time +// by the TemporalEvaluator, not during substitution application. This is +// intentional as interval variable resolution requires temporal context. +func (ta TemporalAtom) ApplySubst(s Subst) Term { + newAtom := ta.Atom.ApplySubst(s).(Atom) + return TemporalAtom{Atom: newAtom, Interval: ta.Interval} +} + +// TemporalLiteral represents a literal (atom or negated atom) with an optional temporal operator. +// This is used in clause premises. +type TemporalLiteral struct { + // The underlying literal (Atom or NegAtom) + Literal Term + + // Optional temporal operator (nil if none) + Operator *TemporalOperator + + // Optional interval binding for the literal's validity time + IntervalVar *Variable +} + +func (tl TemporalLiteral) isTerm() {} + +// String returns a string representation of the temporal literal. +func (tl TemporalLiteral) String() string { + var sb strings.Builder + if tl.Operator != nil { + sb.WriteString(tl.Operator.String()) + sb.WriteString(" ") + } + sb.WriteString(tl.Literal.String()) + if tl.IntervalVar != nil { + sb.WriteString("@") + sb.WriteString(tl.IntervalVar.String()) + } + return sb.String() +} + +// Equals returns true if two temporal literals are equal. +func (tl TemporalLiteral) Equals(u Term) bool { + other, ok := u.(TemporalLiteral) + if !ok { + return false + } + if !tl.Literal.Equals(other.Literal) { + return false + } + // Compare operators + if (tl.Operator == nil) != (other.Operator == nil) { + return false + } + if tl.Operator != nil && *tl.Operator != *other.Operator { + return false + } + // Compare interval variables + if (tl.IntervalVar == nil) != (other.IntervalVar == nil) { + return false + } + if tl.IntervalVar != nil && *tl.IntervalVar != *other.IntervalVar { + return false + } + return true +} + +// ApplySubst applies a substitution to the temporal literal. +// Note: The IntervalVar is a binding variable that captures the fact's validity +// interval during evaluation. It is not substituted here because it is populated +// by the TemporalEvaluator when the literal is matched against temporal facts. +func (tl TemporalLiteral) ApplySubst(s Subst) Term { + newLiteral := tl.Literal.ApplySubst(s) + return TemporalLiteral{ + Literal: newLiteral, + Operator: tl.Operator, + IntervalVar: tl.IntervalVar, + } +} diff --git a/ast/temporal_test.go b/ast/temporal_test.go new file mode 100644 index 0000000..7b1096c --- /dev/null +++ b/ast/temporal_test.go @@ -0,0 +1,560 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ast + +import ( + "testing" + "time" +) + +func TestTemporalBound(t *testing.T) { + tests := []struct { + name string + bound TemporalBound + wantStr string + wantType TemporalBoundType + }{ + { + name: "timestamp bound", + bound: NewTimestampBound(time.Date(2024, 3, 15, 10, 30, 0, 0, time.UTC)), + wantStr: "2024-03-15T10:30:00Z", + wantType: TimestampBound, + }, + { + name: "variable bound", + bound: NewVariableBound(Variable{"T"}), + wantStr: "T", + wantType: VariableBound, + }, + { + name: "negative infinity", + bound: NegativeInfinity(), + wantStr: "_", + wantType: UnboundedBound, + }, + { + name: "positive infinity", + bound: PositiveInfinity(), + wantStr: "_", + wantType: UnboundedBound, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.bound.String(); got != tt.wantStr { + t.Errorf("TemporalBound.String() = %v, want %v", got, tt.wantStr) + } + if tt.bound.Type != tt.wantType { + t.Errorf("TemporalBound.Type = %v, want %v", tt.bound.Type, tt.wantType) + } + }) + } +} + +func TestTemporalBoundEquals(t *testing.T) { + t1 := time.Date(2024, 3, 15, 10, 30, 0, 0, time.UTC) + t2 := time.Date(2024, 3, 15, 10, 30, 0, 0, time.UTC) + t3 := time.Date(2024, 3, 16, 10, 30, 0, 0, time.UTC) + + tests := []struct { + name string + a TemporalBound + b TemporalBound + want bool + }{ + { + name: "same timestamp", + a: NewTimestampBound(t1), + b: NewTimestampBound(t2), + want: true, + }, + { + name: "different timestamps", + a: NewTimestampBound(t1), + b: NewTimestampBound(t3), + want: false, + }, + { + name: "same variable", + a: NewVariableBound(Variable{"T"}), + b: NewVariableBound(Variable{"T"}), + want: true, + }, + { + name: "different variables", + a: NewVariableBound(Variable{"T1"}), + b: NewVariableBound(Variable{"T2"}), + want: false, + }, + { + name: "both negative infinity", + a: NegativeInfinity(), + b: NegativeInfinity(), + want: true, + }, + { + name: "both positive infinity", + a: PositiveInfinity(), + b: PositiveInfinity(), + want: true, + }, + { + name: "negative vs positive infinity", + a: NegativeInfinity(), + b: PositiveInfinity(), + want: false, + }, + { + name: "timestamp vs variable", + a: NewTimestampBound(t1), + b: NewVariableBound(Variable{"T"}), + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.a.Equals(tt.b); got != tt.want { + t.Errorf("TemporalBound.Equals() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestInterval(t *testing.T) { + t1 := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) + t2 := time.Date(2023, 6, 15, 0, 0, 0, 0, time.UTC) + + tests := []struct { + name string + interval Interval + wantStr string + isEternal bool + isPoint bool + }{ + { + name: "concrete interval", + interval: NewInterval(NewTimestampBound(t1), NewTimestampBound(t2)), + wantStr: "@[2020-01-01T00:00:00Z, 2023-06-15T00:00:00Z]", + isEternal: false, + isPoint: false, + }, + { + name: "eternal interval", + interval: EternalInterval(), + wantStr: "", + isEternal: true, + isPoint: false, + }, + { + name: "point interval", + interval: NewPointInterval(t1), + wantStr: "@[2020-01-01T00:00:00Z]", + isEternal: false, + isPoint: true, + }, + { + name: "half-open interval (unbounded end)", + interval: NewInterval(NewTimestampBound(t1), PositiveInfinity()), + wantStr: "@[2020-01-01T00:00:00Z, _]", + isEternal: false, + isPoint: false, + }, + { + name: "interval with variable", + interval: NewInterval(NewTimestampBound(t1), NewVariableBound(Variable{"T"})), + wantStr: "@[2020-01-01T00:00:00Z, T]", + isEternal: false, + isPoint: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.interval.String(); got != tt.wantStr { + t.Errorf("Interval.String() = %v, want %v", got, tt.wantStr) + } + if got := tt.interval.IsEternal(); got != tt.isEternal { + t.Errorf("Interval.IsEternal() = %v, want %v", got, tt.isEternal) + } + if got := tt.interval.IsPoint(); got != tt.isPoint { + t.Errorf("Interval.IsPoint() = %v, want %v", got, tt.isPoint) + } + }) + } +} + +func TestIntervalContains(t *testing.T) { + start := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) + end := time.Date(2023, 6, 15, 0, 0, 0, 0, time.UTC) + interval := NewInterval(NewTimestampBound(start), NewTimestampBound(end)) + + tests := []struct { + name string + t time.Time + want bool + }{ + { + name: "time within interval", + t: time.Date(2021, 6, 1, 0, 0, 0, 0, time.UTC), + want: true, + }, + { + name: "time at start", + t: start, + want: true, + }, + { + name: "time at end", + t: end, + want: true, + }, + { + name: "time before interval", + t: time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), + want: false, + }, + { + name: "time after interval", + t: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := interval.Contains(tt.t); got != tt.want { + t.Errorf("Interval.Contains() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIntervalContainsWithUnbounded(t *testing.T) { + start := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) + + // Interval from 2020-01-01 to +infinity + openEnd := NewInterval(NewTimestampBound(start), PositiveInfinity()) + + // Interval from -infinity to 2020-01-01 + openStart := NewInterval(NegativeInfinity(), NewTimestampBound(start)) + + // Eternal interval + eternal := EternalInterval() + + testTime := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC) + pastTime := time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC) + + if !openEnd.Contains(testTime) { + t.Error("Open-end interval should contain future time") + } + if openEnd.Contains(pastTime) { + t.Error("Open-end interval should not contain time before start") + } + + if openStart.Contains(testTime) { + t.Error("Open-start interval should not contain time after end") + } + if !openStart.Contains(pastTime) { + t.Error("Open-start interval should contain past time") + } + + if !eternal.Contains(testTime) || !eternal.Contains(pastTime) { + t.Error("Eternal interval should contain all times") + } +} + +func TestIntervalOverlaps(t *testing.T) { + // Interval A: 2020-01-01 to 2022-12-31 + a := NewInterval( + NewTimestampBound(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)), + NewTimestampBound(time.Date(2022, 12, 31, 0, 0, 0, 0, time.UTC)), + ) + + tests := []struct { + name string + b Interval + want bool + }{ + { + name: "overlapping interval", + b: NewInterval( + NewTimestampBound(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)), + NewTimestampBound(time.Date(2023, 12, 31, 0, 0, 0, 0, time.UTC)), + ), + want: true, + }, + { + name: "contained interval", + b: NewInterval( + NewTimestampBound(time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)), + NewTimestampBound(time.Date(2021, 12, 31, 0, 0, 0, 0, time.UTC)), + ), + want: true, + }, + { + name: "before interval", + b: NewInterval( + NewTimestampBound(time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC)), + NewTimestampBound(time.Date(2019, 12, 31, 0, 0, 0, 0, time.UTC)), + ), + want: false, + }, + { + name: "after interval", + b: NewInterval( + NewTimestampBound(time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)), + NewTimestampBound(time.Date(2025, 12, 31, 0, 0, 0, 0, time.UTC)), + ), + want: false, + }, + { + name: "touching at end", + b: NewInterval( + NewTimestampBound(time.Date(2022, 12, 31, 0, 0, 0, 0, time.UTC)), + NewTimestampBound(time.Date(2023, 12, 31, 0, 0, 0, 0, time.UTC)), + ), + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := a.Overlaps(tt.b); got != tt.want { + t.Errorf("Interval.Overlaps() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestTemporalAtom(t *testing.T) { + alice, _ := Name("/alice") + engineering, _ := Name("/engineering") + atom := NewAtom("team_member", alice, engineering) + + start := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) + end := time.Date(2023, 6, 15, 0, 0, 0, 0, time.UTC) + interval := NewInterval(NewTimestampBound(start), NewTimestampBound(end)) + + tests := []struct { + name string + ta TemporalAtom + wantStr string + }{ + { + name: "atom without temporal annotation", + ta: TemporalAtom{Atom: atom, Interval: nil}, + wantStr: "team_member(/alice,/engineering)", + }, + { + name: "atom with temporal annotation", + ta: TemporalAtom{Atom: atom, Interval: &interval}, + wantStr: "team_member(/alice,/engineering)@[2020-01-01T00:00:00Z, 2023-06-15T00:00:00Z]", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.ta.String(); got != tt.wantStr { + t.Errorf("TemporalAtom.String() = %v, want %v", got, tt.wantStr) + } + }) + } +} + +func TestTemporalOperator(t *testing.T) { + interval := NewInterval( + NewTimestampBound(time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)), + NewTimestampBound(time.Date(2024, 1, 8, 0, 0, 0, 0, time.UTC)), + ) + + tests := []struct { + name string + op TemporalOperator + wantStr string + }{ + { + name: "diamond minus", + op: TemporalOperator{Type: DiamondMinus, Interval: interval}, + wantStr: "<-[2024-01-01T00:00:00Z, 2024-01-08T00:00:00Z]", + }, + { + name: "box minus", + op: TemporalOperator{Type: BoxMinus, Interval: interval}, + wantStr: "[-[2024-01-01T00:00:00Z, 2024-01-08T00:00:00Z]", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.op.String(); got != tt.wantStr { + t.Errorf("TemporalOperator.String() = %v, want %v", got, tt.wantStr) + } + }) + } +} + +func TestDateHelpers(t *testing.T) { + // Ensure we start with UTC + SetDefaultTimezone(time.UTC) + defer SetDefaultTimezone(time.UTC) // Reset after test + + t.Run("Date uses default timezone", func(t *testing.T) { + d := Date(2024, 1, 15) + if d.Location() != time.UTC { + t.Errorf("Date() location = %v, want UTC", d.Location()) + } + if d.Year() != 2024 || d.Month() != 1 || d.Day() != 15 { + t.Errorf("Date() = %v, want 2024-01-15", d) + } + if d.Hour() != 0 || d.Minute() != 0 || d.Second() != 0 { + t.Errorf("Date() should be midnight, got %v", d) + } + }) + + t.Run("DateTime uses default timezone", func(t *testing.T) { + d := DateTime(2024, 1, 15, 10, 30) + if d.Hour() != 10 || d.Minute() != 30 { + t.Errorf("DateTime() = %v, want 10:30", d) + } + }) + + t.Run("DateTimeSec uses default timezone", func(t *testing.T) { + d := DateTimeSec(2024, 1, 15, 10, 30, 45) + if d.Second() != 45 { + t.Errorf("DateTimeSec() second = %d, want 45", d.Second()) + } + }) + + t.Run("SetTimezone with string changes behavior", func(t *testing.T) { + err := SetTimezone("America/New_York") + if err != nil { + t.Skip("Could not load America/New_York timezone") + } + + d := Date(2024, 1, 15) + if d.Location().String() != "America/New_York" { + t.Errorf("After SetTimezone(America/New_York), Date() location = %v, want America/New_York", d.Location()) + } + + // Reset to UTC + SetTimezone("UTC") + d = Date(2024, 1, 15) + if d.Location() != time.UTC { + t.Errorf("After reset, Date() location = %v, want UTC", d.Location()) + } + }) + + t.Run("SetTimezone with abbreviations", func(t *testing.T) { + err := SetTimezone("PST") + if err != nil { + t.Skip("Could not load PST timezone") + } + if GetDefaultTimezone().String() != "America/Los_Angeles" { + t.Errorf("SetTimezone(PST) should map to America/Los_Angeles, got %v", GetDefaultTimezone()) + } + SetTimezone("UTC") + }) + + t.Run("SetTimezone with Local", func(t *testing.T) { + err := SetTimezone("Local") + if err != nil { + t.Fatalf("SetTimezone(Local) failed: %v", err) + } + if GetDefaultTimezone() != time.Local { + t.Errorf("SetTimezone(Local) should use time.Local") + } + SetTimezone("UTC") + }) + + t.Run("DateIn with string timezone", func(t *testing.T) { + SetDefaultTimezone(time.UTC) + + d := DateIn(2024, 1, 15, "America/New_York") + if d.Location().String() != "America/New_York" { + t.Errorf("DateIn(..., America/New_York) location = %v, want America/New_York", d.Location()) + } + + // Default should still be UTC + d2 := Date(2024, 1, 15) + if d2.Location() != time.UTC { + t.Errorf("Date() after DateIn() should still use UTC, got %v", d2.Location()) + } + }) + + t.Run("DateIn with abbreviation", func(t *testing.T) { + d := DateIn(2024, 1, 15, "PST") + if d.Location().String() != "America/Los_Angeles" { + t.Errorf("DateIn(..., PST) location = %v, want America/Los_Angeles", d.Location()) + } + }) + + t.Run("GetDefaultTimezone returns current setting", func(t *testing.T) { + SetDefaultTimezone(time.UTC) + if got := GetDefaultTimezone(); got != time.UTC { + t.Errorf("GetDefaultTimezone() = %v, want UTC", got) + } + }) + + t.Run("SetDefaultTimezone with nil defaults to UTC", func(t *testing.T) { + SetDefaultTimezone(nil) + if got := GetDefaultTimezone(); got != time.UTC { + t.Errorf("SetDefaultTimezone(nil) should default to UTC, got %v", got) + } + }) + + t.Run("MustSetTimezone panics on invalid", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("MustSetTimezone with invalid tz should panic") + } + SetTimezone("UTC") // Reset + }() + MustSetTimezone("Invalid/Timezone") + }) +} + +func TestDateInterval(t *testing.T) { + // Explicitly set UTC to avoid test pollution + SetDefaultTimezone(time.UTC) + defer SetDefaultTimezone(time.UTC) + + interval := DateInterval(2023, 1, 1, 2024, 12, 31) + + startTime := interval.Start.Time().UTC() + endTime := interval.End.Time().UTC() + + if startTime.Year() != 2023 || startTime.Month() != 1 || startTime.Day() != 1 { + t.Errorf("DateInterval start = %v, want 2023-01-01", startTime) + } + if endTime.Year() != 2024 || endTime.Month() != 12 || endTime.Day() != 31 { + t.Errorf("DateInterval end = %v, want 2024-12-31", endTime) + } +} + +func TestTimeInterval(t *testing.T) { + start := time.Date(2024, 1, 1, 10, 0, 0, 0, time.UTC) + end := time.Date(2024, 1, 1, 18, 0, 0, 0, time.UTC) + + interval := TimeInterval(start, end) + + if !interval.Start.Time().Equal(start) { + t.Errorf("TimeInterval start = %v, want %v", interval.Start.Time(), start) + } + if !interval.End.Time().Equal(end) { + t.Errorf("TimeInterval end = %v, want %v", interval.End.Time(), end) + } +} diff --git a/builtin/builtin.go b/builtin/builtin.go index e6a24c8..8db70a2 100644 --- a/builtin/builtin.go +++ b/builtin/builtin.go @@ -54,6 +54,16 @@ var ( symbols.MatchNil: {ast.ArgModeInput}, symbols.MatchField: {ast.ArgModeInput, ast.ArgModeInput, ast.ArgModeOutput}, symbols.MatchEntry: {ast.ArgModeInput, ast.ArgModeInput, ast.ArgModeOutput}, + // Temporal interval predicates (Allen's interval algebra) + symbols.IntervalBefore: {ast.ArgModeInput, ast.ArgModeInput}, + symbols.IntervalAfter: {ast.ArgModeInput, ast.ArgModeInput}, + symbols.IntervalMeets: {ast.ArgModeInput, ast.ArgModeInput}, + symbols.IntervalOverlaps: {ast.ArgModeInput, ast.ArgModeInput}, + symbols.IntervalDuring: {ast.ArgModeInput, ast.ArgModeInput}, + symbols.IntervalContains: {ast.ArgModeInput, ast.ArgModeInput}, + symbols.IntervalStarts: {ast.ArgModeInput, ast.ArgModeInput}, + symbols.IntervalFinishes: {ast.ArgModeInput, ast.ArgModeInput}, + symbols.IntervalEquals: {ast.ArgModeInput, ast.ArgModeInput}, } varX = ast.Variable{"X"} @@ -104,11 +114,11 @@ var ( symbols.Tuple: emptyType, symbols.Struct: emptyType, - // Time functions +// Time functions symbols.TimeNow: symbols.NewFunType(ast.TimeBound /* <= */), symbols.TimeAdd: symbols.NewFunType(ast.TimeBound /* <= */, ast.TimeBound, ast.DurationBound), symbols.TimeSub: symbols.NewFunType(ast.DurationBound /* <= */, ast.TimeBound, ast.TimeBound), - symbols.TimeFormat: symbols.NewFunType(ast.StringBound /* <= */, ast.TimeBound, ast.StringBound), + symbols.TimeFormat: symbols.NewFunType(ast.StringBound /* <= */, ast.TimeBound, ast.NameBound), symbols.TimeFormatCivil: symbols.NewFunType(ast.StringBound /* <= */, ast.TimeBound, ast.StringBound, ast.NameBound), symbols.TimeParseRFC3339: symbols.NewFunType(ast.TimeBound /* <= */, ast.StringBound), symbols.TimeParseCivil: symbols.NewFunType(ast.TimeBound /* <= */, ast.StringBound, ast.StringBound), @@ -134,6 +144,11 @@ var ( symbols.DurationFromMinutes: symbols.NewFunType(ast.DurationBound /* <= */, ast.Float64Bound), symbols.DurationFromSeconds: symbols.NewFunType(ast.DurationBound /* <= */, ast.Float64Bound), symbols.DurationParse: symbols.NewFunType(ast.DurationBound /* <= */, ast.StringBound), + + // Interval functions - work with intervals represented as pairs of time values + symbols.IntervalStart: symbols.NewFunType(ast.TimeBound /* <= */, symbols.NewPairType(ast.TimeBound, ast.TimeBound)), + symbols.IntervalEnd: symbols.NewFunType(ast.TimeBound /* <= */, symbols.NewPairType(ast.TimeBound, ast.TimeBound)), + symbols.IntervalDuration: symbols.NewFunType(ast.DurationBound /* <= */, symbols.NewPairType(ast.TimeBound, ast.TimeBound)), } // ReducerFunctions has those built-in functions with are reducers. @@ -199,6 +214,11 @@ func IsReducerFunction(sym ast.FunctionSym) bool { // Decide evaluates an atom of a built-in predicate. The atom must no longer contain any // apply-expressions or variables. func Decide(atom ast.Atom, subst *unionfind.UnionFind) (bool, []*unionfind.UnionFind, error) { + // Check for temporal predicates first + if IsTemporalPredicate(atom.Predicate) { + return DecideTemporalPredicate(atom, subst) + } + switch atom.Predicate.Symbol { case symbols.StartsWith.Symbol: fallthrough diff --git a/builtin/temporal.go b/builtin/temporal.go new file mode 100644 index 0000000..457f273 --- /dev/null +++ b/builtin/temporal.go @@ -0,0 +1,204 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builtin + +import ( + "fmt" + + "github.com/google/mangle/ast" + "github.com/google/mangle/symbols" + "github.com/google/mangle/unionfind" +) + +// DecideTemporalPredicate evaluates temporal interval predicates. +// Returns (ok, substitions, error). If ok is false with no error, the predicate failed. +func DecideTemporalPredicate(atom ast.Atom, subst *unionfind.UnionFind) (bool, []*unionfind.UnionFind, error) { + if len(atom.Args) != 2 { + return false, nil, fmt.Errorf("temporal predicate %s requires 2 arguments, got %d", atom.Predicate.Symbol, len(atom.Args)) + } + + // Extract intervals from arguments + interval1, err := getIntervalValue(atom.Args[0]) + if err != nil { + return false, nil, err + } + interval2, err := getIntervalValue(atom.Args[1]) + if err != nil { + return false, nil, err + } + + var result bool + switch atom.Predicate.Symbol { + case symbols.IntervalBefore.Symbol: + result = intervalBefore(interval1, interval2) + case symbols.IntervalAfter.Symbol: + result = intervalAfter(interval1, interval2) + case symbols.IntervalMeets.Symbol: + result = intervalMeets(interval1, interval2) + case symbols.IntervalOverlaps.Symbol: + result = intervalOverlaps(interval1, interval2) + case symbols.IntervalDuring.Symbol: + result = intervalDuring(interval1, interval2) + case symbols.IntervalContains.Symbol: + result = intervalContains(interval1, interval2) + case symbols.IntervalStarts.Symbol: + result = intervalStarts(interval1, interval2) + case symbols.IntervalFinishes.Symbol: + result = intervalFinishes(interval1, interval2) + case symbols.IntervalEquals.Symbol: + result = intervalEquals(interval1, interval2) + default: + return false, nil, fmt.Errorf("unknown temporal predicate: %s", atom.Predicate.Symbol) + } + + if result { + return true, []*unionfind.UnionFind{subst}, nil + } + return false, nil, nil +} + +// getIntervalValue extracts an interval from a constant. +// Intervals are represented as pairs of timestamps (start, end). +func getIntervalValue(term ast.BaseTerm) (ast.Interval, error) { + c, ok := term.(ast.Constant) + if !ok { + return ast.Interval{}, fmt.Errorf("expected constant for interval, got %T", term) + } + + // Intervals are stored as pairs of numbers (nanoseconds since epoch) + if c.Type == ast.PairShape { + fst, snd, err := c.PairValue() + if err != nil { + return ast.Interval{}, fmt.Errorf("invalid interval pair: %w", err) + } + startNano, err := fst.NumberValue() + if err != nil { + return ast.Interval{}, fmt.Errorf("invalid interval start: %w", err) + } + endNano, err := snd.NumberValue() + if err != nil { + return ast.Interval{}, fmt.Errorf("invalid interval end: %w", err) + } + return ast.Interval{ + Start: ast.TemporalBound{Type: ast.TimestampBound, Timestamp: startNano}, + End: ast.TemporalBound{Type: ast.TimestampBound, Timestamp: endNano}, + }, nil + } + + return ast.Interval{}, fmt.Errorf("expected pair for interval, got %v", c.Type) +} + +// Allen's Interval Algebra implementations + +// intervalBefore: T1 ends before T2 starts +func intervalBefore(t1, t2 ast.Interval) bool { + if t1.End.Type != ast.TimestampBound || t2.Start.Type != ast.TimestampBound { + return false + } + return t1.End.Timestamp < t2.Start.Timestamp +} + +// intervalAfter: T1 starts after T2 ends +func intervalAfter(t1, t2 ast.Interval) bool { + return intervalBefore(t2, t1) +} + +// intervalMeets: T1 ends exactly when T2 starts +func intervalMeets(t1, t2 ast.Interval) bool { + if t1.End.Type != ast.TimestampBound || t2.Start.Type != ast.TimestampBound { + return false + } + return t1.End.Timestamp == t2.Start.Timestamp +} + +// intervalOverlaps: T1 and T2 share some time +func intervalOverlaps(t1, t2 ast.Interval) bool { + return t1.Overlaps(t2) +} + +// intervalDuring: T1 is contained within T2 +func intervalDuring(t1, t2 ast.Interval) bool { + // T1.start >= T2.start AND T1.end <= T2.end + if t1.Start.Type != ast.TimestampBound || t1.End.Type != ast.TimestampBound { + return false + } + if t2.Start.Type != ast.TimestampBound || t2.End.Type != ast.TimestampBound { + // Check for unbounded + if t2.Start.Type == ast.UnboundedBound && !t2.Start.IsPositiveInf { + if t2.End.Type == ast.UnboundedBound && t2.End.IsPositiveInf { + return true // T2 is eternal + } + if t2.End.Type == ast.TimestampBound { + return t1.End.Timestamp <= t2.End.Timestamp + } + } + if t2.End.Type == ast.UnboundedBound && t2.End.IsPositiveInf { + if t2.Start.Type == ast.TimestampBound { + return t1.Start.Timestamp >= t2.Start.Timestamp + } + } + return false + } + return t1.Start.Timestamp >= t2.Start.Timestamp && t1.End.Timestamp <= t2.End.Timestamp +} + +// intervalContains: T1 contains T2 +func intervalContains(t1, t2 ast.Interval) bool { + return intervalDuring(t2, t1) +} + +// intervalStarts: T1 and T2 start at the same time +func intervalStarts(t1, t2 ast.Interval) bool { + if t1.Start.Type != ast.TimestampBound || t2.Start.Type != ast.TimestampBound { + if t1.Start.Type == ast.UnboundedBound && t2.Start.Type == ast.UnboundedBound { + return t1.Start.IsPositiveInf == t2.Start.IsPositiveInf + } + return false + } + return t1.Start.Timestamp == t2.Start.Timestamp +} + +// intervalFinishes: T1 and T2 end at the same time +func intervalFinishes(t1, t2 ast.Interval) bool { + if t1.End.Type != ast.TimestampBound || t2.End.Type != ast.TimestampBound { + if t1.End.Type == ast.UnboundedBound && t2.End.Type == ast.UnboundedBound { + return t1.End.IsPositiveInf == t2.End.IsPositiveInf + } + return false + } + return t1.End.Timestamp == t2.End.Timestamp +} + +// intervalEquals: T1 and T2 are identical +func intervalEquals(t1, t2 ast.Interval) bool { + return t1.Equals(t2) +} + +// IsTemporalPredicate returns true if the predicate is a temporal interval predicate. +func IsTemporalPredicate(pred ast.PredicateSym) bool { + switch pred.Symbol { + case symbols.IntervalBefore.Symbol, + symbols.IntervalAfter.Symbol, + symbols.IntervalMeets.Symbol, + symbols.IntervalOverlaps.Symbol, + symbols.IntervalDuring.Symbol, + symbols.IntervalContains.Symbol, + symbols.IntervalStarts.Symbol, + symbols.IntervalFinishes.Symbol, + symbols.IntervalEquals.Symbol: + return true + } + return false +} diff --git a/builtin/temporal_test.go b/builtin/temporal_test.go new file mode 100644 index 0000000..0b7cfb3 --- /dev/null +++ b/builtin/temporal_test.go @@ -0,0 +1,309 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package builtin + +import ( + "testing" + "time" + + "github.com/google/mangle/ast" + "github.com/google/mangle/symbols" + "github.com/google/mangle/unionfind" +) + +func makeIntervalConstant(startNano, endNano int64) ast.Constant { + startConst := ast.Number(startNano) + endConst := ast.Number(endNano) + return ast.Pair(&startConst, &endConst) +} + +func TestIntervalBefore(t *testing.T) { + jan1 := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC).UnixNano() + jan5 := time.Date(2024, 1, 5, 0, 0, 0, 0, time.UTC).UnixNano() + jan10 := time.Date(2024, 1, 10, 0, 0, 0, 0, time.UTC).UnixNano() + jan15 := time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC).UnixNano() + + tests := []struct { + name string + t1 ast.Constant + t2 ast.Constant + want bool + }{ + { + name: "before", + t1: makeIntervalConstant(jan1, jan5), + t2: makeIntervalConstant(jan10, jan15), + want: true, + }, + { + name: "after", + t1: makeIntervalConstant(jan10, jan15), + t2: makeIntervalConstant(jan1, jan5), + want: false, + }, + { + name: "overlapping", + t1: makeIntervalConstant(jan1, jan10), + t2: makeIntervalConstant(jan5, jan15), + want: false, + }, + { + name: "meets", + t1: makeIntervalConstant(jan1, jan10), + t2: makeIntervalConstant(jan10, jan15), + want: false, // meets is not before + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + atom := ast.NewAtom(symbols.IntervalBefore.Symbol, tt.t1, tt.t2) + atom.Predicate = symbols.IntervalBefore + subst := unionfind.New() + + ok, _, err := DecideTemporalPredicate(atom, &subst) + if err != nil { + t.Fatalf("DecideTemporalPredicate error: %v", err) + } + if ok != tt.want { + t.Errorf("intervalBefore = %v, want %v", ok, tt.want) + } + }) + } +} + +func TestIntervalOverlaps(t *testing.T) { + jan1 := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC).UnixNano() + jan5 := time.Date(2024, 1, 5, 0, 0, 0, 0, time.UTC).UnixNano() + jan10 := time.Date(2024, 1, 10, 0, 0, 0, 0, time.UTC).UnixNano() + jan15 := time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC).UnixNano() + jan20 := time.Date(2024, 1, 20, 0, 0, 0, 0, time.UTC).UnixNano() + + tests := []struct { + name string + t1 ast.Constant + t2 ast.Constant + want bool + }{ + { + name: "overlapping", + t1: makeIntervalConstant(jan1, jan10), + t2: makeIntervalConstant(jan5, jan15), + want: true, + }, + { + name: "disjoint", + t1: makeIntervalConstant(jan1, jan5), + t2: makeIntervalConstant(jan10, jan15), + want: false, + }, + { + name: "contained", + t1: makeIntervalConstant(jan1, jan20), + t2: makeIntervalConstant(jan5, jan15), + want: true, + }, + { + name: "same", + t1: makeIntervalConstant(jan1, jan10), + t2: makeIntervalConstant(jan1, jan10), + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + atom := ast.NewAtom(symbols.IntervalOverlaps.Symbol, tt.t1, tt.t2) + atom.Predicate = symbols.IntervalOverlaps + subst := unionfind.New() + + ok, _, err := DecideTemporalPredicate(atom, &subst) + if err != nil { + t.Fatalf("DecideTemporalPredicate error: %v", err) + } + if ok != tt.want { + t.Errorf("intervalOverlaps = %v, want %v", ok, tt.want) + } + }) + } +} + +func TestIntervalDuring(t *testing.T) { + jan1 := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC).UnixNano() + jan5 := time.Date(2024, 1, 5, 0, 0, 0, 0, time.UTC).UnixNano() + jan10 := time.Date(2024, 1, 10, 0, 0, 0, 0, time.UTC).UnixNano() + jan15 := time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC).UnixNano() + jan20 := time.Date(2024, 1, 20, 0, 0, 0, 0, time.UTC).UnixNano() + + tests := []struct { + name string + t1 ast.Constant + t2 ast.Constant + want bool + }{ + { + name: "t1_during_t2", + t1: makeIntervalConstant(jan5, jan15), + t2: makeIntervalConstant(jan1, jan20), + want: true, + }, + { + name: "t1_not_during_t2", + t1: makeIntervalConstant(jan1, jan20), + t2: makeIntervalConstant(jan5, jan15), + want: false, + }, + { + name: "same", + t1: makeIntervalConstant(jan1, jan10), + t2: makeIntervalConstant(jan1, jan10), + want: true, + }, + { + name: "partial_overlap", + t1: makeIntervalConstant(jan1, jan10), + t2: makeIntervalConstant(jan5, jan15), + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + atom := ast.NewAtom(symbols.IntervalDuring.Symbol, tt.t1, tt.t2) + atom.Predicate = symbols.IntervalDuring + subst := unionfind.New() + + ok, _, err := DecideTemporalPredicate(atom, &subst) + if err != nil { + t.Fatalf("DecideTemporalPredicate error: %v", err) + } + if ok != tt.want { + t.Errorf("intervalDuring = %v, want %v", ok, tt.want) + } + }) + } +} + +func TestIntervalMeets(t *testing.T) { + jan1 := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC).UnixNano() + jan10 := time.Date(2024, 1, 10, 0, 0, 0, 0, time.UTC).UnixNano() + jan15 := time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC).UnixNano() + + tests := []struct { + name string + t1 ast.Constant + t2 ast.Constant + want bool + }{ + { + name: "meets", + t1: makeIntervalConstant(jan1, jan10), + t2: makeIntervalConstant(jan10, jan15), + want: true, + }, + { + name: "gap", + t1: makeIntervalConstant(jan1, jan10-1), + t2: makeIntervalConstant(jan10, jan15), + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + atom := ast.NewAtom(symbols.IntervalMeets.Symbol, tt.t1, tt.t2) + atom.Predicate = symbols.IntervalMeets + subst := unionfind.New() + + ok, _, err := DecideTemporalPredicate(atom, &subst) + if err != nil { + t.Fatalf("DecideTemporalPredicate error: %v", err) + } + if ok != tt.want { + t.Errorf("intervalMeets = %v, want %v", ok, tt.want) + } + }) + } +} + +func TestIntervalEquals(t *testing.T) { + jan1 := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC).UnixNano() + jan10 := time.Date(2024, 1, 10, 0, 0, 0, 0, time.UTC).UnixNano() + jan15 := time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC).UnixNano() + + tests := []struct { + name string + t1 ast.Constant + t2 ast.Constant + want bool + }{ + { + name: "equal", + t1: makeIntervalConstant(jan1, jan10), + t2: makeIntervalConstant(jan1, jan10), + want: true, + }, + { + name: "not_equal", + t1: makeIntervalConstant(jan1, jan10), + t2: makeIntervalConstant(jan1, jan15), + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + atom := ast.NewAtom(symbols.IntervalEquals.Symbol, tt.t1, tt.t2) + atom.Predicate = symbols.IntervalEquals + subst := unionfind.New() + + ok, _, err := DecideTemporalPredicate(atom, &subst) + if err != nil { + t.Fatalf("DecideTemporalPredicate error: %v", err) + } + if ok != tt.want { + t.Errorf("intervalEquals = %v, want %v", ok, tt.want) + } + }) + } +} + +func TestIsTemporalPredicate(t *testing.T) { + tests := []struct { + pred ast.PredicateSym + want bool + }{ + {symbols.IntervalBefore, true}, + {symbols.IntervalAfter, true}, + {symbols.IntervalMeets, true}, + {symbols.IntervalOverlaps, true}, + {symbols.IntervalDuring, true}, + {symbols.IntervalContains, true}, + {symbols.IntervalStarts, true}, + {symbols.IntervalFinishes, true}, + {symbols.IntervalEquals, true}, + {symbols.Lt, false}, + {symbols.Filter, false}, + } + + for _, tt := range tests { + t.Run(tt.pred.Symbol, func(t *testing.T) { + got := IsTemporalPredicate(tt.pred) + if got != tt.want { + t.Errorf("IsTemporalPredicate(%v) = %v, want %v", tt.pred, got, tt.want) + } + }) + } +} diff --git a/engine/seminaivebottomup.go b/engine/seminaivebottomup.go index 32f0835..ca43878 100644 --- a/engine/seminaivebottomup.go +++ b/engine/seminaivebottomup.go @@ -47,6 +47,8 @@ type Stats struct { type engine struct { store factstore.FactStore deltaStore factstore.FactStore + temporalStore factstore.TemporalFactStore // Optional temporal store + evalTime time.Time // Evaluation time for temporal queries programInfo *analysis.ProgramInfo strata []analysis.Nodeset predToStratum map[ast.PredicateSym]int @@ -95,6 +97,9 @@ type EvalOptions struct { // if non-nil, only predicates in this allowlist get evaluated. predicateAllowList *func(ast.PredicateSym) bool externalPredicates map[ast.PredicateSym]ExternalPredicateCallback + // Temporal evaluation options + temporalStore factstore.TemporalFactStore + evalTime time.Time } // EvalOption affects the way the evaluation is performed. @@ -111,6 +116,16 @@ func WithExternalPredicates( return func(o *EvalOptions) { o.externalPredicates = callbacks } } +// WithTemporalStore configures a temporal fact store for temporal reasoning. +func WithTemporalStore(store factstore.TemporalFactStore) EvalOption { + return func(o *EvalOptions) { o.temporalStore = store } +} + +// WithEvaluationTime sets the evaluation time for temporal queries. +func WithEvaluationTime(t time.Time) EvalOption { + return func(o *EvalOptions) { o.evalTime = t } +} + // EvalProgram evaluates a given program on the given facts, modifying the fact store in the process. // Deprecated: use EvalStratifiedProgramWithStats instead. func EvalProgram(programInfo *analysis.ProgramInfo, store factstore.FactStore, options ...EvalOption) error { @@ -181,8 +196,24 @@ func EvalStratifiedProgramWithStats(programInfo *analysis.ProgramInfo, if opts.createdFactLimit > 0 { opts.totalFactLimit = store.EstimateFactCount() + opts.createdFactLimit } - e := &engine{store, factstore.NewMultiIndexedArrayInMemoryStore(), programInfo, strata, - predToStratum, predToRules, predToDecl, stats, opts} + // Set default evaluation time if not specified + evalTime := opts.evalTime + if evalTime.IsZero() { + evalTime = time.Now() + } + e := &engine{ + store: store, + deltaStore: factstore.NewMultiIndexedArrayInMemoryStore(), + temporalStore: opts.temporalStore, + evalTime: evalTime, + programInfo: programInfo, + strata: strata, + predToStratum: predToStratum, + predToRules: predToRules, + predToDecl: predToDecl, + stats: stats, + options: opts, + } if err := e.evalStrata(); err != nil { return Stats{}, err } @@ -222,6 +253,8 @@ func (e *engine) evalStrata() error { e := engine{ store: e.store, deltaStore: factstore.NewMultiIndexedArrayInMemoryStore(), + temporalStore: e.temporalStore, + evalTime: e.evalTime, programInfo: &analysis.ProgramInfo{stratifiedProgram.EdbPredicates, stratifiedProgram.IdbPredicates, nil, stratifiedProgram.Rules, stratumDecls}, predToStratum: e.predToStratum, predToRules: e.predToRules, @@ -597,6 +630,19 @@ func (e *engine) oneStepEvalPremise(premise ast.Term, subst unionfind.UnionFind, case ast.Ineq: return premiseIneq(p.Left, p.Right, subst) + case ast.TemporalLiteral: + if e.temporalStore == nil { + return nil, fmt.Errorf("temporal literal encountered but no temporal store configured") + } + return premiseTemporalLiteral(p, e.temporalStore, e.evalTime, subst) + + case ast.TemporalAtom: + if e.temporalStore == nil { + return nil, fmt.Errorf("temporal atom encountered but no temporal store configured") + } + // Convert TemporalAtom to TemporalLiteral for evaluation + tl := ast.TemporalLiteral{Literal: p.Atom, Operator: nil, IntervalVar: nil} + return premiseTemporalLiteral(tl, e.temporalStore, e.evalTime, subst) } return nil, nil } diff --git a/engine/temporal.go b/engine/temporal.go new file mode 100644 index 0000000..37dd93b --- /dev/null +++ b/engine/temporal.go @@ -0,0 +1,548 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package engine + +import ( + "fmt" + "math" + "time" + + "github.com/google/mangle/ast" + "github.com/google/mangle/factstore" + "github.com/google/mangle/functional" + "github.com/google/mangle/unionfind" +) + +// TemporalEvaluator provides temporal reasoning capabilities for the engine. +type TemporalEvaluator struct { + // The temporal fact store for time-indexed facts + temporalStore factstore.TemporalFactStore + // The current evaluation time (for relative time calculations) + evaluationTime time.Time +} + +// NewTemporalEvaluator creates a new temporal evaluator. +func NewTemporalEvaluator(store factstore.TemporalFactStore, evalTime time.Time) *TemporalEvaluator { + return &TemporalEvaluator{ + temporalStore: store, + evaluationTime: evalTime, + } +} + +// EvalTemporalLiteral evaluates a temporal literal (an atom with temporal operator). +// Returns solutions (substitutions) that satisfy the temporal constraint. +func (te *TemporalEvaluator) EvalTemporalLiteral( + tl ast.TemporalLiteral, + subst unionfind.UnionFind, +) ([]unionfind.UnionFind, error) { + // Extract the underlying atom + atom, ok := tl.Literal.(ast.Atom) + if !ok { + return nil, fmt.Errorf("temporal literal must wrap an atom, got %T", tl.Literal) + } + + // Evaluate the atom with current substitution + evaledAtom, err := functional.EvalAtom(atom, subst) + if err != nil { + return nil, err + } + + // If no temporal operator, this is just a regular lookup with optional interval binding + if tl.Operator == nil { + return te.evalTemporalAtomWithoutOperator(evaledAtom, tl.IntervalVar, subst) + } + + // Evaluate the temporal operator + switch tl.Operator.Type { + case ast.DiamondMinus: + return te.evalDiamondMinus(evaledAtom, tl.Operator.Interval, tl.IntervalVar, subst) + case ast.BoxMinus: + return te.evalBoxMinus(evaledAtom, tl.Operator.Interval, tl.IntervalVar, subst) + case ast.DiamondPlus: + return te.evalDiamondPlus(evaledAtom, tl.Operator.Interval, tl.IntervalVar, subst) + case ast.BoxPlus: + return te.evalBoxPlus(evaledAtom, tl.Operator.Interval, tl.IntervalVar, subst) + default: + return nil, fmt.Errorf("unknown temporal operator type: %v", tl.Operator.Type) + } +} + +// evalTemporalAtomWithoutOperator looks up facts and optionally binds the interval variable. +func (te *TemporalEvaluator) evalTemporalAtomWithoutOperator( + atom ast.Atom, + intervalVar *ast.Variable, + subst unionfind.UnionFind, +) ([]unionfind.UnionFind, error) { + var solutions []unionfind.UnionFind + + // Query all temporal facts matching the atom + err := te.temporalStore.GetAllFacts(atom, func(tf factstore.TemporalFact) error { + // Check if the fact is valid at current evaluation time + if !tf.Interval.Contains(te.evaluationTime) { + return nil + } + + // Unify the fact with the query atom + newSubst, err := unionfind.UnifyTermsExtend(atom.Args, tf.Atom.Args, subst) + if err != nil { + return nil // No match, continue + } + + // Bind interval variable if present + if intervalVar != nil { + newSubst = te.bindIntervalVariable(*intervalVar, tf.Interval, newSubst) + } + + solutions = append(solutions, newSubst) + return nil + }) + + if err != nil { + return nil, err + } + + return solutions, nil +} + +// evalDiamondMinus implements the diamond-minus operator (<-). +// It returns true if the atom was true at SOME point in the past interval. +// Syntax: <-[d1, d2] p(X) means "p(X) was true at some point between d1 and d2 ago" +func (te *TemporalEvaluator) evalDiamondMinus( + atom ast.Atom, + opInterval ast.Interval, + intervalVar *ast.Variable, + subst unionfind.UnionFind, +) ([]unionfind.UnionFind, error) { + // Calculate the query interval based on the operator's interval + // The interval [d1, d2] means "from d2 ago to d1 ago" + queryInterval, err := te.resolveOperatorInterval(opInterval) + if err != nil { + return nil, err + } + + var solutions []unionfind.UnionFind + + // Query facts that overlap with the query interval + err = te.temporalStore.GetFactsDuring(atom, queryInterval, func(tf factstore.TemporalFact) error { + // Unify the fact with the query atom + newSubst, err := unionfind.UnifyTermsExtend(atom.Args, tf.Atom.Args, subst) + if err != nil { + return nil // No match, continue + } + + // Bind interval variable if present + if intervalVar != nil { + newSubst = te.bindIntervalVariable(*intervalVar, tf.Interval, newSubst) + } + + solutions = append(solutions, newSubst) + return nil + }) + + if err != nil { + return nil, err + } + + return solutions, nil +} + +// evalBoxMinus implements the box-minus operator ([-). +// It returns true if the atom was true CONTINUOUSLY throughout the past interval. +// Syntax: [-[d1, d2] p(X) means "p(X) was true for the entire period from d2 ago to d1 ago" +func (te *TemporalEvaluator) evalBoxMinus( + atom ast.Atom, + opInterval ast.Interval, + intervalVar *ast.Variable, + subst unionfind.UnionFind, +) ([]unionfind.UnionFind, error) { + // Calculate the query interval based on the operator's interval + queryInterval, err := te.resolveOperatorInterval(opInterval) + if err != nil { + return nil, err + } + + var solutions []unionfind.UnionFind + + // Query all facts matching the atom + err = te.temporalStore.GetAllFacts(atom, func(tf factstore.TemporalFact) error { + // Unify first to check if this fact matches + newSubst, err := unionfind.UnifyTermsExtend(atom.Args, tf.Atom.Args, subst) + if err != nil { + return nil // No match, continue + } + + // For box-minus, the fact's interval must CONTAIN the query interval + // (the fact must be true throughout the entire query period) + if te.intervalContains(tf.Interval, queryInterval) { + // Bind interval variable if present + if intervalVar != nil { + newSubst = te.bindIntervalVariable(*intervalVar, tf.Interval, newSubst) + } + + solutions = append(solutions, newSubst) + } + return nil + }) + + if err != nil { + return nil, err + } + + return solutions, nil +} + +// resolveOperatorInterval converts an operator interval (with durations) to absolute timestamps. +// For past operators, durations are interpreted as offsets from the evaluation time. +func (te *TemporalEvaluator) resolveOperatorInterval(interval ast.Interval) (ast.Interval, error) { + start, err := te.resolveBound(interval.Start, true) // true = past operator + if err != nil { + return ast.Interval{}, err + } + + end, err := te.resolveBound(interval.End, true) + if err != nil { + return ast.Interval{}, err + } + + // For past operators with duration bounds, the semantics are: + // <-[0d, 7d] means "from 7 days ago to now" + // So start should be the larger duration (further in past) and end the smaller + return ast.NewInterval(end, start), nil // Note: swapped because past operators +} + +// resolveBound converts a temporal bound to an absolute timestamp. +func (te *TemporalEvaluator) resolveBound(bound ast.TemporalBound, isPast bool) (ast.TemporalBound, error) { + switch bound.Type { + case ast.TimestampBound: + // If it's a negative timestamp, it's a duration offset + if bound.Timestamp < 0 { + // Convert from negative nanoseconds (duration) to absolute time + duration := time.Duration(-bound.Timestamp) + if isPast { + absoluteTime := te.evaluationTime.Add(-duration) + return ast.NewTimestampBound(absoluteTime), nil + } + absoluteTime := te.evaluationTime.Add(duration) + return ast.NewTimestampBound(absoluteTime), nil + } + // Already an absolute timestamp + return bound, nil + + case ast.VariableBound: + // Variables need to be resolved at runtime - return as-is for now + return bound, nil + + case ast.UnboundedBound: + return bound, nil + + case ast.NowBound: + // 'now' resolves to the current evaluation time + return ast.NewTimestampBound(te.evaluationTime), nil + + default: + return ast.TemporalBound{}, fmt.Errorf("unknown bound type: %v", bound.Type) + } +} + +// intervalContains checks if interval a fully contains interval b. +func (te *TemporalEvaluator) intervalContains(a, b ast.Interval) bool { + // Handle unbounded intervals + aStartOK := a.Start.Type == ast.UnboundedBound && !a.Start.IsPositiveInf + bStartOK := b.Start.Type == ast.UnboundedBound && !b.Start.IsPositiveInf + + if !aStartOK && a.Start.Type == ast.TimestampBound { + if !bStartOK && b.Start.Type == ast.TimestampBound { + // a.Start must be <= b.Start + if a.Start.Timestamp > b.Start.Timestamp { + return false + } + } else if bStartOK { + // b starts at -inf but a doesn't + return false + } + } + + aEndOK := a.End.Type == ast.UnboundedBound && a.End.IsPositiveInf + bEndOK := b.End.Type == ast.UnboundedBound && b.End.IsPositiveInf + + if !aEndOK && a.End.Type == ast.TimestampBound { + if !bEndOK && b.End.Type == ast.TimestampBound { + // a.End must be >= b.End + if a.End.Timestamp < b.End.Timestamp { + return false + } + } else if bEndOK { + // b ends at +inf but a doesn't + return false + } + } + + return true +} + +// evalDiamondPlus implements the diamond-plus operator (<+). +// It returns true if the atom will be true at SOME point in the future interval. +// Syntax: <+[d1, d2] p(X) means "p(X) will be true at some point between d1 and d2 from now" +func (te *TemporalEvaluator) evalDiamondPlus( + atom ast.Atom, + opInterval ast.Interval, + intervalVar *ast.Variable, + subst unionfind.UnionFind, +) ([]unionfind.UnionFind, error) { + // Calculate the query interval based on the operator's interval + queryInterval, err := te.resolveFutureOperatorInterval(opInterval) + if err != nil { + return nil, err + } + + var solutions []unionfind.UnionFind + + // Query facts that overlap with the future query interval + err = te.temporalStore.GetFactsDuring(atom, queryInterval, func(tf factstore.TemporalFact) error { + // Unify the fact with the query atom + newSubst, err := unionfind.UnifyTermsExtend(atom.Args, tf.Atom.Args, subst) + if err != nil { + return nil // No match, continue + } + + // Bind interval variable if present + if intervalVar != nil { + newSubst = te.bindIntervalVariable(*intervalVar, tf.Interval, newSubst) + } + + solutions = append(solutions, newSubst) + return nil + }) + + if err != nil { + return nil, err + } + + return solutions, nil +} + +// evalBoxPlus implements the box-plus operator ([+). +// It returns true if the atom will be true CONTINUOUSLY throughout the future interval. +// Syntax: [+[d1, d2] p(X) means "p(X) will be true for the entire period from d1 to d2 from now" +func (te *TemporalEvaluator) evalBoxPlus( + atom ast.Atom, + opInterval ast.Interval, + intervalVar *ast.Variable, + subst unionfind.UnionFind, +) ([]unionfind.UnionFind, error) { + // Calculate the query interval based on the operator's interval + queryInterval, err := te.resolveFutureOperatorInterval(opInterval) + if err != nil { + return nil, err + } + + var solutions []unionfind.UnionFind + + // Query all facts matching the atom + err = te.temporalStore.GetAllFacts(atom, func(tf factstore.TemporalFact) error { + // Unify first to check if this fact matches + newSubst, err := unionfind.UnifyTermsExtend(atom.Args, tf.Atom.Args, subst) + if err != nil { + return nil // No match, continue + } + + // For box-plus, the fact's interval must CONTAIN the query interval + // (the fact must be true throughout the entire future query period) + if te.intervalContains(tf.Interval, queryInterval) { + // Bind interval variable if present + if intervalVar != nil { + newSubst = te.bindIntervalVariable(*intervalVar, tf.Interval, newSubst) + } + + solutions = append(solutions, newSubst) + } + return nil + }) + + if err != nil { + return nil, err + } + + return solutions, nil +} + +// resolveFutureOperatorInterval converts a future operator interval to absolute timestamps. +func (te *TemporalEvaluator) resolveFutureOperatorInterval(interval ast.Interval) (ast.Interval, error) { + start, err := te.resolveBound(interval.Start, false) // false = future operator + if err != nil { + return ast.Interval{}, err + } + + end, err := te.resolveBound(interval.End, false) + if err != nil { + return ast.Interval{}, err + } + + // For future operators, the interval is from start to end (not swapped) + return ast.NewInterval(start, end), nil +} + +// bindIntervalVariable binds an interval to a variable in the substitution. +// Intervals are represented as pairs of numbers (start nanoseconds, end nanoseconds). +func (te *TemporalEvaluator) bindIntervalVariable(v ast.Variable, interval ast.Interval, subst unionfind.UnionFind) unionfind.UnionFind { + // Create an interval constant as a pair of timestamps + intervalConst := intervalToConstant(interval) + newSubst, err := unionfind.UnifyTermsExtend([]ast.BaseTerm{v}, []ast.BaseTerm{intervalConst}, subst) + if err != nil { + // If binding fails, return original substitution + return subst + } + return newSubst +} + +// intervalToConstant converts an interval to a Mangle constant (pair of numbers). +func intervalToConstant(interval ast.Interval) ast.Constant { + var startNano, endNano int64 + + if interval.Start.Type == ast.TimestampBound { + startNano = interval.Start.Timestamp + } else if interval.Start.Type == ast.UnboundedBound && !interval.Start.IsPositiveInf { + startNano = math.MinInt64 // -inf + } + + if interval.End.Type == ast.TimestampBound { + endNano = interval.End.Timestamp + } else if interval.End.Type == ast.UnboundedBound && interval.End.IsPositiveInf { + endNano = math.MaxInt64 // +inf + } + + startConst := ast.Number(startNano) + endConst := ast.Number(endNano) + return ast.Pair(&startConst, &endConst) +} + +// premiseTemporalLiteral evaluates a temporal literal premise. +// This is called from oneStepEvalPremise when encountering a TemporalLiteral. +func premiseTemporalLiteral( + tl ast.TemporalLiteral, + temporalStore factstore.TemporalFactStore, + evalTime time.Time, + subst unionfind.UnionFind, +) ([]unionfind.UnionFind, error) { + te := NewTemporalEvaluator(temporalStore, evalTime) + return te.EvalTemporalLiteral(tl, subst) +} + +// ResolveHeadTime resolves variables and special bounds (like 'now') in a HeadTime interval +// using the given substitution. Returns the resolved interval or nil if HeadTime is nil. +func ResolveHeadTime(headTime *ast.Interval, subst unionfind.UnionFind, evalTime time.Time) (*ast.Interval, error) { + if headTime == nil { + return nil, nil + } + + // Resolve start bound + start, err := resolveBoundWithSubst(headTime.Start, subst, evalTime) + if err != nil { + return nil, fmt.Errorf("failed to resolve start bound: %w", err) + } + + // Resolve end bound + end, err := resolveBoundWithSubst(headTime.End, subst, evalTime) + if err != nil { + return nil, fmt.Errorf("failed to resolve end bound: %w", err) + } + + resolved := ast.NewInterval(start, end) + return &resolved, nil +} + +// resolveBoundWithSubst resolves a temporal bound, substituting variables and handling 'now'. +func resolveBoundWithSubst(bound ast.TemporalBound, subst unionfind.UnionFind, evalTime time.Time) (ast.TemporalBound, error) { + switch bound.Type { + case ast.TimestampBound: + return bound, nil + + case ast.VariableBound: + // Look up the variable in the substitution + term := subst.Get(bound.Variable) + if term == nil { + return ast.TemporalBound{}, fmt.Errorf("variable %v not bound in substitution", bound.Variable.Symbol) + } + + // The term should be a constant (either a number representing nanoseconds or a pair for an interval) + switch t := term.(type) { + case ast.Constant: + // If it's a number, interpret as nanoseconds since epoch + if t.Type == ast.NumberType { + nano, err := t.NumberValue() + if err != nil { + return ast.TemporalBound{}, fmt.Errorf("failed to get number value: %w", err) + } + return ast.TemporalBound{Type: ast.TimestampBound, Timestamp: nano}, nil + } + return ast.TemporalBound{}, fmt.Errorf("expected number constant for temporal bound, got %v", t.Type) + case ast.Variable: + // Variable is still unbound + return ast.TemporalBound{}, fmt.Errorf("variable %v not fully resolved", t.Symbol) + default: + return ast.TemporalBound{}, fmt.Errorf("unexpected term type for temporal bound: %T", term) + } + + case ast.UnboundedBound: + return bound, nil + + case ast.NowBound: + // 'now' resolves to the current evaluation time + return ast.NewTimestampBound(evalTime), nil + + default: + return ast.TemporalBound{}, fmt.Errorf("unknown bound type: %v", bound.Type) + } +} + +// DerivedTemporalFact represents a temporal fact derived from a rule. +type DerivedTemporalFact struct { + Atom ast.Atom + Interval *ast.Interval +} + +// EvalClauseWithTemporalHead evaluates a clause and produces temporal facts if the clause +// has a temporal annotation on its head. This is used for deriving new temporal facts. +func EvalClauseWithTemporalHead( + clause ast.Clause, + solutions []unionfind.UnionFind, + evalTime time.Time, +) ([]DerivedTemporalFact, error) { + var results []DerivedTemporalFact + + for _, sol := range solutions { + // Evaluate the head atom + head, err := functional.EvalAtom(clause.Head, sol) + if err != nil { + return nil, err + } + + // Resolve the temporal annotation if present + var interval *ast.Interval + if clause.HeadTime != nil { + interval, err = ResolveHeadTime(clause.HeadTime, sol, evalTime) + if err != nil { + return nil, fmt.Errorf("failed to resolve HeadTime: %w", err) + } + } + + results = append(results, DerivedTemporalFact{ + Atom: head, + Interval: interval, + }) + } + + return results, nil +} diff --git a/engine/temporal_integration_test.go b/engine/temporal_integration_test.go new file mode 100644 index 0000000..c830949 --- /dev/null +++ b/engine/temporal_integration_test.go @@ -0,0 +1,639 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package engine + +import ( + "strings" + "testing" + "time" + + "github.com/google/mangle/ast" + "github.com/google/mangle/factstore" + "github.com/google/mangle/parse" + "github.com/google/mangle/unionfind" +) + +// End-to-end integration tests for temporal reasoning in Mangle. +// These tests verify the complete flow from parsing to evaluation. + +func TestIntegration_TemporalFactParsing(t *testing.T) { + tests := []struct { + name string + program string + wantFacts int + wantTemporal bool + }{ + { + name: "simple temporal fact", + program: "foo(/bar)@[2024-01-15, 2024-06-30].", + wantFacts: 1, + wantTemporal: true, + }, + { + name: "point interval fact", + program: "event(/login)@[2024-03-15].", + wantFacts: 1, + wantTemporal: true, + }, + { + name: "non-temporal fact", + program: "regular(/fact).", + wantFacts: 1, + wantTemporal: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + unit, err := parse.Unit(strings.NewReader(test.program)) + if err != nil { + t.Fatalf("Failed to parse program: %v", err) + } + + if len(unit.Clauses) != test.wantFacts { + t.Errorf("Got %d clauses, want %d", len(unit.Clauses), test.wantFacts) + } + + if len(unit.Clauses) > 0 { + clause := unit.Clauses[0] + hasTemporal := clause.HeadTime != nil + if hasTemporal != test.wantTemporal { + t.Errorf("Clause has temporal = %v, want %v", hasTemporal, test.wantTemporal) + } + } + }) + } +} + +func TestIntegration_TemporalDeclarations(t *testing.T) { + tests := []struct { + name string + program string + predName string + wantTemporal bool + }{ + { + name: "temporal predicate declaration", + program: "Decl employee(X) temporal bound [/name].", + predName: "employee", + wantTemporal: true, + }, + { + name: "non-temporal predicate declaration", + program: "Decl config(X) bound [/string].", + predName: "config", + wantTemporal: false, + }, + { + name: "temporal with documentation", + program: `Decl status(X, Y) temporal + descr [doc("Employee status over time")] + bound [/name, /string].`, + predName: "status", + wantTemporal: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + unit, err := parse.Unit(strings.NewReader(test.program)) + if err != nil { + t.Fatalf("Failed to parse program: %v", err) + } + + // Find the declaration (skip Package decl) + var decl ast.Decl + for _, d := range unit.Decls { + if d.DeclaredAtom.Predicate.Symbol == test.predName { + decl = d + break + } + } + + if decl.DeclaredAtom.Predicate.Symbol != test.predName { + t.Fatalf("Declaration not found for predicate %s", test.predName) + } + + if decl.IsTemporal() != test.wantTemporal { + t.Errorf("Decl.IsTemporal() = %v, want %v", decl.IsTemporal(), test.wantTemporal) + } + }) + } +} + +func TestIntegration_TemporalOperators(t *testing.T) { + tests := []struct { + name string + program string + wantOpType ast.TemporalOperatorType + }{ + { + name: "diamond minus operator", + program: "recently_active(X) :- <-[0s, 168h] active(X).", + wantOpType: ast.DiamondMinus, + }, + { + name: "box minus operator", + program: "stable(X) :- [-[0s, 720h] employed(X).", + wantOpType: ast.BoxMinus, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + unit, err := parse.Unit(strings.NewReader(test.program)) + if err != nil { + t.Fatalf("Failed to parse program: %v", err) + } + + if len(unit.Clauses) != 1 { + t.Fatalf("Expected 1 clause, got %d", len(unit.Clauses)) + } + + clause := unit.Clauses[0] + if len(clause.Premises) != 1 { + t.Fatalf("Expected 1 premise, got %d", len(clause.Premises)) + } + + tempLit, ok := clause.Premises[0].(ast.TemporalLiteral) + if !ok { + t.Fatalf("Premise is %T, want TemporalLiteral", clause.Premises[0]) + } + + if tempLit.Operator == nil { + t.Fatal("TemporalLiteral.Operator is nil") + } + + if tempLit.Operator.Type != test.wantOpType { + t.Errorf("Operator.Type = %v, want %v", tempLit.Operator.Type, test.wantOpType) + } + }) + } +} + +func TestIntegration_TemporalStoreAndQuery(t *testing.T) { + // Create a temporal store + store := factstore.NewTemporalStore() + + // Add temporal facts + aliceActive := ast.NewAtom("active", name("/alice")) + bobActive := ast.NewAtom("active", name("/bob")) + + // Alice was active from Jan 1-15, 2024 + store.Add(aliceActive, ast.TimeInterval( + ast.Date(2024, 1, 1), + ast.Date(2024, 1, 15), + )) + + // Bob is active from Jan 10-31, 2024 + store.Add(bobActive, ast.TimeInterval( + ast.Date(2024, 1, 10), + ast.Date(2024, 1, 31), + )) + + tests := []struct { + name string + evalTime time.Time + wantActive []string + }{ + { + name: "Jan 5: only Alice active", + evalTime: ast.Date(2024, 1, 5), + wantActive: []string{"/alice"}, + }, + { + name: "Jan 12: both active", + evalTime: ast.Date(2024, 1, 12), + wantActive: []string{"/alice", "/bob"}, + }, + { + name: "Jan 20: only Bob active", + evalTime: ast.Date(2024, 1, 20), + wantActive: []string{"/bob"}, + }, + { + name: "Feb 1: no one active", + evalTime: ast.Date(2024, 2, 1), + wantActive: []string{}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + te := NewTemporalEvaluator(store, test.evalTime) + + query := ast.NewAtom("active", ast.Variable{Symbol: "X"}) + tl := ast.TemporalLiteral{ + Literal: query, + Operator: nil, + } + + solutions, err := te.EvalTemporalLiteral(tl, unionfind.New()) + if err != nil { + t.Fatalf("EvalTemporalLiteral failed: %v", err) + } + + // Extract the bound names + var gotActive []string + for _, sol := range solutions { + term := sol.Get(ast.Variable{Symbol: "X"}) + if term != nil { + if c, ok := term.(ast.Constant); ok { + gotActive = append(gotActive, c.Symbol) + } + } + } + + if len(gotActive) != len(test.wantActive) { + t.Errorf("Got %d active, want %d: got %v, want %v", + len(gotActive), len(test.wantActive), gotActive, test.wantActive) + } + }) + } +} + +func TestIntegration_DiamondMinusQuery(t *testing.T) { + // Create a temporal store + store := factstore.NewTemporalStore() + + // Add a fact: employee was on_leave from Jan 5-10, 2024 + onLeave := ast.NewAtom("on_leave", name("/alice")) + store.Add(onLeave, ast.TimeInterval( + ast.Date(2024, 1, 5), + ast.Date(2024, 1, 10), + )) + + tests := []struct { + name string + evalTime time.Time + lookbackDays int + wantMatch bool + }{ + { + name: "Jan 15: within 30 day lookback", + evalTime: ast.Date(2024, 1, 15), + lookbackDays: 30, + wantMatch: true, + }, + { + name: "Jan 15: within 10 day lookback (barely)", + evalTime: ast.Date(2024, 1, 15), + lookbackDays: 10, + wantMatch: true, + }, + { + name: "Jan 15: 3 day lookback misses it", + evalTime: ast.Date(2024, 1, 15), + lookbackDays: 3, + wantMatch: false, + }, + { + name: "Feb 15: 30 day lookback misses it", + evalTime: ast.Date(2024, 2, 15), + lookbackDays: 30, + wantMatch: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + te := NewTemporalEvaluator(store, test.evalTime) + + query := ast.NewAtom("on_leave", name("/alice")) + operator := ast.TemporalOperator{ + Type: ast.DiamondMinus, + Interval: ast.NewInterval( + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -1}, // ~now + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -int64(time.Duration(test.lookbackDays) * 24 * time.Hour)}, + ), + } + + tl := ast.TemporalLiteral{ + Literal: query, + Operator: &operator, + } + + solutions, err := te.EvalTemporalLiteral(tl, unionfind.New()) + if err != nil { + t.Fatalf("EvalTemporalLiteral failed: %v", err) + } + + gotMatch := len(solutions) > 0 + if gotMatch != test.wantMatch { + t.Errorf("Got match = %v, want %v", gotMatch, test.wantMatch) + } + }) + } +} + +func TestIntegration_BoxMinusQuery(t *testing.T) { + // Create a temporal store + store := factstore.NewTemporalStore() + + // Add a fact: employee was employed from Jan 1, 2023 to Dec 31, 2024 + employed := ast.NewAtom("employed", name("/alice")) + store.Add(employed, ast.TimeInterval( + ast.Date(2023, 1, 1), + ast.Date(2024, 12, 31), + )) + + tests := []struct { + name string + evalTime time.Time + lookbackDays int + wantMatch bool + }{ + { + name: "Mid-2024: employed continuously for last 365 days", + evalTime: ast.Date(2024, 6, 15), + lookbackDays: 365, + wantMatch: true, + }, + { + name: "Early 2023: employed continuously for last 30 days", + evalTime: ast.Date(2023, 2, 15), + lookbackDays: 30, + wantMatch: true, + }, + { + name: "Early 2023: NOT employed continuously for last 365 days", + evalTime: ast.Date(2023, 2, 15), + lookbackDays: 365, + wantMatch: false, // Alice wasn't employed before Jan 1, 2023 + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + te := NewTemporalEvaluator(store, test.evalTime) + + query := ast.NewAtom("employed", name("/alice")) + operator := ast.TemporalOperator{ + Type: ast.BoxMinus, + Interval: ast.NewInterval( + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -1}, // ~now + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -int64(time.Duration(test.lookbackDays) * 24 * time.Hour)}, + ), + } + + tl := ast.TemporalLiteral{ + Literal: query, + Operator: &operator, + } + + solutions, err := te.EvalTemporalLiteral(tl, unionfind.New()) + if err != nil { + t.Fatalf("EvalTemporalLiteral failed: %v", err) + } + + gotMatch := len(solutions) > 0 + if gotMatch != test.wantMatch { + t.Errorf("Got match = %v, want %v", gotMatch, test.wantMatch) + } + }) + } +} + +func TestIntegration_EternalFacts(t *testing.T) { + // Create a temporal store + store := factstore.NewTemporalStore() + + // Add an eternal fact (valid for all time) + admin := ast.NewAtom("admin", name("/root")) + store.AddEternal(admin) + + tests := []struct { + name string + evalTime time.Time + wantMatch bool + }{ + { + name: "Past: eternal fact is valid", + evalTime: ast.Date(1990, 1, 1), + wantMatch: true, + }, + { + name: "Present: eternal fact is valid", + evalTime: ast.Date(2024, 1, 1), + wantMatch: true, + }, + { + name: "Future: eternal fact is valid", + evalTime: ast.Date(2100, 1, 1), + wantMatch: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + te := NewTemporalEvaluator(store, test.evalTime) + + query := ast.NewAtom("admin", name("/root")) + tl := ast.TemporalLiteral{ + Literal: query, + Operator: nil, + } + + solutions, err := te.EvalTemporalLiteral(tl, unionfind.New()) + if err != nil { + t.Fatalf("EvalTemporalLiteral failed: %v", err) + } + + gotMatch := len(solutions) > 0 + if gotMatch != test.wantMatch { + t.Errorf("Got match = %v, want %v", gotMatch, test.wantMatch) + } + }) + } +} + +func TestIntegration_IntervalFunctions(t *testing.T) { + // Test the interval extraction functions + program := ` + # Extract interval components + Decl status(X) temporal. + ` + + _, err := parse.Unit(strings.NewReader(program)) + if err != nil { + t.Fatalf("Failed to parse program: %v", err) + } + + // Test interval to constant conversion + interval := ast.TimeInterval( + ast.Date(2024, 1, 1), + ast.Date(2024, 12, 31), + ) + + intervalConst := intervalToConstant(interval) + + // Verify it's a pair + if intervalConst.Type != ast.PairShape { + t.Errorf("intervalToConstant returned type %v, want PairShape", intervalConst.Type) + } +} + +func TestIntegration_BackwardCompatibility(t *testing.T) { + // These programs should parse and run correctly without temporal features + tests := []struct { + name string + program string + }{ + { + name: "simple facts and rules", + program: ` + node(/a). + node(/b). + edge(/a, /b). + path(X, Y) :- edge(X, Y). + path(X, Z) :- edge(X, Y), path(Y, Z). + `, + }, + { + name: "negation", + program: ` + all(/a). + all(/b). + excluded(/a). + included(X) :- all(X), !excluded(X). + `, + }, + { + name: "transforms", + program: ` + item(1). + item(2). + item(3). + total(N) :- item(X) |> do fn:group_by(), let N = fn:count(). + `, + }, + { + name: "comparisons", + program: ` + age(/alice, 30). + age(/bob, 25). + adult(Name) :- age(Name, Age), Age >= 18. + `, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + unit, err := parse.Unit(strings.NewReader(test.program)) + if err != nil { + t.Fatalf("Failed to parse program: %v", err) + } + + // Verify no clauses have temporal annotations + for i, clause := range unit.Clauses { + if clause.HeadTime != nil && !clause.HeadTime.IsEternal() { + t.Errorf("Clause %d has unexpected temporal annotation: %v", i, clause.HeadTime) + } + } + }) + } +} + +func TestIntegration_NowKeyword(t *testing.T) { + tests := []struct { + name string + program string + }{ + { + name: "now as end bound", + program: "active(/alice)@[2024-01-01, now].", + }, + { + name: "now as point interval", + program: "logged_in(/bob)@[now].", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + unit, err := parse.Unit(strings.NewReader(test.program)) + if err != nil { + t.Fatalf("Failed to parse program: %v", err) + } + + if len(unit.Clauses) != 1 { + t.Fatalf("Expected 1 clause, got %d", len(unit.Clauses)) + } + + clause := unit.Clauses[0] + if clause.HeadTime == nil { + t.Fatal("Expected temporal annotation") + } + + // Verify 'now' bound is present + hasNow := clause.HeadTime.Start.Type == ast.NowBound || + clause.HeadTime.End.Type == ast.NowBound + if !hasNow { + t.Errorf("Expected 'now' bound in interval, got %v", clause.HeadTime) + } + }) + } +} + +func TestIntegration_TemporalCoalesce(t *testing.T) { + // Test interval coalescing for overlapping facts + store := factstore.NewTemporalStore() + + status, _ := ast.Name("/active") + + // Add overlapping intervals + store.Add(ast.NewAtom("status", status), ast.TimeInterval( + ast.Date(2024, 1, 1), + ast.Date(2024, 1, 15), + )) + store.Add(ast.NewAtom("status", status), ast.TimeInterval( + ast.Date(2024, 1, 10), + ast.Date(2024, 1, 25), + )) + store.Add(ast.NewAtom("status", status), ast.TimeInterval( + ast.Date(2024, 1, 20), + ast.Date(2024, 1, 31), + )) + + // Coalesce + store.Coalesce(ast.PredicateSym{Symbol: "status", Arity: 1}) + + // Query should return a single coalesced interval + query := ast.NewAtom("status", status) + var factCount int + store.GetAllFacts(query, func(tf factstore.TemporalFact) error { + factCount++ + // Verify the coalesced interval spans the full range + if tf.Interval.Start.Type == ast.TimestampBound { + startTime := time.Unix(0, tf.Interval.Start.Timestamp).UTC() + expectedStart := ast.Date(2024, 1, 1) + if !startTime.Equal(expectedStart) { + t.Errorf("Coalesced start = %v, want %v", startTime, expectedStart) + } + } + if tf.Interval.End.Type == ast.TimestampBound { + endTime := time.Unix(0, tf.Interval.End.Timestamp).UTC() + expectedEnd := ast.Date(2024, 1, 31) + if !endTime.Equal(expectedEnd) { + t.Errorf("Coalesced end = %v, want %v", endTime, expectedEnd) + } + } + return nil + }) + + if factCount != 1 { + t.Errorf("After coalesce, got %d facts, want 1", factCount) + } +} diff --git a/engine/temporal_test.go b/engine/temporal_test.go new file mode 100644 index 0000000..02ad87b --- /dev/null +++ b/engine/temporal_test.go @@ -0,0 +1,841 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package engine + +import ( + "testing" + "time" + + "github.com/google/mangle/ast" + "github.com/google/mangle/factstore" + "github.com/google/mangle/unionfind" +) + +func name(str string) ast.Constant { + res, _ := ast.Name(str) + return res +} + +func TestTemporalEvaluator_DiamondMinus(t *testing.T) { + store := factstore.NewTemporalStore() + + // Set up test data: employee was active from Jan 1 to Jan 15, 2024 + activeAtom := ast.NewAtom("active", name("/alice")) + store.Add(activeAtom, ast.DateInterval(2024, 1, 1, 2024, 1, 15)) + + // Evaluation time: Jan 20, 2024 + evalTime := ast.Date(2024, 1, 20) + te := NewTemporalEvaluator(store, evalTime) + + tests := []struct { + name string + operator ast.TemporalOperator + wantSolns int + description string + }{ + { + name: "within_range", + // <-[0d, 30d] active(/alice) - was active sometime in last 30 days + // The operator interval uses negative timestamps to represent durations + // Start: 0 days ago (now), End: 30 days ago + operator: ast.TemporalOperator{ + Type: ast.DiamondMinus, + Interval: ast.NewInterval( + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -1}, // ~0 days ago = now (use -1 to indicate duration) + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -int64(30 * 24 * time.Hour)}, // 30 days ago + ), + }, + wantSolns: 1, + description: "Alice was active within the last 30 days", + }, + { + name: "outside_range", + // <-[0d, 3d] active(/alice) - was active sometime in last 3 days + operator: ast.TemporalOperator{ + Type: ast.DiamondMinus, + Interval: ast.NewInterval( + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -1}, // ~0 days ago + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -int64(3 * 24 * time.Hour)}, + ), + }, + wantSolns: 0, + description: "Alice was NOT active within the last 3 days (she left on Jan 15)", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tl := ast.TemporalLiteral{ + Literal: activeAtom, + Operator: &tt.operator, + } + + solutions, err := te.EvalTemporalLiteral(tl, unionfind.New()) + if err != nil { + t.Fatalf("EvalTemporalLiteral error: %v", err) + } + + if len(solutions) != tt.wantSolns { + t.Errorf("%s: got %d solutions, want %d", tt.description, len(solutions), tt.wantSolns) + } + }) + } +} + +func TestTemporalEvaluator_BoxMinus(t *testing.T) { + store := factstore.NewTemporalStore() + + // Set up test data: service was running from Dec 1, 2023 to Feb 1, 2024 + runningAtom := ast.NewAtom("running", name("/service")) + store.Add(runningAtom, ast.TimeInterval( + ast.Date(2023, 12, 1), + ast.Date(2024, 2, 1), + )) + + // Evaluation time: Jan 15, 2024 + evalTime := ast.Date(2024, 1, 15) + te := NewTemporalEvaluator(store, evalTime) + + tests := []struct { + name string + operator ast.TemporalOperator + wantSolns int + description string + }{ + { + name: "continuously_true", + // [-[0d, 30d] running(/service) - was running continuously for last 30 days + operator: ast.TemporalOperator{ + Type: ast.BoxMinus, + Interval: ast.NewInterval( + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -0}, + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -int64(30 * 24 * time.Hour)}, + ), + }, + wantSolns: 1, + description: "Service was running continuously for the last 30 days", + }, + { + name: "not_continuously_true", + // [-[0d, 60d] running(/service) - was running continuously for last 60 days + // This should fail because service started on Dec 1, which is only ~45 days ago + operator: ast.TemporalOperator{ + Type: ast.BoxMinus, + Interval: ast.NewInterval( + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -0}, + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -int64(60 * 24 * time.Hour)}, + ), + }, + wantSolns: 0, + description: "Service was NOT running for the full 60 days (only started Dec 1)", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tl := ast.TemporalLiteral{ + Literal: runningAtom, + Operator: &tt.operator, + } + + solutions, err := te.EvalTemporalLiteral(tl, unionfind.New()) + if err != nil { + t.Fatalf("EvalTemporalLiteral error: %v", err) + } + + if len(solutions) != tt.wantSolns { + t.Errorf("%s: got %d solutions, want %d", tt.description, len(solutions), tt.wantSolns) + } + }) + } +} + +func TestTemporalEvaluator_WithVariable(t *testing.T) { + store := factstore.NewTemporalStore() + + // Add multiple employees with different active periods + store.Add(ast.NewAtom("active", name("/alice")), ast.TimeInterval( + ast.Date(2024, 1, 1), + ast.Date(2024, 1, 15), + )) + store.Add(ast.NewAtom("active", name("/bob")), ast.TimeInterval( + ast.Date(2024, 1, 10), + ast.Date(2024, 1, 25), + )) + store.Add(ast.NewAtom("active", name("/charlie")), ast.TimeInterval( + ast.Date(2023, 6, 1), + ast.Date(2023, 12, 31), + )) + + // Evaluation time: Jan 20, 2024 + evalTime := ast.Date(2024, 1, 20) + te := NewTemporalEvaluator(store, evalTime) + + // Query: <-[0d, 30d] active(X) - find who was active in last 30 days + queryAtom := ast.NewAtom("active", ast.Variable{Symbol: "X"}) + operator := ast.TemporalOperator{ + Type: ast.DiamondMinus, + Interval: ast.NewInterval( + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -1}, // ~0 days ago + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -int64(30 * 24 * time.Hour)}, + ), + } + + tl := ast.TemporalLiteral{ + Literal: queryAtom, + Operator: &operator, + } + + solutions, err := te.EvalTemporalLiteral(tl, unionfind.New()) + if err != nil { + t.Fatalf("EvalTemporalLiteral error: %v", err) + } + + // Should find Alice, Bob, AND Charlie + // (Alice: Jan 1-15, Bob: Jan 10-25, Charlie: Jun-Dec 31 2023) + // At Jan 20, looking back 30 days (Dec 21 - Jan 20), all three overlap + if len(solutions) != 3 { + t.Errorf("Expected 3 solutions (Alice, Bob, and Charlie), got %d", len(solutions)) + } +} + +func TestTemporalEvaluator_EternalFact(t *testing.T) { + store := factstore.NewTemporalStore() + + // Add an eternal fact (valid for all time) + store.AddEternal(ast.NewAtom("admin", name("/root"))) + + evalTime := ast.Date(2024, 1, 20) + te := NewTemporalEvaluator(store, evalTime) + + // Query with any past operator should succeed for eternal facts + queryAtom := ast.NewAtom("admin", name("/root")) + operator := ast.TemporalOperator{ + Type: ast.DiamondMinus, + Interval: ast.NewInterval( + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -0}, + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -int64(365 * 24 * time.Hour)}, + ), + } + + tl := ast.TemporalLiteral{ + Literal: queryAtom, + Operator: &operator, + } + + solutions, err := te.EvalTemporalLiteral(tl, unionfind.New()) + if err != nil { + t.Fatalf("EvalTemporalLiteral error: %v", err) + } + + if len(solutions) != 1 { + t.Errorf("Eternal fact should match any temporal query, got %d solutions", len(solutions)) + } +} + +func TestTemporalEvaluator_NoOperator(t *testing.T) { + store := factstore.NewTemporalStore() + + // Add a temporal fact + store.Add(ast.NewAtom("status", name("/ok")), ast.TimeInterval( + ast.Date(2024, 1, 1), + ast.Date(2024, 1, 31), + )) + + tests := []struct { + name string + evalTime time.Time + wantSolns int + }{ + { + name: "within_validity", + evalTime: ast.Date(2024, 1, 15), + wantSolns: 1, + }, + { + name: "before_validity", + evalTime: ast.Date(2023, 12, 15), + wantSolns: 0, + }, + { + name: "after_validity", + evalTime: ast.Date(2024, 2, 15), + wantSolns: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + te := NewTemporalEvaluator(store, tt.evalTime) + + // Temporal literal without operator - just checks if fact is valid at eval time + tl := ast.TemporalLiteral{ + Literal: ast.NewAtom("status", name("/ok")), + Operator: nil, + } + + solutions, err := te.EvalTemporalLiteral(tl, unionfind.New()) + if err != nil { + t.Fatalf("EvalTemporalLiteral error: %v", err) + } + + if len(solutions) != tt.wantSolns { + t.Errorf("At %v: got %d solutions, want %d", tt.evalTime, len(solutions), tt.wantSolns) + } + }) + } +} + +func TestTemporalEvaluator_DiamondPlus(t *testing.T) { + store := factstore.NewTemporalStore() + + // Set up test data: scheduled maintenance from Feb 1 to Feb 15, 2024 + maintenanceAtom := ast.NewAtom("maintenance", name("/server")) + store.Add(maintenanceAtom, ast.TimeInterval( + ast.Date(2024, 2, 1), + ast.Date(2024, 2, 15), + )) + + // Evaluation time: Jan 20, 2024 + evalTime := ast.Date(2024, 1, 20) + te := NewTemporalEvaluator(store, evalTime) + + tests := []struct { + name string + operator ast.TemporalOperator + wantSolns int + description string + }{ + { + name: "within_future_range", + // <+[0d, 30d] maintenance(/server) - will there be maintenance in the next 30 days? + operator: ast.TemporalOperator{ + Type: ast.DiamondPlus, + Interval: ast.NewInterval( + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -1}, + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -int64(30 * 24 * time.Hour)}, + ), + }, + wantSolns: 1, + description: "Maintenance is scheduled within the next 30 days", + }, + { + name: "outside_future_range", + // <+[0d, 5d] maintenance(/server) - will there be maintenance in next 5 days? + operator: ast.TemporalOperator{ + Type: ast.DiamondPlus, + Interval: ast.NewInterval( + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -1}, + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -int64(5 * 24 * time.Hour)}, + ), + }, + wantSolns: 0, + description: "No maintenance in the next 5 days (starts Feb 1)", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tl := ast.TemporalLiteral{ + Literal: maintenanceAtom, + Operator: &tt.operator, + } + + solutions, err := te.EvalTemporalLiteral(tl, unionfind.New()) + if err != nil { + t.Fatalf("EvalTemporalLiteral error: %v", err) + } + + if len(solutions) != tt.wantSolns { + t.Errorf("%s: got %d solutions, want %d", tt.description, len(solutions), tt.wantSolns) + } + }) + } +} + +func TestTemporalEvaluator_BoxPlus(t *testing.T) { + store := factstore.NewTemporalStore() + + // Set up test data: contract valid from Jan 1, 2024 to Dec 31, 2024 + contractAtom := ast.NewAtom("contract", name("/customer")) + store.Add(contractAtom, ast.TimeInterval( + ast.Date(2024, 1, 1), + ast.Date(2024, 12, 31), + )) + + // Evaluation time: Jan 15, 2024 + evalTime := ast.Date(2024, 1, 15) + te := NewTemporalEvaluator(store, evalTime) + + tests := []struct { + name string + operator ast.TemporalOperator + wantSolns int + description string + }{ + { + name: "continuously_true_future", + // [+[0d, 30d] contract(/customer) - will contract be valid for all of next 30 days? + operator: ast.TemporalOperator{ + Type: ast.BoxPlus, + Interval: ast.NewInterval( + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -1}, + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -int64(30 * 24 * time.Hour)}, + ), + }, + wantSolns: 1, + description: "Contract is valid for the entire next 30 days", + }, + { + name: "not_continuously_true_future", + // [+[0d, 365d] contract(/customer) - will contract be valid for all of next 365 days? + operator: ast.TemporalOperator{ + Type: ast.BoxPlus, + Interval: ast.NewInterval( + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -1}, + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -int64(365 * 24 * time.Hour)}, + ), + }, + wantSolns: 0, + description: "Contract expires before 365 days (ends Dec 31)", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tl := ast.TemporalLiteral{ + Literal: contractAtom, + Operator: &tt.operator, + } + + solutions, err := te.EvalTemporalLiteral(tl, unionfind.New()) + if err != nil { + t.Fatalf("EvalTemporalLiteral error: %v", err) + } + + if len(solutions) != tt.wantSolns { + t.Errorf("%s: got %d solutions, want %d", tt.description, len(solutions), tt.wantSolns) + } + }) + } +} + +func TestTemporalEvaluator_IntervalVariableBinding(t *testing.T) { + store := factstore.NewTemporalStore() + + // Add a fact with a known interval + startTime := ast.Date(2024, 1, 1) + endTime := ast.Date(2024, 1, 31) + store.Add(ast.NewAtom("event", name("/meeting")), ast.TimeInterval(startTime, endTime)) + + evalTime := ast.Date(2024, 1, 15) + te := NewTemporalEvaluator(store, evalTime) + + // Query with interval variable binding + intervalVar := ast.Variable{Symbol: "T"} + tl := ast.TemporalLiteral{ + Literal: ast.NewAtom("event", name("/meeting")), + Operator: nil, + IntervalVar: &intervalVar, + } + + solutions, err := te.EvalTemporalLiteral(tl, unionfind.New()) + if err != nil { + t.Fatalf("EvalTemporalLiteral error: %v", err) + } + + if len(solutions) != 1 { + t.Fatalf("Expected 1 solution, got %d", len(solutions)) + } + + // Check that the interval variable was bound + boundValue := solutions[0].Get(intervalVar) + if boundValue == nil { + t.Error("Interval variable T was not bound") + } + + // The bound value should be a pair constant + if c, ok := boundValue.(ast.Constant); ok { + if c.Type != ast.PairShape { + t.Errorf("Expected pair constant for interval, got %v", c.Type) + } + } +} + +func TestIntervalContains(t *testing.T) { + te := &TemporalEvaluator{} + + jan1 := ast.Date(2024, 1, 1) + jan15 := ast.Date(2024, 1, 15) + jan10 := ast.Date(2024, 1, 10) + jan20 := ast.Date(2024, 1, 20) + jan31 := ast.Date(2024, 1, 31) + + tests := []struct { + name string + a ast.Interval // container + b ast.Interval // contained + want bool + }{ + { + name: "exact_match", + a: ast.TimeInterval(jan1, jan31), + b: ast.TimeInterval(jan1, jan31), + want: true, + }, + { + name: "a_contains_b", + a: ast.TimeInterval(jan1, jan31), + b: ast.TimeInterval(jan10, jan20), + want: true, + }, + { + name: "a_does_not_contain_b_start", + a: ast.TimeInterval(jan10, jan31), + b: ast.TimeInterval(jan1, jan20), + want: false, + }, + { + name: "a_does_not_contain_b_end", + a: ast.TimeInterval(jan1, jan15), + b: ast.TimeInterval(jan10, jan31), + want: false, + }, + { + name: "eternal_contains_any", + a: ast.EternalInterval(), + b: ast.TimeInterval(jan1, jan31), + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := te.intervalContains(tt.a, tt.b) + if got != tt.want { + t.Errorf("intervalContains(%v, %v) = %v, want %v", tt.a, tt.b, got, tt.want) + } + }) + } +} + +// Benchmarks + +func BenchmarkTemporalEvaluator_DiamondMinus(b *testing.B) { + store := factstore.NewTemporalStore() + + // Add many facts + for i := 0; i < 1000; i++ { + atomName, _ := ast.Name("/user" + string(rune('0'+i%10))) + store.Add(ast.NewAtom("active", atomName), ast.TimeInterval( + time.Date(2024, 1, 1+i%28, 0, 0, 0, 0, time.UTC), + time.Date(2024, 1, 15+i%15, 0, 0, 0, 0, time.UTC), + )) + } + + evalTime := ast.Date(2024, 1, 20) + te := NewTemporalEvaluator(store, evalTime) + + queryAtom := ast.NewAtom("active", ast.Variable{Symbol: "X"}) + operator := ast.TemporalOperator{ + Type: ast.DiamondMinus, + Interval: ast.NewInterval( + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -1}, + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: -int64(30 * 24 * time.Hour)}, + ), + } + + tl := ast.TemporalLiteral{ + Literal: queryAtom, + Operator: &operator, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := te.EvalTemporalLiteral(tl, unionfind.New()) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkTemporalStore_GetFactsDuring(b *testing.B) { + store := factstore.NewTemporalStore() + + // Add many facts + for i := 0; i < 10000; i++ { + atomName, _ := ast.Name("/item" + string(rune('0'+i%100))) + store.Add(ast.NewAtom("available", atomName), ast.TimeInterval( + time.Date(2024, 1, 1+i%28, 0, 0, 0, 0, time.UTC), + time.Date(2024, 2, 1+i%28, 0, 0, 0, 0, time.UTC), + )) + } + + query := ast.NewAtom("available", ast.Variable{Symbol: "X"}) + interval := ast.TimeInterval( + ast.Date(2024, 1, 10), + ast.Date(2024, 1, 20), + ) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + count := 0 + store.GetFactsDuring(query, interval, func(tf factstore.TemporalFact) error { + count++ + return nil + }) + } +} + +func BenchmarkIntervalCoalesce(b *testing.B) { + for i := 0; i < b.N; i++ { + store := factstore.NewTemporalStore() + + // Add overlapping intervals + pred, _ := ast.Name("/test") + for j := 0; j < 100; j++ { + store.Add(ast.NewAtom("status", pred), ast.TimeInterval( + time.Date(2024, 1, 1+j, 0, 0, 0, 0, time.UTC), + time.Date(2024, 1, 3+j, 0, 0, 0, 0, time.UTC), + )) + } + + store.Coalesce(ast.PredicateSym{Symbol: "status", Arity: 1}) + } +} + +// Tests for derived temporal facts (rules that produce temporal facts) + +func TestResolveHeadTime_NilHeadTime(t *testing.T) { + evalTime := ast.Date(2024, 1, 15) + subst := unionfind.New() + + result, err := ResolveHeadTime(nil, subst, evalTime) + if err != nil { + t.Fatalf("ResolveHeadTime(nil) returned error: %v", err) + } + if result != nil { + t.Errorf("ResolveHeadTime(nil) = %v, want nil", result) + } +} + +func TestResolveHeadTime_TimestampBounds(t *testing.T) { + evalTime := ast.Date(2024, 1, 15) + subst := unionfind.New() + + // Create an interval with timestamp bounds + start := ast.Date(2024, 1, 1) + end := ast.Date(2024, 6, 30) + headTime := ast.NewInterval( + ast.NewTimestampBound(start), + ast.NewTimestampBound(end), + ) + + result, err := ResolveHeadTime(&headTime, subst, evalTime) + if err != nil { + t.Fatalf("ResolveHeadTime returned error: %v", err) + } + if result == nil { + t.Fatal("ResolveHeadTime returned nil") + } + if !result.Equals(headTime) { + t.Errorf("ResolveHeadTime = %v, want %v", result, headTime) + } +} + +func TestResolveHeadTime_NowBound(t *testing.T) { + evalTime := time.Date(2024, 1, 15, 12, 0, 0, 0, time.UTC) + subst := unionfind.New() + + // Create an interval with 'now' as the end bound + start := ast.Date(2024, 1, 1) + headTime := ast.NewInterval( + ast.NewTimestampBound(start), + ast.Now(), // 'now' bound + ) + + result, err := ResolveHeadTime(&headTime, subst, evalTime) + if err != nil { + t.Fatalf("ResolveHeadTime returned error: %v", err) + } + if result == nil { + t.Fatal("ResolveHeadTime returned nil") + } + + // The 'now' bound should be resolved to evalTime + expectedEnd := ast.NewTimestampBound(evalTime) + if result.End.Type != ast.TimestampBound || result.End.Timestamp != expectedEnd.Timestamp { + t.Errorf("ResolveHeadTime end = %v, want %v", result.End, expectedEnd) + } +} + +func TestResolveHeadTime_VariableBound(t *testing.T) { + evalTime := ast.Date(2024, 1, 15) + + // Create a substitution with a bound variable + subst := unionfind.New() + startTime := ast.Date(2024, 2, 1) + endTime := ast.Date(2024, 2, 28) + + // Bind T1 and T2 to timestamp values (as nanoseconds) + t1Var := ast.Variable{Symbol: "T1"} + t2Var := ast.Variable{Symbol: "T2"} + subst, _ = unionfind.UnifyTermsExtend( + []ast.BaseTerm{t1Var}, + []ast.BaseTerm{ast.Number(startTime.UnixNano())}, + subst, + ) + subst, _ = unionfind.UnifyTermsExtend( + []ast.BaseTerm{t2Var}, + []ast.BaseTerm{ast.Number(endTime.UnixNano())}, + subst, + ) + + // Create an interval with variable bounds + headTime := ast.NewInterval( + ast.NewVariableBound(t1Var), + ast.NewVariableBound(t2Var), + ) + + result, err := ResolveHeadTime(&headTime, subst, evalTime) + if err != nil { + t.Fatalf("ResolveHeadTime returned error: %v", err) + } + if result == nil { + t.Fatal("ResolveHeadTime returned nil") + } + + // Check that the variables were resolved + if result.Start.Type != ast.TimestampBound { + t.Errorf("ResolveHeadTime start type = %v, want TimestampBound", result.Start.Type) + } + if result.Start.Timestamp != startTime.UnixNano() { + t.Errorf("ResolveHeadTime start = %v, want %v", result.Start.Timestamp, startTime.UnixNano()) + } + if result.End.Type != ast.TimestampBound { + t.Errorf("ResolveHeadTime end type = %v, want TimestampBound", result.End.Type) + } + if result.End.Timestamp != endTime.UnixNano() { + t.Errorf("ResolveHeadTime end = %v, want %v", result.End.Timestamp, endTime.UnixNano()) + } +} + +func TestEvalClauseWithTemporalHead(t *testing.T) { + evalTime := ast.Date(2024, 1, 15) + + tests := []struct { + name string + clause ast.Clause + solutions []unionfind.UnionFind + wantFacts int + wantHasInt bool // whether we expect intervals + }{ + { + name: "clause_without_temporal_head", + clause: ast.Clause{ + Head: ast.NewAtom("derived", ast.Variable{Symbol: "X"}), + HeadTime: nil, + Premises: []ast.Term{}, + }, + solutions: []unionfind.UnionFind{ + func() unionfind.UnionFind { + s := unionfind.New() + s, _ = unionfind.UnifyTermsExtend( + []ast.BaseTerm{ast.Variable{Symbol: "X"}}, + []ast.BaseTerm{name("/alice")}, + s, + ) + return s + }(), + }, + wantFacts: 1, + wantHasInt: false, + }, + { + name: "clause_with_temporal_head_timestamp", + clause: func() ast.Clause { + interval := ast.NewInterval( + ast.NewTimestampBound(ast.Date(2024, 1, 1)), + ast.NewTimestampBound(ast.Date(2024, 12, 31)), + ) + return ast.Clause{ + Head: ast.NewAtom("active", ast.Variable{Symbol: "X"}), + HeadTime: &interval, + Premises: []ast.Term{}, + } + }(), + solutions: []unionfind.UnionFind{ + func() unionfind.UnionFind { + s := unionfind.New() + s, _ = unionfind.UnifyTermsExtend( + []ast.BaseTerm{ast.Variable{Symbol: "X"}}, + []ast.BaseTerm{name("/bob")}, + s, + ) + return s + }(), + }, + wantFacts: 1, + wantHasInt: true, + }, + { + name: "clause_with_temporal_head_now", + clause: func() ast.Clause { + interval := ast.NewInterval( + ast.NewTimestampBound(ast.Date(2024, 1, 1)), + ast.Now(), + ) + return ast.Clause{ + Head: ast.NewAtom("current", ast.Variable{Symbol: "X"}), + HeadTime: &interval, + Premises: []ast.Term{}, + } + }(), + solutions: []unionfind.UnionFind{ + func() unionfind.UnionFind { + s := unionfind.New() + s, _ = unionfind.UnifyTermsExtend( + []ast.BaseTerm{ast.Variable{Symbol: "X"}}, + []ast.BaseTerm{name("/charlie")}, + s, + ) + return s + }(), + }, + wantFacts: 1, + wantHasInt: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + results, err := EvalClauseWithTemporalHead(test.clause, test.solutions, evalTime) + if err != nil { + t.Fatalf("EvalClauseWithTemporalHead returned error: %v", err) + } + + if len(results) != test.wantFacts { + t.Errorf("got %d facts, want %d", len(results), test.wantFacts) + } + + for i, result := range results { + hasInterval := result.Interval != nil + if hasInterval != test.wantHasInt { + t.Errorf("result[%d] has interval = %v, want %v", i, hasInterval, test.wantHasInt) + } + } + }) + } +} diff --git a/factstore/interval_tree.go b/factstore/interval_tree.go new file mode 100644 index 0000000..f5e1a19 --- /dev/null +++ b/factstore/interval_tree.go @@ -0,0 +1,392 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package factstore + +import ( + "github.com/google/mangle/ast" +) + +// IntervalTree is an augmented interval tree for efficient interval queries. +// It uses a balanced BST (AVL tree) where each node stores: +// - An interval with start and end times +// - The maximum end time in the subtree rooted at this node +// This enables O(log n + k) queries where n is the number of intervals +// and k is the number of matching results. +type IntervalTree struct { + root *treeNode + size int +} + +// treeNode represents a node in the interval tree. +type treeNode struct { + interval ast.Interval + maxEnd int64 // Maximum end time in subtree + height int // Height for AVL balancing + left *treeNode + right *treeNode +} + +// NewIntervalTree creates a new interval tree. +func NewIntervalTree() *IntervalTree { + return &IntervalTree{} +} + +// Insert adds an interval to the tree. +// Returns true if the interval was added, false if it was a duplicate. +func (t *IntervalTree) Insert(interval ast.Interval) bool { + // Check for duplicate + if t.contains(interval) { + return false + } + + t.root = t.insert(t.root, interval) + t.size++ + return true +} + +// insert recursively inserts an interval and rebalances. +func (t *IntervalTree) insert(node *treeNode, interval ast.Interval) *treeNode { + if node == nil { + return &treeNode{ + interval: interval, + maxEnd: getEndTime(interval), + height: 1, + } + } + + start := getStartTime(interval) + nodeStart := getStartTime(node.interval) + + if start < nodeStart { + node.left = t.insert(node.left, interval) + } else { + node.right = t.insert(node.right, interval) + } + + // Update max end + node.maxEnd = maxInt(node.maxEnd, getEndTime(interval)) + + // Rebalance + return t.rebalance(node) +} + +// contains checks if an exact interval exists in the tree. +func (t *IntervalTree) contains(interval ast.Interval) bool { + return t.findExact(t.root, interval) +} + +// findExact searches for an exact interval match. +func (t *IntervalTree) findExact(node *treeNode, interval ast.Interval) bool { + if node == nil { + return false + } + + if node.interval.Equals(interval) { + return true + } + + start := getStartTime(interval) + nodeStart := getStartTime(node.interval) + + if start < nodeStart { + return t.findExact(node.left, interval) + } + // Search both subtrees for same start time + if t.findExact(node.right, interval) { + return true + } + if start == nodeStart { + return t.findExact(node.left, interval) + } + return false +} + +// QueryPoint returns all intervals containing the given timestamp. +func (t *IntervalTree) QueryPoint(timestamp int64, fn func(ast.Interval) error) error { + return t.queryPoint(t.root, timestamp, fn) +} + +// queryPoint recursively queries for intervals containing a point. +func (t *IntervalTree) queryPoint(node *treeNode, timestamp int64, fn func(ast.Interval) error) error { + if node == nil { + return nil + } + + // If max end in this subtree is less than timestamp, no intervals here contain it + if node.maxEnd < timestamp { + return nil + } + + // Check left subtree + if err := t.queryPoint(node.left, timestamp, fn); err != nil { + return err + } + + // Check current node + if containsTimestamp(node.interval, timestamp) { + if err := fn(node.interval); err != nil { + return err + } + } + + // Check right subtree only if its intervals could contain the timestamp + // (start time <= timestamp is guaranteed by the query, we check end time via maxEnd) + nodeStart := getStartTime(node.interval) + if timestamp >= nodeStart { + if err := t.queryPoint(node.right, timestamp, fn); err != nil { + return err + } + } + + return nil +} + +// QueryRange returns all intervals overlapping with [start, end]. +func (t *IntervalTree) QueryRange(start, end int64, fn func(ast.Interval) error) error { + return t.queryRange(t.root, start, end, fn) +} + +// queryRange recursively queries for overlapping intervals. +// Two intervals [s1, e1] and [s2, e2] overlap if s1 <= e2 AND s2 <= e1. +func (t *IntervalTree) queryRange(node *treeNode, start, end int64, fn func(ast.Interval) error) error { + if node == nil { + return nil + } + + // If max end in this subtree is less than query start, no overlap possible + if node.maxEnd < start { + return nil + } + + // Check left subtree + if err := t.queryRange(node.left, start, end, fn); err != nil { + return err + } + + // Check current node for overlap + nodeStart := getStartTime(node.interval) + nodeEnd := getEndTime(node.interval) + + // Overlap condition: nodeStart <= end AND start <= nodeEnd + if nodeStart <= end && start <= nodeEnd { + if err := fn(node.interval); err != nil { + return err + } + } + + // Check right subtree only if possible overlap exists + if nodeStart <= end { + if err := t.queryRange(node.right, start, end, fn); err != nil { + return err + } + } + + return nil +} + +// All calls fn for every interval in the tree (in-order traversal). +func (t *IntervalTree) All(fn func(ast.Interval) error) error { + return t.inOrder(t.root, fn) +} + +// inOrder performs an in-order traversal. +func (t *IntervalTree) inOrder(node *treeNode, fn func(ast.Interval) error) error { + if node == nil { + return nil + } + + if err := t.inOrder(node.left, fn); err != nil { + return err + } + if err := fn(node.interval); err != nil { + return err + } + return t.inOrder(node.right, fn) +} + +// Size returns the number of intervals in the tree. +func (t *IntervalTree) Size() int { + return t.size +} + +// Clear removes all intervals from the tree. +func (t *IntervalTree) Clear() { + t.root = nil + t.size = 0 +} + +// Rebuild reconstructs the tree from a list of intervals (for coalescing). +func (t *IntervalTree) Rebuild(intervals []ast.Interval) { + t.Clear() + for _, interval := range intervals { + t.Insert(interval) + } +} + +// AVL tree helper functions + +// height returns the height of a node. +func height(node *treeNode) int { + if node == nil { + return 0 + } + return node.height +} + +// updateHeight updates the height of a node based on children. +func updateHeight(node *treeNode) { + left := height(node.left) + right := height(node.right) + if left > right { + node.height = 1 + left + } else { + node.height = 1 + right + } +} + +// balanceFactor returns the balance factor of a node. +func balanceFactor(node *treeNode) int { + if node == nil { + return 0 + } + return height(node.left) - height(node.right) +} + +// updateMaxEnd updates the maxEnd value of a node based on children. +func updateMaxEnd(node *treeNode) { + node.maxEnd = getEndTime(node.interval) + if node.left != nil && node.left.maxEnd > node.maxEnd { + node.maxEnd = node.left.maxEnd + } + if node.right != nil && node.right.maxEnd > node.maxEnd { + node.maxEnd = node.right.maxEnd + } +} + +// rotateRight performs a right rotation. +func (t *IntervalTree) rotateRight(y *treeNode) *treeNode { + x := y.left + z := x.right + + x.right = y + y.left = z + + updateHeight(y) + updateMaxEnd(y) + updateHeight(x) + updateMaxEnd(x) + + return x +} + +// rotateLeft performs a left rotation. +func (t *IntervalTree) rotateLeft(x *treeNode) *treeNode { + y := x.right + z := y.left + + y.left = x + x.right = z + + updateHeight(x) + updateMaxEnd(x) + updateHeight(y) + updateMaxEnd(y) + + return y +} + +// rebalance rebalances a node after insertion. +func (t *IntervalTree) rebalance(node *treeNode) *treeNode { + updateHeight(node) + updateMaxEnd(node) + + balance := balanceFactor(node) + + // Left-heavy + if balance > 1 { + if balanceFactor(node.left) < 0 { + // Left-Right case + node.left = t.rotateLeft(node.left) + } + // Left-Left case + return t.rotateRight(node) + } + + // Right-heavy + if balance < -1 { + if balanceFactor(node.right) > 0 { + // Right-Left case + node.right = t.rotateRight(node.right) + } + // Right-Right case + return t.rotateLeft(node) + } + + return node +} + +// max returns the maximum of two int64 values. +func maxInt(a, b int64) int64 { + if a > b { + return a + } + return b +} + +// Helper functions for interval time extraction + +// getStartTime extracts the start time from an interval. +// Returns MinInt64 for unbounded start (negative infinity). +func getStartTime(interval ast.Interval) int64 { + switch interval.Start.Type { + case ast.TimestampBound: + return interval.Start.Timestamp + case ast.UnboundedBound: + if !interval.Start.IsPositiveInf { + return minInt64 + } + return maxInt64 + default: + return 0 + } +} + +// getEndTime extracts the end time from an interval. +// Returns MaxInt64 for unbounded end (positive infinity). +func getEndTime(interval ast.Interval) int64 { + switch interval.End.Type { + case ast.TimestampBound: + return interval.End.Timestamp + case ast.UnboundedBound: + if interval.End.IsPositiveInf { + return maxInt64 + } + return minInt64 + default: + return 0 + } +} + +// containsTimestamp checks if an interval contains a timestamp. +func containsTimestamp(interval ast.Interval, timestamp int64) bool { + start := getStartTime(interval) + end := getEndTime(interval) + return start <= timestamp && timestamp <= end +} + +const ( + minInt64 = -1 << 63 + maxInt64 = 1<<63 - 1 +) diff --git a/factstore/interval_tree_test.go b/factstore/interval_tree_test.go new file mode 100644 index 0000000..85c9b66 --- /dev/null +++ b/factstore/interval_tree_test.go @@ -0,0 +1,335 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package factstore + +import ( + "testing" + "time" + + "github.com/google/mangle/ast" +) + +func TestIntervalTree_Insert(t *testing.T) { + tree := NewIntervalTree() + + i1 := makeTestInterval(100, 200) + i2 := makeTestInterval(150, 250) + i3 := makeTestInterval(50, 75) + + // Insert first interval + if !tree.Insert(i1) { + t.Error("Expected first insert to succeed") + } + if tree.Size() != 1 { + t.Errorf("Expected size 1, got %d", tree.Size()) + } + + // Insert duplicate + if tree.Insert(i1) { + t.Error("Expected duplicate insert to fail") + } + if tree.Size() != 1 { + t.Errorf("Expected size still 1 after duplicate, got %d", tree.Size()) + } + + // Insert different intervals + if !tree.Insert(i2) { + t.Error("Expected second insert to succeed") + } + if !tree.Insert(i3) { + t.Error("Expected third insert to succeed") + } + if tree.Size() != 3 { + t.Errorf("Expected size 3, got %d", tree.Size()) + } +} + +func TestIntervalTree_QueryPoint(t *testing.T) { + tree := NewIntervalTree() + + // Insert intervals: [100, 200], [150, 250], [50, 75], [300, 400] + tree.Insert(makeTestInterval(100, 200)) + tree.Insert(makeTestInterval(150, 250)) + tree.Insert(makeTestInterval(50, 75)) + tree.Insert(makeTestInterval(300, 400)) + + tests := []struct { + name string + timestamp int64 + wantCount int + }{ + {"before all", 25, 0}, + {"in first interval only", 60, 1}, + {"at start of interval", 100, 1}, + {"in overlapping region", 175, 2}, + {"at end of first interval (overlaps with second)", 200, 2}, + {"between intervals", 275, 0}, + {"in last interval", 350, 1}, + {"after all", 500, 0}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + count := 0 + tree.QueryPoint(tt.timestamp, func(interval ast.Interval) error { + count++ + return nil + }) + if count != tt.wantCount { + t.Errorf("QueryPoint(%d) returned %d intervals, want %d", tt.timestamp, count, tt.wantCount) + } + }) + } +} + +func TestIntervalTree_QueryRange(t *testing.T) { + tree := NewIntervalTree() + + // Insert intervals: [100, 200], [150, 250], [50, 75], [300, 400] + tree.Insert(makeTestInterval(100, 200)) + tree.Insert(makeTestInterval(150, 250)) + tree.Insert(makeTestInterval(50, 75)) + tree.Insert(makeTestInterval(300, 400)) + + tests := []struct { + name string + start int64 + end int64 + wantCount int + }{ + {"before all", 0, 40, 0}, + {"overlap first", 60, 70, 1}, + {"overlap two", 175, 180, 2}, + {"overlap all middle", 100, 250, 2}, + {"gap between", 275, 290, 0}, + {"overlap last", 350, 375, 1}, + {"overlap all", 0, 500, 4}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + count := 0 + tree.QueryRange(tt.start, tt.end, func(interval ast.Interval) error { + count++ + return nil + }) + if count != tt.wantCount { + t.Errorf("QueryRange(%d, %d) returned %d intervals, want %d", tt.start, tt.end, count, tt.wantCount) + } + }) + } +} + +func TestIntervalTree_Balance(t *testing.T) { + tree := NewIntervalTree() + + // Insert many intervals in sorted order (worst case for naive BST) + for i := int64(0); i < 100; i++ { + tree.Insert(makeTestInterval(i*10, i*10+5)) + } + + if tree.Size() != 100 { + t.Errorf("Expected size 100, got %d", tree.Size()) + } + + // Tree should be balanced, so root height should be O(log n) + // For 100 elements, height should be around 7-8 (log2(100) ≈ 6.6) + if tree.root == nil { + t.Fatal("Root should not be nil") + } + if tree.root.height > 10 { + t.Errorf("Tree appears unbalanced: height %d for 100 elements", tree.root.height) + } +} + +func TestIntervalTree_All(t *testing.T) { + tree := NewIntervalTree() + + intervals := []ast.Interval{ + makeTestInterval(100, 200), + makeTestInterval(50, 75), + makeTestInterval(150, 250), + } + + for _, i := range intervals { + tree.Insert(i) + } + + count := 0 + tree.All(func(interval ast.Interval) error { + count++ + return nil + }) + + if count != len(intervals) { + t.Errorf("All() returned %d intervals, want %d", count, len(intervals)) + } +} + +func TestIntervalTree_Clear(t *testing.T) { + tree := NewIntervalTree() + + tree.Insert(makeTestInterval(100, 200)) + tree.Insert(makeTestInterval(50, 75)) + + if tree.Size() != 2 { + t.Errorf("Expected size 2 before clear, got %d", tree.Size()) + } + + tree.Clear() + + if tree.Size() != 0 { + t.Errorf("Expected size 0 after clear, got %d", tree.Size()) + } +} + +func TestIntervalTree_Rebuild(t *testing.T) { + tree := NewIntervalTree() + + // Initial intervals + tree.Insert(makeTestInterval(100, 200)) + tree.Insert(makeTestInterval(50, 75)) + + // Rebuild with different intervals + newIntervals := []ast.Interval{ + makeTestInterval(300, 400), + makeTestInterval(500, 600), + makeTestInterval(700, 800), + } + + tree.Rebuild(newIntervals) + + if tree.Size() != 3 { + t.Errorf("Expected size 3 after rebuild, got %d", tree.Size()) + } + + // Old intervals should be gone + count := 0 + tree.QueryPoint(100, func(interval ast.Interval) error { + count++ + return nil + }) + if count != 0 { + t.Error("Old interval should not be found after rebuild") + } + + // New intervals should be present + count = 0 + tree.QueryPoint(350, func(interval ast.Interval) error { + count++ + return nil + }) + if count != 1 { + t.Error("New interval should be found after rebuild") + } +} + +func TestIntervalTree_EternalInterval(t *testing.T) { + tree := NewIntervalTree() + + // Insert an eternal interval (negative to positive infinity) + eternal := ast.EternalInterval() + tree.Insert(eternal) + + // Should contain any timestamp + tests := []int64{-1000000000000, 0, 1000000000000} + for _, ts := range tests { + count := 0 + tree.QueryPoint(ts, func(interval ast.Interval) error { + count++ + return nil + }) + if count != 1 { + t.Errorf("Eternal interval should contain timestamp %d", ts) + } + } +} + +func TestTemporalStore_WithIntervalTree(t *testing.T) { + store := NewTemporalStore() + + atom := ast.Atom{ + Predicate: ast.PredicateSym{Symbol: "test", Arity: 1}, + Args: []ast.BaseTerm{ast.String("alice")}, + } + + interval := makeTestInterval(100, 200) + + // Add fact + added, err := store.Add(atom, interval) + if err != nil { + t.Fatalf("Add failed: %v", err) + } + if !added { + t.Error("Expected Add to return true") + } + + // Check count + if store.EstimateFactCount() != 1 { + t.Errorf("Expected count 1, got %d", store.EstimateFactCount()) + } + + // Query at valid time + query := ast.NewQuery(atom.Predicate) + foundAt := false + store.GetFactsAt(query, time.Unix(0, 150), func(tf TemporalFact) error { + foundAt = true + return nil + }) + if !foundAt { + t.Error("Expected to find fact at valid time") + } + + // Query at invalid time + foundAt = false + store.GetFactsAt(query, time.Unix(0, 50), func(tf TemporalFact) error { + foundAt = true + return nil + }) + if foundAt { + t.Error("Did not expect to find fact at invalid time") + } +} + +func TestTemporalStore_IntervalTreeLimit(t *testing.T) { + store := NewTemporalStore(WithMaxIntervalsPerAtom(3)) + + atom := ast.Atom{ + Predicate: ast.PredicateSym{Symbol: "test", Arity: 1}, + Args: []ast.BaseTerm{ast.String("alice")}, + } + + // Add 3 intervals (should succeed) + for i := int64(0); i < 3; i++ { + _, err := store.Add(atom, makeTestInterval(i*100, i*100+50)) + if err != nil { + t.Fatalf("Add %d failed: %v", i, err) + } + } + + // 4th should fail + _, err := store.Add(atom, makeTestInterval(300, 350)) + if err == nil { + t.Error("Expected error when exceeding interval limit") + } +} + +// makeInterval creates an interval from start and end nanoseconds. +func makeTestInterval(startNano, endNano int64) ast.Interval { + return ast.NewInterval( + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: startNano}, + ast.TemporalBound{Type: ast.TimestampBound, Timestamp: endNano}, + ) +} diff --git a/factstore/temporal.go b/factstore/temporal.go new file mode 100644 index 0000000..49bbfcf --- /dev/null +++ b/factstore/temporal.go @@ -0,0 +1,450 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package factstore + +import ( + "fmt" + "sort" + "time" + + "github.com/google/mangle/ast" +) + +// TemporalFact represents a fact with a validity interval. +type TemporalFact struct { + Atom ast.Atom + Interval ast.Interval +} + +// ReadOnlyTemporalFactStore provides read access to temporal facts. +type ReadOnlyTemporalFactStore interface { + // GetFactsAt returns facts valid at a specific point in time. + GetFactsAt(query ast.Atom, t time.Time, fn func(TemporalFact) error) error + + // GetFactsDuring returns facts that overlap with the given interval. + GetFactsDuring(query ast.Atom, interval ast.Interval, fn func(TemporalFact) error) error + + // GetAllFacts returns all facts (with their intervals) matching the query. + GetAllFacts(query ast.Atom, fn func(TemporalFact) error) error + + // ContainsAt returns true if the atom is valid at the given time. + ContainsAt(atom ast.Atom, t time.Time) bool + + // ListPredicates lists predicates available in this store. + ListPredicates() []ast.PredicateSym + + // EstimateFactCount returns the estimated number of temporal facts. + EstimateFactCount() int +} + +// TemporalFactStore provides access to temporal facts. +type TemporalFactStore interface { + ReadOnlyTemporalFactStore + + // Add adds a temporal fact to the store. + // Returns (true, nil) if the fact was added, (false, nil) if duplicate. + // Returns (false, ErrIntervalLimitExceeded) if the atom has too many intervals. + Add(atom ast.Atom, interval ast.Interval) (bool, error) + + // AddEternal adds a fact valid for all time (eternal/timeless). + AddEternal(atom ast.Atom) (bool, error) + + // Coalesce merges adjacent/overlapping intervals for the same fact. + Coalesce(predicate ast.PredicateSym) error + + // Merge merges contents of given store. + Merge(ReadOnlyTemporalFactStore) error +} + +// DefaultMaxIntervalsPerAtom is the default maximum number of intervals allowed per atom. +// This prevents interval explosion in recursive temporal rules. +const DefaultMaxIntervalsPerAtom = 1000 + +// ErrIntervalLimitExceeded is returned when an atom has too many intervals. +var ErrIntervalLimitExceeded = fmt.Errorf("interval limit exceeded") + +// TemporalStore is an in-memory implementation of TemporalFactStore. +// Facts are indexed by predicate symbol and atom hash, with each atom +// having an interval tree for O(log n + k) query performance. +type TemporalStore struct { + facts map[ast.PredicateSym]map[uint64]*IntervalTree + atoms map[uint64]ast.Atom + count int + maxIntervalsPerAtom int // negative = no limit, 0 = use default +} + +var _ TemporalFactStore = &TemporalStore{} + +// TemporalStoreOption configures a TemporalStore. +type TemporalStoreOption func(*TemporalStore) + +// WithMaxIntervalsPerAtom sets the maximum intervals allowed per atom. +// Negative value disables the limit. +func WithMaxIntervalsPerAtom(limit int) TemporalStoreOption { + return func(s *TemporalStore) { s.maxIntervalsPerAtom = limit } +} + +// NewTemporalStore creates a new TemporalStore. +func NewTemporalStore(opts ...TemporalStoreOption) *TemporalStore { + s := &TemporalStore{ + facts: make(map[ast.PredicateSym]map[uint64]*IntervalTree), + atoms: make(map[uint64]ast.Atom), + maxIntervalsPerAtom: DefaultMaxIntervalsPerAtom, + } + for _, opt := range opts { + opt(s) + } + return s +} + +// Add adds a temporal fact to the store. +// Returns (true, nil) if added, (false, nil) if duplicate, (false, error) if limit exceeded. +func (s *TemporalStore) Add(atom ast.Atom, interval ast.Interval) (bool, error) { + hash := atom.Hash() + + // Store the atom + s.atoms[hash] = atom + + // Get or create the predicate map + predMap, ok := s.facts[atom.Predicate] + if !ok { + predMap = make(map[uint64]*IntervalTree) + s.facts[atom.Predicate] = predMap + } + + // Get or create the interval tree + tree, ok := predMap[hash] + if !ok { + tree = NewIntervalTree() + predMap[hash] = tree + } + + // Check interval limit before inserting (negative limit means no limit) + if s.maxIntervalsPerAtom > 0 && tree.Size() >= s.maxIntervalsPerAtom { + return false, fmt.Errorf("%w: maximum %d intervals per atom", ErrIntervalLimitExceeded, s.maxIntervalsPerAtom) + } + + // Insert returns false if duplicate + if !tree.Insert(interval) { + return false, nil + } + + s.count++ + return true, nil +} + +// AddEternal adds a fact valid for all time. +func (s *TemporalStore) AddEternal(atom ast.Atom) (bool, error) { + return s.Add(atom, ast.EternalInterval()) +} + +// GetFactsAt returns facts valid at a specific point in time. +// Uses interval tree for O(log n + k) query performance. +func (s *TemporalStore) GetFactsAt(query ast.Atom, t time.Time, fn func(TemporalFact) error) error { + predMap, ok := s.facts[query.Predicate] + if !ok { + return nil + } + + timestamp := t.UnixNano() + + for hash, tree := range predMap { + atom := s.atoms[hash] + if !Matches(query.Args, atom.Args) { + continue + } + + err := tree.QueryPoint(timestamp, func(interval ast.Interval) error { + return fn(TemporalFact{Atom: atom, Interval: interval}) + }) + if err != nil { + return err + } + } + return nil +} + +// GetFactsDuring returns facts that overlap with the given interval. +// Uses interval tree for O(log n + k) query performance. +func (s *TemporalStore) GetFactsDuring(query ast.Atom, interval ast.Interval, fn func(TemporalFact) error) error { + predMap, ok := s.facts[query.Predicate] + if !ok { + return nil + } + + start := getStartTime(interval) + end := getEndTime(interval) + + for hash, tree := range predMap { + atom := s.atoms[hash] + if !Matches(query.Args, atom.Args) { + continue + } + + err := tree.QueryRange(start, end, func(factInterval ast.Interval) error { + return fn(TemporalFact{Atom: atom, Interval: factInterval}) + }) + if err != nil { + return err + } + } + return nil +} + +// GetAllFacts returns all facts matching the query with their intervals. +func (s *TemporalStore) GetAllFacts(query ast.Atom, fn func(TemporalFact) error) error { + predMap, ok := s.facts[query.Predicate] + if !ok { + return nil + } + + for hash, tree := range predMap { + atom := s.atoms[hash] + if !Matches(query.Args, atom.Args) { + continue + } + + err := tree.All(func(interval ast.Interval) error { + return fn(TemporalFact{Atom: atom, Interval: interval}) + }) + if err != nil { + return err + } + } + return nil +} + +// ContainsAt returns true if the atom is valid at the given time. +func (s *TemporalStore) ContainsAt(atom ast.Atom, t time.Time) bool { + predMap, ok := s.facts[atom.Predicate] + if !ok { + return false + } + + tree, ok := predMap[atom.Hash()] + if !ok { + return false + } + + found := false + timestamp := t.UnixNano() + tree.QueryPoint(timestamp, func(interval ast.Interval) error { + found = true + return nil + }) + return found +} + +// ListPredicates returns all predicates in the store. +func (s *TemporalStore) ListPredicates() []ast.PredicateSym { + result := make([]ast.PredicateSym, 0, len(s.facts)) + for pred := range s.facts { + result = append(result, pred) + } + return result +} + +// EstimateFactCount returns the number of temporal facts (atom + interval pairs). +func (s *TemporalStore) EstimateFactCount() int { + return s.count +} + +// Coalesce merges adjacent or overlapping intervals for the same fact. +// This helps prevent interval explosion in recursive rules. +func (s *TemporalStore) Coalesce(predicate ast.PredicateSym) error { + predMap, ok := s.facts[predicate] + if !ok { + return nil + } + + for _, tree := range predMap { + if tree.Size() <= 1 { + continue + } + + // Collect all intervals + var intervals []ast.Interval + tree.All(func(interval ast.Interval) error { + intervals = append(intervals, interval) + return nil + }) + + // Coalesce them + coalesced := coalesceIntervals(intervals) + + // Rebuild the tree with coalesced intervals + s.count -= len(intervals) - len(coalesced) + tree.Rebuild(coalesced) + } + return nil +} + +// coalesceIntervals merges overlapping or adjacent intervals. +// Intervals must have concrete timestamps (not variables or unbounded). +func coalesceIntervals(intervals []ast.Interval) []ast.Interval { + if len(intervals) <= 1 { + return intervals + } + + // Separate concrete intervals from those with variables/unbounded + var concrete []ast.Interval + var other []ast.Interval + + for _, i := range intervals { + if i.Start.Type == ast.TimestampBound && i.End.Type == ast.TimestampBound { + concrete = append(concrete, i) + } else { + other = append(other, i) + } + } + + if len(concrete) <= 1 { + return append(concrete, other...) + } + + // Sort by start time + sort.Slice(concrete, func(i, j int) bool { + return concrete[i].Start.Timestamp < concrete[j].Start.Timestamp + }) + + // Merge overlapping/adjacent intervals + result := []ast.Interval{concrete[0]} + for i := 1; i < len(concrete); i++ { + last := &result[len(result)-1] + curr := concrete[i] + + // Check if current overlaps or is adjacent to last + // Adjacent means end of last + 1 nanosecond = start of current + if last.End.Timestamp >= curr.Start.Timestamp-1 { + // Merge: extend the end if needed + if curr.End.Timestamp > last.End.Timestamp { + last.End = curr.End + } + } else { + result = append(result, curr) + } + } + + return append(result, other...) +} + +// Merge merges contents of another temporal store into this one. +// Returns an error if the interval limit is exceeded for any atom. +func (s *TemporalStore) Merge(other ReadOnlyTemporalFactStore) error { + var mergeErr error + for _, pred := range other.ListPredicates() { + query := ast.NewQuery(pred) + other.GetAllFacts(query, func(tf TemporalFact) error { + _, err := s.Add(tf.Atom, tf.Interval) + if err != nil { + mergeErr = err + return err // Stop iteration on error + } + return nil + }) + if mergeErr != nil { + return mergeErr + } + } + return nil +} + +// TemporalFactStoreAdapter wraps a TemporalFactStore to provide +// a standard FactStore interface. Facts are returned without +// temporal information (using the underlying atom only). +// This allows temporal stores to be used where a regular FactStore is expected. +type TemporalFactStoreAdapter struct { + temporal TemporalFactStore + queryAt *time.Time // If set, only return facts valid at this time +} + +// Ensure TemporalFactStoreAdapter implements FactStore. +var _ FactStore = &TemporalFactStoreAdapter{} + +// NewTemporalFactStoreAdapter creates an adapter that exposes all temporal facts +// as regular facts (ignoring time constraints). +func NewTemporalFactStoreAdapter(temporal TemporalFactStore) *TemporalFactStoreAdapter { + return &TemporalFactStoreAdapter{temporal: temporal, queryAt: nil} +} + +// NewTemporalFactStoreAdapterAt creates an adapter that only exposes facts +// valid at the specified time. +func NewTemporalFactStoreAdapterAt(temporal TemporalFactStore, t time.Time) *TemporalFactStoreAdapter { + return &TemporalFactStoreAdapter{temporal: temporal, queryAt: &t} +} + +// Add adds a fact as eternal (valid for all time). +// Note: errors from temporal store are logged but not returned per FactStore interface. +func (a *TemporalFactStoreAdapter) Add(atom ast.Atom) bool { + added, _ := a.temporal.AddEternal(atom) + return added +} + +// Contains returns true if the fact exists (respecting queryAt if set). +// Per the FactStore interface contract, errors are treated as "false". +// Clients who need to distinguish "absent" from "error" should use GetFacts. +func (a *TemporalFactStoreAdapter) Contains(atom ast.Atom) bool { + if a.queryAt != nil { + return a.temporal.ContainsAt(atom, *a.queryAt) + } + // Without a query time, check if the fact exists with any interval + found := false + _ = a.temporal.GetAllFacts(atom, func(tf TemporalFact) error { + if tf.Atom.Equals(atom) { + found = true + } + return nil + }) + return found +} + +// GetFacts returns facts matching the query (respecting queryAt if set). +func (a *TemporalFactStoreAdapter) GetFacts(query ast.Atom, fn func(ast.Atom) error) error { + seen := make(map[uint64]bool) + + callback := func(tf TemporalFact) error { + hash := tf.Atom.Hash() + if seen[hash] { + return nil + } + seen[hash] = true + return fn(tf.Atom) + } + + if a.queryAt != nil { + return a.temporal.GetFactsAt(query, *a.queryAt, callback) + } + return a.temporal.GetAllFacts(query, callback) +} + +// ListPredicates returns all predicates in the store. +func (a *TemporalFactStoreAdapter) ListPredicates() []ast.PredicateSym { + return a.temporal.ListPredicates() +} + +// EstimateFactCount returns an estimate of the number of facts. +func (a *TemporalFactStoreAdapter) EstimateFactCount() int { + return a.temporal.EstimateFactCount() +} + +// Merge merges another store into this one. +func (a *TemporalFactStoreAdapter) Merge(other ReadOnlyFactStore) { + for _, pred := range other.ListPredicates() { + other.GetFacts(ast.NewQuery(pred), func(fact ast.Atom) error { + a.Add(fact) + return nil + }) + } +} diff --git a/factstore/temporal_test.go b/factstore/temporal_test.go new file mode 100644 index 0000000..5fc1bcb --- /dev/null +++ b/factstore/temporal_test.go @@ -0,0 +1,481 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package factstore + +import ( + "errors" + "testing" + "time" + + "github.com/google/mangle/ast" +) + +func name(str string) ast.Constant { + res, _ := ast.Name(str) + return res +} + + +func TestTemporalStore_Add(t *testing.T) { + store := NewTemporalStore() + + atom := ast.NewAtom("employed", name("/alice")) + interval := ast.TimeInterval( + ast.Date(2020, 1, 1), + ast.Date(2023, 12, 31), + ) + + // First add should succeed + added, err := store.Add(atom, interval) + if err != nil { + t.Errorf("First Add returned error: %v", err) + } + if !added { + t.Error("First Add should return true") + } + + // Duplicate add should fail + added, err = store.Add(atom, interval) + if err != nil { + t.Errorf("Duplicate Add returned error: %v", err) + } + if added { + t.Error("Duplicate Add should return false") + } + + // Adding same atom with different interval should succeed + interval2 := ast.TimeInterval( + ast.Date(2024, 1, 1), + ast.Date(2024, 12, 31), + ) + added, err = store.Add(atom, interval2) + if err != nil { + t.Errorf("Add with different interval returned error: %v", err) + } + if !added { + t.Error("Add with different interval should return true") + } + + if store.EstimateFactCount() != 2 { + t.Errorf("EstimateFactCount = %d, want 2", store.EstimateFactCount()) + } +} + +func TestTemporalStore_AddEternal(t *testing.T) { + store := NewTemporalStore() + + atom := ast.NewAtom("admin", name("/bob")) + + added, err := store.AddEternal(atom) + if err != nil { + t.Errorf("AddEternal returned error: %v", err) + } + if !added { + t.Error("AddEternal should return true") + } + + // Query at any time should return the fact + testTimes := []time.Time{ + ast.Date(1900, 1, 1), + ast.Date(2024, 6, 15), + ast.Date(3000, 12, 31), + } + + for _, queryTime := range testTimes { + if !store.ContainsAt(atom, queryTime) { + t.Errorf("ContainsAt(%v) = false, want true for eternal fact", queryTime) + } + } +} + +func TestTemporalStore_IntervalLimit(t *testing.T) { + // Use a small custom limit for faster testing + customLimit := 100 + store := NewTemporalStore(WithMaxIntervalsPerAtom(customLimit)) + atom := ast.NewAtom("test", name("/foo")) + + // Add intervals up to the limit + for i := 0; i < customLimit; i++ { + start := time.Date(2020, 1, 1, i, 0, 0, 0, time.UTC) + end := time.Date(2020, 1, 1, i, 59, 59, 0, time.UTC) + added, err := store.Add(atom, ast.TimeInterval(start, end)) + if err != nil { + t.Fatalf("Add %d returned unexpected error: %v", i, err) + } + if !added { + t.Fatalf("Add %d should return true", i) + } + } + + // Next add should fail with ErrIntervalLimitExceeded + start := time.Date(2020, 1, 1, customLimit, 0, 0, 0, time.UTC) + end := time.Date(2020, 1, 1, customLimit, 59, 59, 0, time.UTC) + added, err := store.Add(atom, ast.TimeInterval(start, end)) + if !errors.Is(err, ErrIntervalLimitExceeded) { + t.Errorf("Add beyond limit: err = %v, want ErrIntervalLimitExceeded", err) + } + if added { + t.Error("Add beyond limit should return false") + } +} + +func TestTemporalStore_NoLimit(t *testing.T) { + // Negative limit means no limit + store := NewTemporalStore(WithMaxIntervalsPerAtom(-1)) + atom := ast.NewAtom("test", name("/foo")) + + // Should be able to add more than default limit + for i := 0; i < DefaultMaxIntervalsPerAtom+10; i++ { + start := time.Date(2020, 1, 1, 0, i, 0, 0, time.UTC) + end := time.Date(2020, 1, 1, 0, i, 59, 0, time.UTC) + _, err := store.Add(atom, ast.TimeInterval(start, end)) + if err != nil { + t.Fatalf("Add %d returned unexpected error with no limit: %v", i, err) + } + } +} + +func TestTemporalStore_GetFactsAt(t *testing.T) { + store := NewTemporalStore() + + // Alice employed from 2020-2023 + aliceEmployed := ast.NewAtom("employed", name("/alice")) + store.Add(aliceEmployed, ast.TimeInterval( + ast.Date(2020, 1, 1), + ast.Date(2023, 12, 31), + )) + + // Bob employed from 2022 onwards + bobEmployed := ast.NewAtom("employed", name("/bob")) + store.Add(bobEmployed, ast.TimeInterval( + ast.Date(2022, 1, 1), + ast.Date(2099, 12, 31), + )) + + tests := []struct { + name string + queryTime time.Time + wantCount int + }{ + { + name: "before both", + queryTime: ast.Date(2019, 6, 15), + wantCount: 0, + }, + { + name: "alice only", + queryTime: ast.Date(2021, 6, 15), + wantCount: 1, + }, + { + name: "both employed", + queryTime: ast.Date(2022, 6, 15), + wantCount: 2, + }, + { + name: "bob only (after alice left)", + queryTime: ast.Date(2024, 6, 15), + wantCount: 1, + }, + } + + query := ast.NewQuery(ast.PredicateSym{Symbol: "employed", Arity: 1}) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + count := 0 + err := store.GetFactsAt(query, tt.queryTime, func(tf TemporalFact) error { + count++ + return nil + }) + if err != nil { + t.Fatalf("GetFactsAt error: %v", err) + } + if count != tt.wantCount { + t.Errorf("GetFactsAt count = %d, want %d", count, tt.wantCount) + } + }) + } +} + +func TestTemporalStore_GetFactsDuring(t *testing.T) { + store := NewTemporalStore() + + // Event from Jan 1-15 + event1 := ast.NewAtom("event", name("/conference")) + store.Add(event1, ast.TimeInterval( + ast.Date(2024, 1, 1), + ast.Date(2024, 1, 15), + )) + + // Event from Jan 20-30 + event2 := ast.NewAtom("event", name("/workshop")) + store.Add(event2, ast.TimeInterval( + ast.Date(2024, 1, 20), + ast.Date(2024, 1, 30), + )) + + tests := []struct { + name string + interval ast.Interval + wantCount int + }{ + { + name: "before all events", + interval: ast.TimeInterval( + ast.Date(2023, 12, 1), + ast.Date(2023, 12, 31), + ), + wantCount: 0, + }, + { + name: "overlaps first event", + interval: ast.TimeInterval( + ast.Date(2024, 1, 10), + ast.Date(2024, 1, 18), + ), + wantCount: 1, + }, + { + name: "overlaps both events", + interval: ast.TimeInterval( + ast.Date(2024, 1, 10), + ast.Date(2024, 1, 25), + ), + wantCount: 2, + }, + { + name: "between events (no overlap)", + interval: ast.TimeInterval( + ast.Date(2024, 1, 16), + ast.Date(2024, 1, 19), + ), + wantCount: 0, + }, + } + + query := ast.NewQuery(ast.PredicateSym{Symbol: "event", Arity: 1}) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + count := 0 + err := store.GetFactsDuring(query, tt.interval, func(tf TemporalFact) error { + count++ + return nil + }) + if err != nil { + t.Fatalf("GetFactsDuring error: %v", err) + } + if count != tt.wantCount { + t.Errorf("GetFactsDuring count = %d, want %d", count, tt.wantCount) + } + }) + } +} + +func TestTemporalStore_Coalesce(t *testing.T) { + store := NewTemporalStore() + + atom := ast.NewAtom("active", name("/service")) + + // Add overlapping intervals + store.Add(atom, ast.TimeInterval( + ast.Date(2024, 1, 1), + ast.Date(2024, 1, 15), + )) + store.Add(atom, ast.TimeInterval( + ast.Date(2024, 1, 10), + ast.Date(2024, 1, 25), + )) + store.Add(atom, ast.TimeInterval( + ast.Date(2024, 1, 20), + ast.Date(2024, 1, 31), + )) + + // Before coalescing: 3 intervals + if store.EstimateFactCount() != 3 { + t.Errorf("Before coalesce: count = %d, want 3", store.EstimateFactCount()) + } + + // Coalesce + pred := ast.PredicateSym{Symbol: "active", Arity: 1} + if err := store.Coalesce(pred); err != nil { + t.Fatalf("Coalesce error: %v", err) + } + + // After coalescing: should be 1 interval covering Jan 1-31 + if store.EstimateFactCount() != 1 { + t.Errorf("After coalesce: count = %d, want 1", store.EstimateFactCount()) + } + + // Verify the merged interval covers the full range + query := ast.NewQuery(pred) + var resultInterval ast.Interval + store.GetAllFacts(query, func(tf TemporalFact) error { + resultInterval = tf.Interval + return nil + }) + + expectedStart := ast.Date(2024, 1, 1) + expectedEnd := ast.Date(2024, 1, 31) + + // Compare in UTC to avoid timezone issues + if !resultInterval.Start.Time().UTC().Equal(expectedStart) { + t.Errorf("Coalesced start = %v, want %v", resultInterval.Start.Time().UTC(), expectedStart) + } + if !resultInterval.End.Time().UTC().Equal(expectedEnd) { + t.Errorf("Coalesced end = %v, want %v", resultInterval.End.Time().UTC(), expectedEnd) + } +} + +func TestTemporalStore_CoalesceAdjacent(t *testing.T) { + store := NewTemporalStore() + + atom := ast.NewAtom("shift", name("/worker")) + + // Add adjacent intervals (end of one = start of next - 1 nanosecond) + store.Add(atom, ast.TimeInterval( + time.Date(2024, 1, 1, 8, 0, 0, 0, time.UTC), + time.Date(2024, 1, 1, 16, 0, 0, 0, time.UTC), + )) + store.Add(atom, ast.TimeInterval( + time.Date(2024, 1, 1, 16, 0, 0, 1, time.UTC), // 1 nanosecond after + ast.Date(2024, 1, 2), + )) + + pred := ast.PredicateSym{Symbol: "shift", Arity: 1} + if err := store.Coalesce(pred); err != nil { + t.Fatalf("Coalesce error: %v", err) + } + + // Should be coalesced into 1 interval + if store.EstimateFactCount() != 1 { + t.Errorf("After coalesce: count = %d, want 1", store.EstimateFactCount()) + } +} + +func TestTemporalStore_CoalesceNonOverlapping(t *testing.T) { + store := NewTemporalStore() + + atom := ast.NewAtom("vacation", name("/alice")) + + // Add non-overlapping intervals + store.Add(atom, ast.TimeInterval( + ast.Date(2024, 1, 1), + ast.Date(2024, 1, 7), + )) + store.Add(atom, ast.TimeInterval( + ast.Date(2024, 6, 1), + ast.Date(2024, 6, 14), + )) + + pred := ast.PredicateSym{Symbol: "vacation", Arity: 1} + if err := store.Coalesce(pred); err != nil { + t.Fatalf("Coalesce error: %v", err) + } + + // Should remain as 2 separate intervals + if store.EstimateFactCount() != 2 { + t.Errorf("After coalesce: count = %d, want 2", store.EstimateFactCount()) + } +} + +func TestTemporalFactStoreAdapter(t *testing.T) { + temporal := NewTemporalStore() + + // Add some temporal facts + alice := ast.NewAtom("employed", name("/alice")) + bob := ast.NewAtom("employed", name("/bob")) + + temporal.Add(alice, ast.TimeInterval( + ast.Date(2020, 1, 1), + ast.Date(2023, 12, 31), + )) + temporal.Add(bob, ast.TimeInterval( + ast.Date(2022, 1, 1), + ast.Date(2099, 12, 31), + )) + + // Test adapter without time constraint + adapter := NewTemporalFactStoreAdapter(temporal) + + query := ast.NewQuery(ast.PredicateSym{Symbol: "employed", Arity: 1}) + count := 0 + adapter.GetFacts(query, func(a ast.Atom) error { + count++ + return nil + }) + + if count != 2 { + t.Errorf("Adapter without time: count = %d, want 2", count) + } + + // Test adapter with time constraint + queryTime := ast.Date(2021, 6, 15) + adapterAt := NewTemporalFactStoreAdapterAt(temporal, queryTime) + + count = 0 + adapterAt.GetFacts(query, func(a ast.Atom) error { + count++ + return nil + }) + + if count != 1 { + t.Errorf("Adapter at 2021-06-15: count = %d, want 1 (only alice)", count) + } +} + +func TestTemporalFactStoreAdapter_Add(t *testing.T) { + temporal := NewTemporalStore() + adapter := NewTemporalFactStoreAdapter(temporal) + + atom := ast.NewAtom("admin", name("/charlie")) + + // Add through adapter should create eternal fact + if !adapter.Add(atom) { + t.Error("Adapter.Add should return true") + } + + // Should be queryable at any time + testTime := ast.Date(2024, 6, 15) + if !temporal.ContainsAt(atom, testTime) { + t.Error("Fact added through adapter should be eternal") + } +} + +func TestTemporalStore_ListPredicates(t *testing.T) { + store := NewTemporalStore() + + store.Add(ast.NewAtom("foo", name("/a")), ast.EternalInterval()) + store.Add(ast.NewAtom("bar", name("/b")), ast.EternalInterval()) + store.Add(ast.NewAtom("baz", name("/c")), ast.EternalInterval()) + + preds := store.ListPredicates() + if len(preds) != 3 { + t.Errorf("ListPredicates returned %d predicates, want 3", len(preds)) + } + + // Check that all predicates are present + found := make(map[string]bool) + for _, p := range preds { + found[p.Symbol] = true + } + + for _, expected := range []string{"foo", "bar", "baz"} { + if !found[expected] { + t.Errorf("ListPredicates missing %s", expected) + } + } +} diff --git a/functional/functional.go b/functional/functional.go index 5cec4d8..d5b18c7 100644 --- a/functional/functional.go +++ b/functional/functional.go @@ -484,7 +484,7 @@ func EvalApplyFn(applyFn ast.ApplyFn, subst ast.Subst) (ast.Constant, error) { } return ast.Constant{}, fmt.Errorf("key does not exist: %v", lookupField) - // Time functions +// Time functions case symbols.TimeNow.Symbol: return ast.Time(time.Now().UnixNano()), nil @@ -855,6 +855,61 @@ func EvalApplyFn(applyFn ast.ApplyFn, subst ast.Subst) (ast.Constant, error) { } return ast.Duration(int64(d)), nil + // Interval functions - work with intervals represented as pairs of time values + case symbols.IntervalStart.Symbol: + if l := len(evaluatedArgs); l != 1 { + return ast.Constant{}, fmt.Errorf("expected 1 interval argument, got %d argument(s)", l) + } + interval := evaluatedArgs[0] + if interval.Type != ast.PairShape { + return ast.Constant{}, fmt.Errorf("expected pair (interval), got %v", interval.Type) + } + start, _, err := interval.PairValue() + if err != nil { + return ast.Constant{}, fmt.Errorf("invalid interval: %w", err) + } + return start, nil + + case symbols.IntervalEnd.Symbol: + if l := len(evaluatedArgs); l != 1 { + return ast.Constant{}, fmt.Errorf("expected 1 interval argument, got %d argument(s)", l) + } + interval := evaluatedArgs[0] + if interval.Type != ast.PairShape { + return ast.Constant{}, fmt.Errorf("expected pair (interval), got %v", interval.Type) + } + _, end, err := interval.PairValue() + if err != nil { + return ast.Constant{}, fmt.Errorf("invalid interval: %w", err) + } + return end, nil + + case symbols.IntervalDuration.Symbol: + if l := len(evaluatedArgs); l != 1 { + return ast.Constant{}, fmt.Errorf("expected 1 interval argument, got %d argument(s)", l) + } + interval := evaluatedArgs[0] + if interval.Type != ast.PairShape { + return ast.Constant{}, fmt.Errorf("expected pair (interval), got %v", interval.Type) + } + start, end, err := interval.PairValue() + if err != nil { + return ast.Constant{}, fmt.Errorf("invalid interval: %w", err) + } + startNano, err := start.TimeValue() + if err != nil { + return ast.Constant{}, fmt.Errorf("invalid interval start: %w", err) + } + endNano, err := end.TimeValue() + if err != nil { + return ast.Constant{}, fmt.Errorf("invalid interval end: %w", err) + } + // Handle unbounded intervals (represented by special values) + if startNano == math.MinInt64 || endNano == math.MaxInt64 { + return ast.Constant{}, fmt.Errorf("cannot compute duration of unbounded interval") + } + return ast.Duration(endNano - startNano), nil + default: return EvalNumericApplyFn(applyFn, subst) } diff --git a/functional/temporal_test.go b/functional/temporal_test.go new file mode 100644 index 0000000..196c285 --- /dev/null +++ b/functional/temporal_test.go @@ -0,0 +1,211 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package functional + +import ( + "testing" + + "github.com/google/mangle/ast" + "github.com/google/mangle/symbols" +) + +func TestIntervalStart(t *testing.T) { + tests := []struct { + name string + start int64 + end int64 + want int64 + wantErr bool + }{ + { + name: "simple interval", + start: 1000, + end: 2000, + want: 1000, + }, + { + name: "point interval", + start: 1500, + end: 1500, + want: 1500, + }, + { + name: "negative start", + start: -1000, + end: 1000, + want: -1000, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + startConst := ast.Time(tc.start) + endConst := ast.Time(tc.end) + interval := ast.Pair(&startConst, &endConst) + + result, err := EvalApplyFn(ast.ApplyFn{ + Function: symbols.IntervalStart, + Args: []ast.BaseTerm{interval}, + }, nil) + + if (err != nil) != tc.wantErr { + t.Fatalf("EvalApplyFn() error = %v, wantErr %v", err, tc.wantErr) + } + if err != nil { + return + } + + got, err := result.TimeValue() + if err != nil { + t.Fatalf("result.TimeValue() error = %v", err) + } + if got != tc.want { + t.Errorf("fn:interval:start got %d, want %d", got, tc.want) + } + }) + } +} + +func TestIntervalEnd(t *testing.T) { + tests := []struct { + name string + start int64 + end int64 + want int64 + wantErr bool + }{ + { + name: "simple interval", + start: 1000, + end: 2000, + want: 2000, + }, + { + name: "point interval", + start: 1500, + end: 1500, + want: 1500, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + startConst := ast.Time(tc.start) + endConst := ast.Time(tc.end) + interval := ast.Pair(&startConst, &endConst) + + result, err := EvalApplyFn(ast.ApplyFn{ + Function: symbols.IntervalEnd, + Args: []ast.BaseTerm{interval}, + }, nil) + + if (err != nil) != tc.wantErr { + t.Fatalf("EvalApplyFn() error = %v, wantErr %v", err, tc.wantErr) + } + if err != nil { + return + } + + got, err := result.TimeValue() + if err != nil { + t.Fatalf("result.TimeValue() error = %v", err) + } + if got != tc.want { + t.Errorf("fn:interval:end got %d, want %d", got, tc.want) + } + }) + } +} + +func TestIntervalDuration(t *testing.T) { + tests := []struct { + name string + start int64 + end int64 + want int64 + wantErr bool + }{ + { + name: "simple interval", + start: 1000, + end: 2000, + want: 1000, + }, + { + name: "point interval", + start: 1500, + end: 1500, + want: 0, + }, + { + name: "large duration", + start: 0, + end: 1000000000, // 1 second in nanoseconds + want: 1000000000, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + startConst := ast.Time(tc.start) + endConst := ast.Time(tc.end) + interval := ast.Pair(&startConst, &endConst) + + result, err := EvalApplyFn(ast.ApplyFn{ + Function: symbols.IntervalDuration, + Args: []ast.BaseTerm{interval}, + }, nil) + + if (err != nil) != tc.wantErr { + t.Fatalf("EvalApplyFn() error = %v, wantErr %v", err, tc.wantErr) + } + if err != nil { + return + } + + got, err := result.DurationValue() + if err != nil { + t.Fatalf("result.DurationValue() error = %v", err) + } + if got != tc.want { + t.Errorf("fn:interval:duration got %d, want %d", got, tc.want) + } + }) + } +} + +func TestIntervalFunctionsErrors(t *testing.T) { + // Test wrong argument count + t.Run("wrong arg count for start", func(t *testing.T) { + _, err := EvalApplyFn(ast.ApplyFn{ + Function: symbols.IntervalStart, + Args: []ast.BaseTerm{ast.Time(1), ast.Time(2)}, + }, nil) + if err == nil { + t.Error("expected error for wrong argument count") + } + }) + + // Test wrong type + t.Run("wrong type for interval", func(t *testing.T) { + _, err := EvalApplyFn(ast.ApplyFn{ + Function: symbols.IntervalStart, + Args: []ast.BaseTerm{ast.Time(1)}, + }, nil) + if err == nil { + t.Error("expected error for non-pair argument") + } + }) +} diff --git a/go.sum b/go.sum index 5997658..4477c20 100644 --- a/go.sum +++ b/go.sum @@ -2,21 +2,14 @@ bitbucket.org/creachadair/stringset v0.0.11 h1:6Sv4CCv14Wm+OipW4f3tWOb0SQVpBDLW0 bitbucket.org/creachadair/stringset v0.0.11/go.mod h1:wh0BHewFe+j0HrzWz7KcGbSNpFzWwnpmgPRlB57U5jU= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= -github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= -github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= @@ -27,5 +20,3 @@ golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4= google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/parse/gen/Mangle.g4 b/parse/gen/Mangle.g4 index 2387930..0e022fb 100644 --- a/parse/gen/Mangle.g4 +++ b/parse/gen/Mangle.g4 @@ -33,7 +33,7 @@ useDecl ; decl - : 'Decl' atom descrBlock? boundsBlock* constraintsBlock? '.'; + : 'Decl' atom 'temporal'? descrBlock? boundsBlock* constraintsBlock? '.'; descrBlock : 'descr' atoms @@ -48,7 +48,20 @@ constraintsBlock ; clause - : atom ((':-'|LONGLEFTDOUBLEARROW) clauseBody)? '.' + : atom temporalAnnotation? ((':-'|LONGLEFTDOUBLEARROW) clauseBody)? '.' + ; + +// Temporal annotation for facts: @[start, end] or @[point] +temporalAnnotation + : '@' '[' temporalBound (',' temporalBound)? ']' + ; + +// A temporal bound can be a timestamp, duration, variable (including _ for unbounded), or 'now' +temporalBound + : TIMESTAMP // 2024-01-15 or 2024-01-15T10:30:00 + | DURATION // 7d, 24h, 30m, 1s + | VARIABLE // Bind to variable, or _ for unbounded + | 'now' // Current evaluation time ; clauseBody @@ -65,10 +78,18 @@ letStmt ; literalOrFml - : term ((EQ | BANGEQ | LESS | LESSEQ | GREATER | GREATEREQ) term)? + : temporalOperator? term temporalAnnotation? ((EQ | BANGEQ | LESS | LESSEQ | GREATER | GREATEREQ) term)? | '!'term ; +// Temporal operators for querying the past/future +temporalOperator + : DIAMONDMINUS '[' temporalBound ',' temporalBound ']' // <-[0d, 7d] - sometime in past + | BOXMINUS '[' temporalBound ',' temporalBound ']' // [-[0d, 7d] - always in past + | DIAMONDPLUS '[' temporalBound ',' temporalBound ']' // <+[0d, 7d] - sometime in future + | BOXPLUS '[' temporalBound ',' temporalBound ']' // [+[0d, 7d] - always in future + ; + term : VARIABLE # Var | CONSTANT # Const @@ -114,21 +135,36 @@ LPAREN : '('; RPAREN : ')'; LBRACKET : '['; RBRACKET : ']'; +LBRACE : '{'; +RBRACE : '}'; EQ : '='; BANGEQ : '!='; COMMA : ','; BANG : '!'; +LESSEQ : '<='; // Must come before LESS LESS : '<'; -LESSEQ : '<='; +GREATEREQ : '>='; // Must come before GREATER GREATER : '>'; -GREATEREQ : '>='; COLONDASH : ':-'; NEWLINE : '\n'; PIPEGREATER : '|>'; +AT : '@'; +DIAMONDMINUS : '<-'; // Must come before LESS +DIAMONDPLUS : '<+'; // Must come before LESS +BOXMINUS : '[-'; // Must come before LBRACKET +BOXPLUS : '[+'; // Must come before LBRACKET fragment LETTER : 'A'..'Z' | 'a'..'z' ; fragment DIGIT : '0'..'9' ; +// Temporal tokens - must come before NUMBER to take precedence +// Timestamp: 2024-01-15 or 2024-01-15T10:30:00 or 2024-01-15T10:30:00Z +TIMESTAMP : DIGIT DIGIT DIGIT DIGIT '-' DIGIT DIGIT '-' DIGIT DIGIT + ('T' DIGIT DIGIT ':' DIGIT DIGIT ':' DIGIT DIGIT ('.' DIGIT+)? 'Z'?)? ; + +// Duration: 7d, 24h, 30m, 1s, 500ms +DURATION : DIGIT+ ('d' | 'h' | 'm' | 's' | 'ms') ; + NUMBER : '-'? DIGIT (DIGIT)*; FLOAT : '-'? (DIGIT)+ '.' (DIGIT)+ EXPONENT? | '-'? '.' (DIGIT)+ EXPONENT?; @@ -192,5 +228,3 @@ fragment STRING_ESCAPE_SEQ fragment HEXDIGIT : 'a'..'f' | '0'..'9'; - - diff --git a/parse/gen/Mangle.interp b/parse/gen/Mangle.interp index 5c96609..016d2d5 100644 --- a/parse/gen/Mangle.interp +++ b/parse/gen/Mangle.interp @@ -1,15 +1,15 @@ token literal names: null +'temporal' '.' 'descr' 'inclusion' +'now' ':' -'{' -'}' 'opt' null null -'\u27F8' +'⟸' 'Package' 'Use' 'Decl' @@ -20,17 +20,26 @@ null ')' '[' ']' +'{' +'}' '=' '!=' ',' '!' -'<' '<=' -'>' +'<' '>=' +'>' ':-' '\n' '|>' +'@' +'<-' +'<+' +'[-' +'[+' +null +null null null null @@ -63,17 +72,26 @@ LPAREN RPAREN LBRACKET RBRACKET +LBRACE +RBRACE EQ BANGEQ COMMA BANG -LESS LESSEQ -GREATER +LESS GREATEREQ +GREATER COLONDASH NEWLINE PIPEGREATER +AT +DIAMONDMINUS +DIAMONDPLUS +BOXMINUS +BOXPLUS +TIMESTAMP +DURATION NUMBER FLOAT VARIABLE @@ -94,10 +112,13 @@ descrBlock boundsBlock constraintsBlock clause +temporalAnnotation +temporalBound clauseBody transform letStmt literalOrFml +temporalOperator term member atom @@ -105,4 +126,4 @@ atoms atn: -[4, 1, 40, 283, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 1, 0, 1, 0, 1, 0, 1, 1, 3, 1, 39, 8, 1, 1, 1, 5, 1, 42, 8, 1, 10, 1, 12, 1, 45, 9, 1, 1, 1, 1, 1, 5, 1, 49, 8, 1, 10, 1, 12, 1, 52, 9, 1, 1, 2, 1, 2, 1, 2, 3, 2, 57, 8, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 3, 3, 64, 8, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 3, 4, 71, 8, 4, 1, 4, 5, 4, 74, 8, 4, 10, 4, 12, 4, 77, 9, 4, 1, 4, 3, 4, 80, 8, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 5, 6, 92, 8, 6, 10, 6, 12, 6, 95, 9, 6, 1, 6, 3, 6, 98, 8, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 3, 8, 108, 8, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 5, 9, 115, 8, 9, 10, 9, 12, 9, 118, 9, 9, 1, 9, 3, 9, 121, 8, 9, 1, 9, 1, 9, 5, 9, 125, 8, 9, 10, 9, 12, 9, 128, 9, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 5, 10, 136, 8, 10, 10, 10, 12, 10, 139, 9, 10, 3, 10, 141, 8, 10, 1, 10, 1, 10, 1, 10, 5, 10, 146, 8, 10, 10, 10, 12, 10, 149, 9, 10, 3, 10, 151, 8, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 3, 12, 161, 8, 12, 1, 12, 1, 12, 3, 12, 165, 8, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 5, 13, 177, 8, 13, 10, 13, 12, 13, 180, 9, 13, 1, 13, 3, 13, 183, 8, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 5, 13, 192, 8, 13, 10, 13, 12, 13, 195, 9, 13, 1, 13, 1, 13, 1, 13, 1, 13, 3, 13, 201, 8, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 5, 13, 210, 8, 13, 10, 13, 12, 13, 213, 9, 13, 1, 13, 1, 13, 1, 13, 1, 13, 3, 13, 219, 8, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 5, 13, 227, 8, 13, 10, 13, 12, 13, 230, 9, 13, 1, 13, 1, 13, 3, 13, 234, 8, 13, 3, 13, 236, 8, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 5, 13, 244, 8, 13, 10, 13, 12, 13, 247, 9, 13, 1, 13, 3, 13, 250, 8, 13, 1, 13, 3, 13, 253, 8, 13, 1, 14, 1, 14, 1, 14, 3, 14, 258, 8, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 265, 8, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 5, 16, 273, 8, 16, 10, 16, 12, 16, 276, 9, 16, 1, 16, 3, 16, 279, 8, 16, 1, 16, 1, 16, 1, 16, 0, 0, 17, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 0, 2, 2, 0, 10, 10, 29, 29, 2, 0, 21, 22, 25, 28, 311, 0, 34, 1, 0, 0, 0, 2, 38, 1, 0, 0, 0, 4, 53, 1, 0, 0, 0, 6, 60, 1, 0, 0, 0, 8, 67, 1, 0, 0, 0, 10, 83, 1, 0, 0, 0, 12, 86, 1, 0, 0, 0, 14, 101, 1, 0, 0, 0, 16, 104, 1, 0, 0, 0, 18, 111, 1, 0, 0, 0, 20, 150, 1, 0, 0, 0, 22, 152, 1, 0, 0, 0, 24, 164, 1, 0, 0, 0, 26, 252, 1, 0, 0, 0, 28, 264, 1, 0, 0, 0, 30, 266, 1, 0, 0, 0, 32, 268, 1, 0, 0, 0, 34, 35, 3, 2, 1, 0, 35, 36, 5, 0, 0, 1, 36, 1, 1, 0, 0, 0, 37, 39, 3, 4, 2, 0, 38, 37, 1, 0, 0, 0, 38, 39, 1, 0, 0, 0, 39, 43, 1, 0, 0, 0, 40, 42, 3, 6, 3, 0, 41, 40, 1, 0, 0, 0, 42, 45, 1, 0, 0, 0, 43, 41, 1, 0, 0, 0, 43, 44, 1, 0, 0, 0, 44, 50, 1, 0, 0, 0, 45, 43, 1, 0, 0, 0, 46, 49, 3, 8, 4, 0, 47, 49, 3, 16, 8, 0, 48, 46, 1, 0, 0, 0, 48, 47, 1, 0, 0, 0, 49, 52, 1, 0, 0, 0, 50, 48, 1, 0, 0, 0, 50, 51, 1, 0, 0, 0, 51, 3, 1, 0, 0, 0, 52, 50, 1, 0, 0, 0, 53, 54, 5, 11, 0, 0, 54, 56, 5, 35, 0, 0, 55, 57, 3, 32, 16, 0, 56, 55, 1, 0, 0, 0, 56, 57, 1, 0, 0, 0, 57, 58, 1, 0, 0, 0, 58, 59, 5, 24, 0, 0, 59, 5, 1, 0, 0, 0, 60, 61, 5, 12, 0, 0, 61, 63, 5, 35, 0, 0, 62, 64, 3, 32, 16, 0, 63, 62, 1, 0, 0, 0, 63, 64, 1, 0, 0, 0, 64, 65, 1, 0, 0, 0, 65, 66, 5, 24, 0, 0, 66, 7, 1, 0, 0, 0, 67, 68, 5, 13, 0, 0, 68, 70, 3, 30, 15, 0, 69, 71, 3, 10, 5, 0, 70, 69, 1, 0, 0, 0, 70, 71, 1, 0, 0, 0, 71, 75, 1, 0, 0, 0, 72, 74, 3, 12, 6, 0, 73, 72, 1, 0, 0, 0, 74, 77, 1, 0, 0, 0, 75, 73, 1, 0, 0, 0, 75, 76, 1, 0, 0, 0, 76, 79, 1, 0, 0, 0, 77, 75, 1, 0, 0, 0, 78, 80, 3, 14, 7, 0, 79, 78, 1, 0, 0, 0, 79, 80, 1, 0, 0, 0, 80, 81, 1, 0, 0, 0, 81, 82, 5, 1, 0, 0, 82, 9, 1, 0, 0, 0, 83, 84, 5, 2, 0, 0, 84, 85, 3, 32, 16, 0, 85, 11, 1, 0, 0, 0, 86, 87, 5, 14, 0, 0, 87, 93, 5, 19, 0, 0, 88, 89, 3, 26, 13, 0, 89, 90, 5, 23, 0, 0, 90, 92, 1, 0, 0, 0, 91, 88, 1, 0, 0, 0, 92, 95, 1, 0, 0, 0, 93, 91, 1, 0, 0, 0, 93, 94, 1, 0, 0, 0, 94, 97, 1, 0, 0, 0, 95, 93, 1, 0, 0, 0, 96, 98, 3, 26, 13, 0, 97, 96, 1, 0, 0, 0, 97, 98, 1, 0, 0, 0, 98, 99, 1, 0, 0, 0, 99, 100, 5, 20, 0, 0, 100, 13, 1, 0, 0, 0, 101, 102, 5, 3, 0, 0, 102, 103, 3, 32, 16, 0, 103, 15, 1, 0, 0, 0, 104, 107, 3, 30, 15, 0, 105, 106, 7, 0, 0, 0, 106, 108, 3, 18, 9, 0, 107, 105, 1, 0, 0, 0, 107, 108, 1, 0, 0, 0, 108, 109, 1, 0, 0, 0, 109, 110, 5, 1, 0, 0, 110, 17, 1, 0, 0, 0, 111, 116, 3, 24, 12, 0, 112, 113, 5, 23, 0, 0, 113, 115, 3, 24, 12, 0, 114, 112, 1, 0, 0, 0, 115, 118, 1, 0, 0, 0, 116, 114, 1, 0, 0, 0, 116, 117, 1, 0, 0, 0, 117, 120, 1, 0, 0, 0, 118, 116, 1, 0, 0, 0, 119, 121, 5, 23, 0, 0, 120, 119, 1, 0, 0, 0, 120, 121, 1, 0, 0, 0, 121, 126, 1, 0, 0, 0, 122, 123, 5, 31, 0, 0, 123, 125, 3, 20, 10, 0, 124, 122, 1, 0, 0, 0, 125, 128, 1, 0, 0, 0, 126, 124, 1, 0, 0, 0, 126, 127, 1, 0, 0, 0, 127, 19, 1, 0, 0, 0, 128, 126, 1, 0, 0, 0, 129, 130, 5, 16, 0, 0, 130, 140, 3, 26, 13, 0, 131, 132, 5, 23, 0, 0, 132, 137, 3, 22, 11, 0, 133, 134, 5, 23, 0, 0, 134, 136, 3, 22, 11, 0, 135, 133, 1, 0, 0, 0, 136, 139, 1, 0, 0, 0, 137, 135, 1, 0, 0, 0, 137, 138, 1, 0, 0, 0, 138, 141, 1, 0, 0, 0, 139, 137, 1, 0, 0, 0, 140, 131, 1, 0, 0, 0, 140, 141, 1, 0, 0, 0, 141, 151, 1, 0, 0, 0, 142, 147, 3, 22, 11, 0, 143, 144, 5, 23, 0, 0, 144, 146, 3, 22, 11, 0, 145, 143, 1, 0, 0, 0, 146, 149, 1, 0, 0, 0, 147, 145, 1, 0, 0, 0, 147, 148, 1, 0, 0, 0, 148, 151, 1, 0, 0, 0, 149, 147, 1, 0, 0, 0, 150, 129, 1, 0, 0, 0, 150, 142, 1, 0, 0, 0, 151, 21, 1, 0, 0, 0, 152, 153, 5, 15, 0, 0, 153, 154, 5, 34, 0, 0, 154, 155, 5, 21, 0, 0, 155, 156, 3, 26, 13, 0, 156, 23, 1, 0, 0, 0, 157, 160, 3, 26, 13, 0, 158, 159, 7, 1, 0, 0, 159, 161, 3, 26, 13, 0, 160, 158, 1, 0, 0, 0, 160, 161, 1, 0, 0, 0, 161, 165, 1, 0, 0, 0, 162, 163, 5, 24, 0, 0, 163, 165, 3, 26, 13, 0, 164, 157, 1, 0, 0, 0, 164, 162, 1, 0, 0, 0, 165, 25, 1, 0, 0, 0, 166, 253, 5, 34, 0, 0, 167, 253, 5, 38, 0, 0, 168, 253, 5, 32, 0, 0, 169, 253, 5, 33, 0, 0, 170, 253, 5, 39, 0, 0, 171, 253, 5, 40, 0, 0, 172, 178, 5, 19, 0, 0, 173, 174, 3, 26, 13, 0, 174, 175, 5, 23, 0, 0, 175, 177, 1, 0, 0, 0, 176, 173, 1, 0, 0, 0, 177, 180, 1, 0, 0, 0, 178, 176, 1, 0, 0, 0, 178, 179, 1, 0, 0, 0, 179, 182, 1, 0, 0, 0, 180, 178, 1, 0, 0, 0, 181, 183, 3, 26, 13, 0, 182, 181, 1, 0, 0, 0, 182, 183, 1, 0, 0, 0, 183, 184, 1, 0, 0, 0, 184, 253, 5, 20, 0, 0, 185, 193, 5, 19, 0, 0, 186, 187, 3, 26, 13, 0, 187, 188, 5, 4, 0, 0, 188, 189, 3, 26, 13, 0, 189, 190, 5, 23, 0, 0, 190, 192, 1, 0, 0, 0, 191, 186, 1, 0, 0, 0, 192, 195, 1, 0, 0, 0, 193, 191, 1, 0, 0, 0, 193, 194, 1, 0, 0, 0, 194, 200, 1, 0, 0, 0, 195, 193, 1, 0, 0, 0, 196, 197, 3, 26, 13, 0, 197, 198, 5, 4, 0, 0, 198, 199, 3, 26, 13, 0, 199, 201, 1, 0, 0, 0, 200, 196, 1, 0, 0, 0, 200, 201, 1, 0, 0, 0, 201, 202, 1, 0, 0, 0, 202, 253, 5, 20, 0, 0, 203, 211, 5, 5, 0, 0, 204, 205, 3, 26, 13, 0, 205, 206, 5, 4, 0, 0, 206, 207, 3, 26, 13, 0, 207, 208, 5, 23, 0, 0, 208, 210, 1, 0, 0, 0, 209, 204, 1, 0, 0, 0, 210, 213, 1, 0, 0, 0, 211, 209, 1, 0, 0, 0, 211, 212, 1, 0, 0, 0, 212, 218, 1, 0, 0, 0, 213, 211, 1, 0, 0, 0, 214, 215, 3, 26, 13, 0, 215, 216, 5, 4, 0, 0, 216, 217, 3, 26, 13, 0, 217, 219, 1, 0, 0, 0, 218, 214, 1, 0, 0, 0, 218, 219, 1, 0, 0, 0, 219, 220, 1, 0, 0, 0, 220, 253, 5, 6, 0, 0, 221, 222, 5, 37, 0, 0, 222, 228, 5, 25, 0, 0, 223, 224, 3, 28, 14, 0, 224, 225, 5, 23, 0, 0, 225, 227, 1, 0, 0, 0, 226, 223, 1, 0, 0, 0, 227, 230, 1, 0, 0, 0, 228, 226, 1, 0, 0, 0, 228, 229, 1, 0, 0, 0, 229, 235, 1, 0, 0, 0, 230, 228, 1, 0, 0, 0, 231, 233, 3, 28, 14, 0, 232, 234, 5, 23, 0, 0, 233, 232, 1, 0, 0, 0, 233, 234, 1, 0, 0, 0, 234, 236, 1, 0, 0, 0, 235, 231, 1, 0, 0, 0, 235, 236, 1, 0, 0, 0, 236, 237, 1, 0, 0, 0, 237, 253, 5, 27, 0, 0, 238, 239, 5, 35, 0, 0, 239, 245, 5, 17, 0, 0, 240, 241, 3, 26, 13, 0, 241, 242, 5, 23, 0, 0, 242, 244, 1, 0, 0, 0, 243, 240, 1, 0, 0, 0, 244, 247, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 245, 246, 1, 0, 0, 0, 246, 249, 1, 0, 0, 0, 247, 245, 1, 0, 0, 0, 248, 250, 3, 26, 13, 0, 249, 248, 1, 0, 0, 0, 249, 250, 1, 0, 0, 0, 250, 251, 1, 0, 0, 0, 251, 253, 5, 18, 0, 0, 252, 166, 1, 0, 0, 0, 252, 167, 1, 0, 0, 0, 252, 168, 1, 0, 0, 0, 252, 169, 1, 0, 0, 0, 252, 170, 1, 0, 0, 0, 252, 171, 1, 0, 0, 0, 252, 172, 1, 0, 0, 0, 252, 185, 1, 0, 0, 0, 252, 203, 1, 0, 0, 0, 252, 221, 1, 0, 0, 0, 252, 238, 1, 0, 0, 0, 253, 27, 1, 0, 0, 0, 254, 257, 3, 26, 13, 0, 255, 256, 5, 4, 0, 0, 256, 258, 3, 26, 13, 0, 257, 255, 1, 0, 0, 0, 257, 258, 1, 0, 0, 0, 258, 265, 1, 0, 0, 0, 259, 260, 5, 7, 0, 0, 260, 261, 3, 26, 13, 0, 261, 262, 5, 4, 0, 0, 262, 263, 3, 26, 13, 0, 263, 265, 1, 0, 0, 0, 264, 254, 1, 0, 0, 0, 264, 259, 1, 0, 0, 0, 265, 29, 1, 0, 0, 0, 266, 267, 3, 26, 13, 0, 267, 31, 1, 0, 0, 0, 268, 274, 5, 19, 0, 0, 269, 270, 3, 30, 15, 0, 270, 271, 5, 23, 0, 0, 271, 273, 1, 0, 0, 0, 272, 269, 1, 0, 0, 0, 273, 276, 1, 0, 0, 0, 274, 272, 1, 0, 0, 0, 274, 275, 1, 0, 0, 0, 275, 278, 1, 0, 0, 0, 276, 274, 1, 0, 0, 0, 277, 279, 3, 30, 15, 0, 278, 277, 1, 0, 0, 0, 278, 279, 1, 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 281, 5, 20, 0, 0, 281, 33, 1, 0, 0, 0, 37, 38, 43, 48, 50, 56, 63, 70, 75, 79, 93, 97, 107, 116, 120, 126, 137, 140, 147, 150, 160, 164, 178, 182, 193, 200, 211, 218, 228, 233, 235, 245, 249, 252, 257, 264, 274, 278] \ No newline at end of file +[4, 1, 49, 342, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 1, 0, 1, 0, 1, 0, 1, 1, 3, 1, 45, 8, 1, 1, 1, 5, 1, 48, 8, 1, 10, 1, 12, 1, 51, 9, 1, 1, 1, 1, 1, 5, 1, 55, 8, 1, 10, 1, 12, 1, 58, 9, 1, 1, 2, 1, 2, 1, 2, 3, 2, 63, 8, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 3, 3, 70, 8, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 3, 4, 77, 8, 4, 1, 4, 3, 4, 80, 8, 4, 1, 4, 5, 4, 83, 8, 4, 10, 4, 12, 4, 86, 9, 4, 1, 4, 3, 4, 89, 8, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 5, 6, 101, 8, 6, 10, 6, 12, 6, 104, 9, 6, 1, 6, 3, 6, 107, 8, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 3, 8, 116, 8, 8, 1, 8, 1, 8, 3, 8, 120, 8, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 3, 9, 129, 8, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 5, 11, 138, 8, 11, 10, 11, 12, 11, 141, 9, 11, 1, 11, 3, 11, 144, 8, 11, 1, 11, 1, 11, 5, 11, 148, 8, 11, 10, 11, 12, 11, 151, 9, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 5, 12, 159, 8, 12, 10, 12, 12, 12, 162, 9, 12, 3, 12, 164, 8, 12, 1, 12, 1, 12, 1, 12, 5, 12, 169, 8, 12, 10, 12, 12, 12, 172, 9, 12, 3, 12, 174, 8, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 3, 14, 182, 8, 14, 1, 14, 1, 14, 3, 14, 186, 8, 14, 1, 14, 1, 14, 3, 14, 190, 8, 14, 1, 14, 1, 14, 3, 14, 194, 8, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 3, 15, 224, 8, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 5, 16, 236, 8, 16, 10, 16, 12, 16, 239, 9, 16, 1, 16, 3, 16, 242, 8, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 5, 16, 251, 8, 16, 10, 16, 12, 16, 254, 9, 16, 1, 16, 1, 16, 1, 16, 1, 16, 3, 16, 260, 8, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 5, 16, 269, 8, 16, 10, 16, 12, 16, 272, 9, 16, 1, 16, 1, 16, 1, 16, 1, 16, 3, 16, 278, 8, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 5, 16, 286, 8, 16, 10, 16, 12, 16, 289, 9, 16, 1, 16, 1, 16, 3, 16, 293, 8, 16, 3, 16, 295, 8, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 5, 16, 303, 8, 16, 10, 16, 12, 16, 306, 9, 16, 1, 16, 3, 16, 309, 8, 16, 1, 16, 3, 16, 312, 8, 16, 1, 17, 1, 17, 1, 17, 3, 17, 317, 8, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 324, 8, 17, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 5, 19, 332, 8, 19, 10, 19, 12, 19, 335, 9, 19, 1, 19, 3, 19, 338, 8, 19, 1, 19, 1, 19, 1, 19, 0, 0, 20, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 0, 3, 2, 0, 10, 10, 31, 31, 3, 0, 5, 5, 39, 40, 43, 43, 2, 0, 23, 24, 27, 30, 375, 0, 40, 1, 0, 0, 0, 2, 44, 1, 0, 0, 0, 4, 59, 1, 0, 0, 0, 6, 66, 1, 0, 0, 0, 8, 73, 1, 0, 0, 0, 10, 92, 1, 0, 0, 0, 12, 95, 1, 0, 0, 0, 14, 110, 1, 0, 0, 0, 16, 113, 1, 0, 0, 0, 18, 123, 1, 0, 0, 0, 20, 132, 1, 0, 0, 0, 22, 134, 1, 0, 0, 0, 24, 173, 1, 0, 0, 0, 26, 175, 1, 0, 0, 0, 28, 193, 1, 0, 0, 0, 30, 223, 1, 0, 0, 0, 32, 311, 1, 0, 0, 0, 34, 323, 1, 0, 0, 0, 36, 325, 1, 0, 0, 0, 38, 327, 1, 0, 0, 0, 40, 41, 3, 2, 1, 0, 41, 42, 5, 0, 0, 1, 42, 1, 1, 0, 0, 0, 43, 45, 3, 4, 2, 0, 44, 43, 1, 0, 0, 0, 44, 45, 1, 0, 0, 0, 45, 49, 1, 0, 0, 0, 46, 48, 3, 6, 3, 0, 47, 46, 1, 0, 0, 0, 48, 51, 1, 0, 0, 0, 49, 47, 1, 0, 0, 0, 49, 50, 1, 0, 0, 0, 50, 56, 1, 0, 0, 0, 51, 49, 1, 0, 0, 0, 52, 55, 3, 8, 4, 0, 53, 55, 3, 16, 8, 0, 54, 52, 1, 0, 0, 0, 54, 53, 1, 0, 0, 0, 55, 58, 1, 0, 0, 0, 56, 54, 1, 0, 0, 0, 56, 57, 1, 0, 0, 0, 57, 3, 1, 0, 0, 0, 58, 56, 1, 0, 0, 0, 59, 60, 5, 11, 0, 0, 60, 62, 5, 44, 0, 0, 61, 63, 3, 38, 19, 0, 62, 61, 1, 0, 0, 0, 62, 63, 1, 0, 0, 0, 63, 64, 1, 0, 0, 0, 64, 65, 5, 26, 0, 0, 65, 5, 1, 0, 0, 0, 66, 67, 5, 12, 0, 0, 67, 69, 5, 44, 0, 0, 68, 70, 3, 38, 19, 0, 69, 68, 1, 0, 0, 0, 69, 70, 1, 0, 0, 0, 70, 71, 1, 0, 0, 0, 71, 72, 5, 26, 0, 0, 72, 7, 1, 0, 0, 0, 73, 74, 5, 13, 0, 0, 74, 76, 3, 36, 18, 0, 75, 77, 5, 1, 0, 0, 76, 75, 1, 0, 0, 0, 76, 77, 1, 0, 0, 0, 77, 79, 1, 0, 0, 0, 78, 80, 3, 10, 5, 0, 79, 78, 1, 0, 0, 0, 79, 80, 1, 0, 0, 0, 80, 84, 1, 0, 0, 0, 81, 83, 3, 12, 6, 0, 82, 81, 1, 0, 0, 0, 83, 86, 1, 0, 0, 0, 84, 82, 1, 0, 0, 0, 84, 85, 1, 0, 0, 0, 85, 88, 1, 0, 0, 0, 86, 84, 1, 0, 0, 0, 87, 89, 3, 14, 7, 0, 88, 87, 1, 0, 0, 0, 88, 89, 1, 0, 0, 0, 89, 90, 1, 0, 0, 0, 90, 91, 5, 2, 0, 0, 91, 9, 1, 0, 0, 0, 92, 93, 5, 3, 0, 0, 93, 94, 3, 38, 19, 0, 94, 11, 1, 0, 0, 0, 95, 96, 5, 14, 0, 0, 96, 102, 5, 19, 0, 0, 97, 98, 3, 32, 16, 0, 98, 99, 5, 25, 0, 0, 99, 101, 1, 0, 0, 0, 100, 97, 1, 0, 0, 0, 101, 104, 1, 0, 0, 0, 102, 100, 1, 0, 0, 0, 102, 103, 1, 0, 0, 0, 103, 106, 1, 0, 0, 0, 104, 102, 1, 0, 0, 0, 105, 107, 3, 32, 16, 0, 106, 105, 1, 0, 0, 0, 106, 107, 1, 0, 0, 0, 107, 108, 1, 0, 0, 0, 108, 109, 5, 20, 0, 0, 109, 13, 1, 0, 0, 0, 110, 111, 5, 4, 0, 0, 111, 112, 3, 38, 19, 0, 112, 15, 1, 0, 0, 0, 113, 115, 3, 36, 18, 0, 114, 116, 3, 18, 9, 0, 115, 114, 1, 0, 0, 0, 115, 116, 1, 0, 0, 0, 116, 119, 1, 0, 0, 0, 117, 118, 7, 0, 0, 0, 118, 120, 3, 22, 11, 0, 119, 117, 1, 0, 0, 0, 119, 120, 1, 0, 0, 0, 120, 121, 1, 0, 0, 0, 121, 122, 5, 2, 0, 0, 122, 17, 1, 0, 0, 0, 123, 124, 5, 34, 0, 0, 124, 125, 5, 19, 0, 0, 125, 128, 3, 20, 10, 0, 126, 127, 5, 25, 0, 0, 127, 129, 3, 20, 10, 0, 128, 126, 1, 0, 0, 0, 128, 129, 1, 0, 0, 0, 129, 130, 1, 0, 0, 0, 130, 131, 5, 20, 0, 0, 131, 19, 1, 0, 0, 0, 132, 133, 7, 1, 0, 0, 133, 21, 1, 0, 0, 0, 134, 139, 3, 28, 14, 0, 135, 136, 5, 25, 0, 0, 136, 138, 3, 28, 14, 0, 137, 135, 1, 0, 0, 0, 138, 141, 1, 0, 0, 0, 139, 137, 1, 0, 0, 0, 139, 140, 1, 0, 0, 0, 140, 143, 1, 0, 0, 0, 141, 139, 1, 0, 0, 0, 142, 144, 5, 25, 0, 0, 143, 142, 1, 0, 0, 0, 143, 144, 1, 0, 0, 0, 144, 149, 1, 0, 0, 0, 145, 146, 5, 33, 0, 0, 146, 148, 3, 24, 12, 0, 147, 145, 1, 0, 0, 0, 148, 151, 1, 0, 0, 0, 149, 147, 1, 0, 0, 0, 149, 150, 1, 0, 0, 0, 150, 23, 1, 0, 0, 0, 151, 149, 1, 0, 0, 0, 152, 153, 5, 16, 0, 0, 153, 163, 3, 32, 16, 0, 154, 155, 5, 25, 0, 0, 155, 160, 3, 26, 13, 0, 156, 157, 5, 25, 0, 0, 157, 159, 3, 26, 13, 0, 158, 156, 1, 0, 0, 0, 159, 162, 1, 0, 0, 0, 160, 158, 1, 0, 0, 0, 160, 161, 1, 0, 0, 0, 161, 164, 1, 0, 0, 0, 162, 160, 1, 0, 0, 0, 163, 154, 1, 0, 0, 0, 163, 164, 1, 0, 0, 0, 164, 174, 1, 0, 0, 0, 165, 170, 3, 26, 13, 0, 166, 167, 5, 25, 0, 0, 167, 169, 3, 26, 13, 0, 168, 166, 1, 0, 0, 0, 169, 172, 1, 0, 0, 0, 170, 168, 1, 0, 0, 0, 170, 171, 1, 0, 0, 0, 171, 174, 1, 0, 0, 0, 172, 170, 1, 0, 0, 0, 173, 152, 1, 0, 0, 0, 173, 165, 1, 0, 0, 0, 174, 25, 1, 0, 0, 0, 175, 176, 5, 15, 0, 0, 176, 177, 5, 43, 0, 0, 177, 178, 5, 23, 0, 0, 178, 179, 3, 32, 16, 0, 179, 27, 1, 0, 0, 0, 180, 182, 3, 30, 15, 0, 181, 180, 1, 0, 0, 0, 181, 182, 1, 0, 0, 0, 182, 183, 1, 0, 0, 0, 183, 185, 3, 32, 16, 0, 184, 186, 3, 18, 9, 0, 185, 184, 1, 0, 0, 0, 185, 186, 1, 0, 0, 0, 186, 189, 1, 0, 0, 0, 187, 188, 7, 2, 0, 0, 188, 190, 3, 32, 16, 0, 189, 187, 1, 0, 0, 0, 189, 190, 1, 0, 0, 0, 190, 194, 1, 0, 0, 0, 191, 192, 5, 26, 0, 0, 192, 194, 3, 32, 16, 0, 193, 181, 1, 0, 0, 0, 193, 191, 1, 0, 0, 0, 194, 29, 1, 0, 0, 0, 195, 196, 5, 35, 0, 0, 196, 197, 5, 19, 0, 0, 197, 198, 3, 20, 10, 0, 198, 199, 5, 25, 0, 0, 199, 200, 3, 20, 10, 0, 200, 201, 5, 20, 0, 0, 201, 224, 1, 0, 0, 0, 202, 203, 5, 37, 0, 0, 203, 204, 5, 19, 0, 0, 204, 205, 3, 20, 10, 0, 205, 206, 5, 25, 0, 0, 206, 207, 3, 20, 10, 0, 207, 208, 5, 20, 0, 0, 208, 224, 1, 0, 0, 0, 209, 210, 5, 36, 0, 0, 210, 211, 5, 19, 0, 0, 211, 212, 3, 20, 10, 0, 212, 213, 5, 25, 0, 0, 213, 214, 3, 20, 10, 0, 214, 215, 5, 20, 0, 0, 215, 224, 1, 0, 0, 0, 216, 217, 5, 38, 0, 0, 217, 218, 5, 19, 0, 0, 218, 219, 3, 20, 10, 0, 219, 220, 5, 25, 0, 0, 220, 221, 3, 20, 10, 0, 221, 222, 5, 20, 0, 0, 222, 224, 1, 0, 0, 0, 223, 195, 1, 0, 0, 0, 223, 202, 1, 0, 0, 0, 223, 209, 1, 0, 0, 0, 223, 216, 1, 0, 0, 0, 224, 31, 1, 0, 0, 0, 225, 312, 5, 43, 0, 0, 226, 312, 5, 47, 0, 0, 227, 312, 5, 41, 0, 0, 228, 312, 5, 42, 0, 0, 229, 312, 5, 48, 0, 0, 230, 312, 5, 49, 0, 0, 231, 237, 5, 19, 0, 0, 232, 233, 3, 32, 16, 0, 233, 234, 5, 25, 0, 0, 234, 236, 1, 0, 0, 0, 235, 232, 1, 0, 0, 0, 236, 239, 1, 0, 0, 0, 237, 235, 1, 0, 0, 0, 237, 238, 1, 0, 0, 0, 238, 241, 1, 0, 0, 0, 239, 237, 1, 0, 0, 0, 240, 242, 3, 32, 16, 0, 241, 240, 1, 0, 0, 0, 241, 242, 1, 0, 0, 0, 242, 243, 1, 0, 0, 0, 243, 312, 5, 20, 0, 0, 244, 252, 5, 19, 0, 0, 245, 246, 3, 32, 16, 0, 246, 247, 5, 6, 0, 0, 247, 248, 3, 32, 16, 0, 248, 249, 5, 25, 0, 0, 249, 251, 1, 0, 0, 0, 250, 245, 1, 0, 0, 0, 251, 254, 1, 0, 0, 0, 252, 250, 1, 0, 0, 0, 252, 253, 1, 0, 0, 0, 253, 259, 1, 0, 0, 0, 254, 252, 1, 0, 0, 0, 255, 256, 3, 32, 16, 0, 256, 257, 5, 6, 0, 0, 257, 258, 3, 32, 16, 0, 258, 260, 1, 0, 0, 0, 259, 255, 1, 0, 0, 0, 259, 260, 1, 0, 0, 0, 260, 261, 1, 0, 0, 0, 261, 312, 5, 20, 0, 0, 262, 270, 5, 21, 0, 0, 263, 264, 3, 32, 16, 0, 264, 265, 5, 6, 0, 0, 265, 266, 3, 32, 16, 0, 266, 267, 5, 25, 0, 0, 267, 269, 1, 0, 0, 0, 268, 263, 1, 0, 0, 0, 269, 272, 1, 0, 0, 0, 270, 268, 1, 0, 0, 0, 270, 271, 1, 0, 0, 0, 271, 277, 1, 0, 0, 0, 272, 270, 1, 0, 0, 0, 273, 274, 3, 32, 16, 0, 274, 275, 5, 6, 0, 0, 275, 276, 3, 32, 16, 0, 276, 278, 1, 0, 0, 0, 277, 273, 1, 0, 0, 0, 277, 278, 1, 0, 0, 0, 278, 279, 1, 0, 0, 0, 279, 312, 5, 22, 0, 0, 280, 281, 5, 46, 0, 0, 281, 287, 5, 28, 0, 0, 282, 283, 3, 34, 17, 0, 283, 284, 5, 25, 0, 0, 284, 286, 1, 0, 0, 0, 285, 282, 1, 0, 0, 0, 286, 289, 1, 0, 0, 0, 287, 285, 1, 0, 0, 0, 287, 288, 1, 0, 0, 0, 288, 294, 1, 0, 0, 0, 289, 287, 1, 0, 0, 0, 290, 292, 3, 34, 17, 0, 291, 293, 5, 25, 0, 0, 292, 291, 1, 0, 0, 0, 292, 293, 1, 0, 0, 0, 293, 295, 1, 0, 0, 0, 294, 290, 1, 0, 0, 0, 294, 295, 1, 0, 0, 0, 295, 296, 1, 0, 0, 0, 296, 312, 5, 30, 0, 0, 297, 298, 5, 44, 0, 0, 298, 304, 5, 17, 0, 0, 299, 300, 3, 32, 16, 0, 300, 301, 5, 25, 0, 0, 301, 303, 1, 0, 0, 0, 302, 299, 1, 0, 0, 0, 303, 306, 1, 0, 0, 0, 304, 302, 1, 0, 0, 0, 304, 305, 1, 0, 0, 0, 305, 308, 1, 0, 0, 0, 306, 304, 1, 0, 0, 0, 307, 309, 3, 32, 16, 0, 308, 307, 1, 0, 0, 0, 308, 309, 1, 0, 0, 0, 309, 310, 1, 0, 0, 0, 310, 312, 5, 18, 0, 0, 311, 225, 1, 0, 0, 0, 311, 226, 1, 0, 0, 0, 311, 227, 1, 0, 0, 0, 311, 228, 1, 0, 0, 0, 311, 229, 1, 0, 0, 0, 311, 230, 1, 0, 0, 0, 311, 231, 1, 0, 0, 0, 311, 244, 1, 0, 0, 0, 311, 262, 1, 0, 0, 0, 311, 280, 1, 0, 0, 0, 311, 297, 1, 0, 0, 0, 312, 33, 1, 0, 0, 0, 313, 316, 3, 32, 16, 0, 314, 315, 5, 6, 0, 0, 315, 317, 3, 32, 16, 0, 316, 314, 1, 0, 0, 0, 316, 317, 1, 0, 0, 0, 317, 324, 1, 0, 0, 0, 318, 319, 5, 7, 0, 0, 319, 320, 3, 32, 16, 0, 320, 321, 5, 6, 0, 0, 321, 322, 3, 32, 16, 0, 322, 324, 1, 0, 0, 0, 323, 313, 1, 0, 0, 0, 323, 318, 1, 0, 0, 0, 324, 35, 1, 0, 0, 0, 325, 326, 3, 32, 16, 0, 326, 37, 1, 0, 0, 0, 327, 333, 5, 19, 0, 0, 328, 329, 3, 36, 18, 0, 329, 330, 5, 25, 0, 0, 330, 332, 1, 0, 0, 0, 331, 328, 1, 0, 0, 0, 332, 335, 1, 0, 0, 0, 333, 331, 1, 0, 0, 0, 333, 334, 1, 0, 0, 0, 334, 337, 1, 0, 0, 0, 335, 333, 1, 0, 0, 0, 336, 338, 3, 36, 18, 0, 337, 336, 1, 0, 0, 0, 337, 338, 1, 0, 0, 0, 338, 339, 1, 0, 0, 0, 339, 340, 5, 20, 0, 0, 340, 39, 1, 0, 0, 0, 43, 44, 49, 54, 56, 62, 69, 76, 79, 84, 88, 102, 106, 115, 119, 128, 139, 143, 149, 160, 163, 170, 173, 181, 185, 189, 193, 223, 237, 241, 252, 259, 270, 277, 287, 292, 294, 304, 308, 311, 316, 323, 333, 337] \ No newline at end of file diff --git a/parse/gen/Mangle.tokens b/parse/gen/Mangle.tokens index 284f734..935aa18 100644 --- a/parse/gen/Mangle.tokens +++ b/parse/gen/Mangle.tokens @@ -18,34 +18,43 @@ LPAREN=17 RPAREN=18 LBRACKET=19 RBRACKET=20 -EQ=21 -BANGEQ=22 -COMMA=23 -BANG=24 -LESS=25 -LESSEQ=26 -GREATER=27 -GREATEREQ=28 -COLONDASH=29 -NEWLINE=30 -PIPEGREATER=31 -NUMBER=32 -FLOAT=33 -VARIABLE=34 -NAME=35 -TYPENAME=36 -DOT_TYPE=37 -CONSTANT=38 -STRING=39 -BYTESTRING=40 -'.'=1 -'descr'=2 -'inclusion'=3 -':'=4 -'{'=5 -'}'=6 +LBRACE=21 +RBRACE=22 +EQ=23 +BANGEQ=24 +COMMA=25 +BANG=26 +LESSEQ=27 +LESS=28 +GREATEREQ=29 +GREATER=30 +COLONDASH=31 +NEWLINE=32 +PIPEGREATER=33 +AT=34 +DIAMONDMINUS=35 +DIAMONDPLUS=36 +BOXMINUS=37 +BOXPLUS=38 +TIMESTAMP=39 +DURATION=40 +NUMBER=41 +FLOAT=42 +VARIABLE=43 +NAME=44 +TYPENAME=45 +DOT_TYPE=46 +CONSTANT=47 +STRING=48 +BYTESTRING=49 +'temporal'=1 +'.'=2 +'descr'=3 +'inclusion'=4 +'now'=5 +':'=6 'opt'=7 -'\u27F8'=10 +'⟸'=10 'Package'=11 'Use'=12 'Decl'=13 @@ -56,14 +65,21 @@ BYTESTRING=40 ')'=18 '['=19 ']'=20 -'='=21 -'!='=22 -','=23 -'!'=24 -'<'=25 -'<='=26 -'>'=27 -'>='=28 -':-'=29 -'\n'=30 -'|>'=31 +'{'=21 +'}'=22 +'='=23 +'!='=24 +','=25 +'!'=26 +'<='=27 +'<'=28 +'>='=29 +'>'=30 +':-'=31 +'\n'=32 +'|>'=33 +'@'=34 +'<-'=35 +'<+'=36 +'[-'=37 +'[+'=38 diff --git a/parse/gen/MangleLexer.interp b/parse/gen/MangleLexer.interp index e7304d8..f917f00 100644 --- a/parse/gen/MangleLexer.interp +++ b/parse/gen/MangleLexer.interp @@ -1,15 +1,15 @@ token literal names: null +'temporal' '.' 'descr' 'inclusion' +'now' ':' -'{' -'}' 'opt' null null -'\u27F8' +'⟸' 'Package' 'Use' 'Decl' @@ -20,17 +20,26 @@ null ')' '[' ']' +'{' +'}' '=' '!=' ',' '!' -'<' '<=' -'>' +'<' '>=' +'>' ':-' '\n' '|>' +'@' +'<-' +'<+' +'[-' +'[+' +null +null null null null @@ -63,17 +72,26 @@ LPAREN RPAREN LBRACKET RBRACKET +LBRACE +RBRACE EQ BANGEQ COMMA BANG -LESS LESSEQ -GREATER +LESS GREATEREQ +GREATER COLONDASH NEWLINE PIPEGREATER +AT +DIAMONDMINUS +DIAMONDPLUS +BOXMINUS +BOXPLUS +TIMESTAMP +DURATION NUMBER FLOAT VARIABLE @@ -105,19 +123,28 @@ LPAREN RPAREN LBRACKET RBRACKET +LBRACE +RBRACE EQ BANGEQ COMMA BANG -LESS LESSEQ -GREATER +LESS GREATEREQ +GREATER COLONDASH NEWLINE PIPEGREATER +AT +DIAMONDMINUS +DIAMONDPLUS +BOXMINUS +BOXPLUS LETTER DIGIT +TIMESTAMP +DURATION NUMBER FLOAT EXPONENT @@ -147,4 +174,4 @@ mode names: DEFAULT_MODE atn: -[4, 0, 40, 417, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 4, 7, 137, 8, 7, 11, 7, 12, 7, 138, 1, 7, 1, 7, 1, 8, 1, 8, 5, 8, 145, 8, 8, 10, 8, 12, 8, 148, 9, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 23, 1, 23, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 32, 1, 32, 1, 33, 3, 33, 224, 8, 33, 1, 33, 1, 33, 5, 33, 228, 8, 33, 10, 33, 12, 33, 231, 9, 33, 1, 34, 3, 34, 234, 8, 34, 1, 34, 4, 34, 237, 8, 34, 11, 34, 12, 34, 238, 1, 34, 1, 34, 4, 34, 243, 8, 34, 11, 34, 12, 34, 244, 1, 34, 3, 34, 248, 8, 34, 1, 34, 3, 34, 251, 8, 34, 1, 34, 1, 34, 4, 34, 255, 8, 34, 11, 34, 12, 34, 256, 1, 34, 3, 34, 260, 8, 34, 3, 34, 262, 8, 34, 1, 35, 1, 35, 3, 35, 266, 8, 35, 1, 35, 4, 35, 269, 8, 35, 11, 35, 12, 35, 270, 1, 36, 1, 36, 1, 37, 1, 37, 3, 37, 277, 8, 37, 1, 38, 1, 38, 1, 38, 5, 38, 282, 8, 38, 10, 38, 12, 38, 285, 9, 38, 3, 38, 287, 8, 38, 1, 39, 1, 39, 1, 39, 3, 39, 292, 8, 39, 1, 40, 3, 40, 295, 8, 40, 1, 40, 1, 40, 1, 40, 1, 40, 5, 40, 301, 8, 40, 10, 40, 12, 40, 304, 9, 40, 1, 41, 1, 41, 1, 41, 1, 41, 5, 41, 310, 8, 41, 10, 41, 12, 41, 313, 9, 41, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 3, 43, 321, 8, 43, 1, 44, 1, 44, 4, 44, 325, 8, 44, 11, 44, 12, 44, 326, 1, 44, 1, 44, 4, 44, 331, 8, 44, 11, 44, 12, 44, 332, 5, 44, 335, 8, 44, 10, 44, 12, 44, 338, 9, 44, 1, 45, 1, 45, 3, 45, 342, 8, 45, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 5, 47, 350, 8, 47, 10, 47, 12, 47, 353, 9, 47, 1, 47, 1, 47, 1, 47, 1, 47, 5, 47, 359, 8, 47, 10, 47, 12, 47, 362, 9, 47, 1, 47, 3, 47, 365, 8, 47, 1, 48, 1, 48, 5, 48, 369, 8, 48, 10, 48, 12, 48, 372, 9, 48, 1, 48, 1, 48, 1, 49, 1, 49, 3, 49, 378, 8, 49, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 3, 51, 405, 8, 51, 1, 51, 3, 51, 408, 8, 51, 1, 51, 1, 51, 1, 51, 1, 51, 3, 51, 414, 8, 51, 1, 52, 1, 52, 1, 370, 0, 53, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, 51, 26, 53, 27, 55, 28, 57, 29, 59, 30, 61, 31, 63, 0, 65, 0, 67, 32, 69, 33, 71, 0, 73, 0, 75, 0, 77, 34, 79, 0, 81, 35, 83, 36, 85, 37, 87, 0, 89, 38, 91, 39, 93, 40, 95, 0, 97, 0, 99, 0, 101, 0, 103, 0, 105, 0, 1, 0, 11, 3, 0, 9, 10, 12, 13, 32, 32, 1, 0, 10, 10, 2, 0, 65, 90, 97, 122, 2, 0, 69, 69, 101, 101, 2, 0, 43, 43, 45, 45, 2, 0, 58, 58, 95, 95, 4, 0, 37, 37, 45, 46, 95, 95, 126, 126, 2, 0, 39, 39, 92, 92, 2, 0, 34, 34, 92, 92, 1, 0, 92, 92, 2, 0, 48, 57, 97, 102, 449, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 61, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 77, 1, 0, 0, 0, 0, 81, 1, 0, 0, 0, 0, 83, 1, 0, 0, 0, 0, 85, 1, 0, 0, 0, 0, 89, 1, 0, 0, 0, 0, 91, 1, 0, 0, 0, 0, 93, 1, 0, 0, 0, 1, 107, 1, 0, 0, 0, 3, 109, 1, 0, 0, 0, 5, 115, 1, 0, 0, 0, 7, 125, 1, 0, 0, 0, 9, 127, 1, 0, 0, 0, 11, 129, 1, 0, 0, 0, 13, 131, 1, 0, 0, 0, 15, 136, 1, 0, 0, 0, 17, 142, 1, 0, 0, 0, 19, 151, 1, 0, 0, 0, 21, 153, 1, 0, 0, 0, 23, 161, 1, 0, 0, 0, 25, 165, 1, 0, 0, 0, 27, 170, 1, 0, 0, 0, 29, 176, 1, 0, 0, 0, 31, 180, 1, 0, 0, 0, 33, 183, 1, 0, 0, 0, 35, 185, 1, 0, 0, 0, 37, 187, 1, 0, 0, 0, 39, 189, 1, 0, 0, 0, 41, 191, 1, 0, 0, 0, 43, 193, 1, 0, 0, 0, 45, 196, 1, 0, 0, 0, 47, 198, 1, 0, 0, 0, 49, 200, 1, 0, 0, 0, 51, 202, 1, 0, 0, 0, 53, 205, 1, 0, 0, 0, 55, 207, 1, 0, 0, 0, 57, 210, 1, 0, 0, 0, 59, 213, 1, 0, 0, 0, 61, 215, 1, 0, 0, 0, 63, 218, 1, 0, 0, 0, 65, 220, 1, 0, 0, 0, 67, 223, 1, 0, 0, 0, 69, 261, 1, 0, 0, 0, 71, 263, 1, 0, 0, 0, 73, 272, 1, 0, 0, 0, 75, 276, 1, 0, 0, 0, 77, 286, 1, 0, 0, 0, 79, 291, 1, 0, 0, 0, 81, 294, 1, 0, 0, 0, 83, 305, 1, 0, 0, 0, 85, 314, 1, 0, 0, 0, 87, 320, 1, 0, 0, 0, 89, 322, 1, 0, 0, 0, 91, 341, 1, 0, 0, 0, 93, 343, 1, 0, 0, 0, 95, 364, 1, 0, 0, 0, 97, 366, 1, 0, 0, 0, 99, 377, 1, 0, 0, 0, 101, 379, 1, 0, 0, 0, 103, 413, 1, 0, 0, 0, 105, 415, 1, 0, 0, 0, 107, 108, 5, 46, 0, 0, 108, 2, 1, 0, 0, 0, 109, 110, 5, 100, 0, 0, 110, 111, 5, 101, 0, 0, 111, 112, 5, 115, 0, 0, 112, 113, 5, 99, 0, 0, 113, 114, 5, 114, 0, 0, 114, 4, 1, 0, 0, 0, 115, 116, 5, 105, 0, 0, 116, 117, 5, 110, 0, 0, 117, 118, 5, 99, 0, 0, 118, 119, 5, 108, 0, 0, 119, 120, 5, 117, 0, 0, 120, 121, 5, 115, 0, 0, 121, 122, 5, 105, 0, 0, 122, 123, 5, 111, 0, 0, 123, 124, 5, 110, 0, 0, 124, 6, 1, 0, 0, 0, 125, 126, 5, 58, 0, 0, 126, 8, 1, 0, 0, 0, 127, 128, 5, 123, 0, 0, 128, 10, 1, 0, 0, 0, 129, 130, 5, 125, 0, 0, 130, 12, 1, 0, 0, 0, 131, 132, 5, 111, 0, 0, 132, 133, 5, 112, 0, 0, 133, 134, 5, 116, 0, 0, 134, 14, 1, 0, 0, 0, 135, 137, 7, 0, 0, 0, 136, 135, 1, 0, 0, 0, 137, 138, 1, 0, 0, 0, 138, 136, 1, 0, 0, 0, 138, 139, 1, 0, 0, 0, 139, 140, 1, 0, 0, 0, 140, 141, 6, 7, 0, 0, 141, 16, 1, 0, 0, 0, 142, 146, 5, 35, 0, 0, 143, 145, 8, 1, 0, 0, 144, 143, 1, 0, 0, 0, 145, 148, 1, 0, 0, 0, 146, 144, 1, 0, 0, 0, 146, 147, 1, 0, 0, 0, 147, 149, 1, 0, 0, 0, 148, 146, 1, 0, 0, 0, 149, 150, 6, 8, 0, 0, 150, 18, 1, 0, 0, 0, 151, 152, 5, 10232, 0, 0, 152, 20, 1, 0, 0, 0, 153, 154, 5, 80, 0, 0, 154, 155, 5, 97, 0, 0, 155, 156, 5, 99, 0, 0, 156, 157, 5, 107, 0, 0, 157, 158, 5, 97, 0, 0, 158, 159, 5, 103, 0, 0, 159, 160, 5, 101, 0, 0, 160, 22, 1, 0, 0, 0, 161, 162, 5, 85, 0, 0, 162, 163, 5, 115, 0, 0, 163, 164, 5, 101, 0, 0, 164, 24, 1, 0, 0, 0, 165, 166, 5, 68, 0, 0, 166, 167, 5, 101, 0, 0, 167, 168, 5, 99, 0, 0, 168, 169, 5, 108, 0, 0, 169, 26, 1, 0, 0, 0, 170, 171, 5, 98, 0, 0, 171, 172, 5, 111, 0, 0, 172, 173, 5, 117, 0, 0, 173, 174, 5, 110, 0, 0, 174, 175, 5, 100, 0, 0, 175, 28, 1, 0, 0, 0, 176, 177, 5, 108, 0, 0, 177, 178, 5, 101, 0, 0, 178, 179, 5, 116, 0, 0, 179, 30, 1, 0, 0, 0, 180, 181, 5, 100, 0, 0, 181, 182, 5, 111, 0, 0, 182, 32, 1, 0, 0, 0, 183, 184, 5, 40, 0, 0, 184, 34, 1, 0, 0, 0, 185, 186, 5, 41, 0, 0, 186, 36, 1, 0, 0, 0, 187, 188, 5, 91, 0, 0, 188, 38, 1, 0, 0, 0, 189, 190, 5, 93, 0, 0, 190, 40, 1, 0, 0, 0, 191, 192, 5, 61, 0, 0, 192, 42, 1, 0, 0, 0, 193, 194, 5, 33, 0, 0, 194, 195, 5, 61, 0, 0, 195, 44, 1, 0, 0, 0, 196, 197, 5, 44, 0, 0, 197, 46, 1, 0, 0, 0, 198, 199, 5, 33, 0, 0, 199, 48, 1, 0, 0, 0, 200, 201, 5, 60, 0, 0, 201, 50, 1, 0, 0, 0, 202, 203, 5, 60, 0, 0, 203, 204, 5, 61, 0, 0, 204, 52, 1, 0, 0, 0, 205, 206, 5, 62, 0, 0, 206, 54, 1, 0, 0, 0, 207, 208, 5, 62, 0, 0, 208, 209, 5, 61, 0, 0, 209, 56, 1, 0, 0, 0, 210, 211, 5, 58, 0, 0, 211, 212, 5, 45, 0, 0, 212, 58, 1, 0, 0, 0, 213, 214, 5, 10, 0, 0, 214, 60, 1, 0, 0, 0, 215, 216, 5, 124, 0, 0, 216, 217, 5, 62, 0, 0, 217, 62, 1, 0, 0, 0, 218, 219, 7, 2, 0, 0, 219, 64, 1, 0, 0, 0, 220, 221, 2, 48, 57, 0, 221, 66, 1, 0, 0, 0, 222, 224, 5, 45, 0, 0, 223, 222, 1, 0, 0, 0, 223, 224, 1, 0, 0, 0, 224, 225, 1, 0, 0, 0, 225, 229, 3, 65, 32, 0, 226, 228, 3, 65, 32, 0, 227, 226, 1, 0, 0, 0, 228, 231, 1, 0, 0, 0, 229, 227, 1, 0, 0, 0, 229, 230, 1, 0, 0, 0, 230, 68, 1, 0, 0, 0, 231, 229, 1, 0, 0, 0, 232, 234, 5, 45, 0, 0, 233, 232, 1, 0, 0, 0, 233, 234, 1, 0, 0, 0, 234, 236, 1, 0, 0, 0, 235, 237, 3, 65, 32, 0, 236, 235, 1, 0, 0, 0, 237, 238, 1, 0, 0, 0, 238, 236, 1, 0, 0, 0, 238, 239, 1, 0, 0, 0, 239, 240, 1, 0, 0, 0, 240, 242, 5, 46, 0, 0, 241, 243, 3, 65, 32, 0, 242, 241, 1, 0, 0, 0, 243, 244, 1, 0, 0, 0, 244, 242, 1, 0, 0, 0, 244, 245, 1, 0, 0, 0, 245, 247, 1, 0, 0, 0, 246, 248, 3, 71, 35, 0, 247, 246, 1, 0, 0, 0, 247, 248, 1, 0, 0, 0, 248, 262, 1, 0, 0, 0, 249, 251, 5, 45, 0, 0, 250, 249, 1, 0, 0, 0, 250, 251, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 254, 5, 46, 0, 0, 253, 255, 3, 65, 32, 0, 254, 253, 1, 0, 0, 0, 255, 256, 1, 0, 0, 0, 256, 254, 1, 0, 0, 0, 256, 257, 1, 0, 0, 0, 257, 259, 1, 0, 0, 0, 258, 260, 3, 71, 35, 0, 259, 258, 1, 0, 0, 0, 259, 260, 1, 0, 0, 0, 260, 262, 1, 0, 0, 0, 261, 233, 1, 0, 0, 0, 261, 250, 1, 0, 0, 0, 262, 70, 1, 0, 0, 0, 263, 265, 7, 3, 0, 0, 264, 266, 7, 4, 0, 0, 265, 264, 1, 0, 0, 0, 265, 266, 1, 0, 0, 0, 266, 268, 1, 0, 0, 0, 267, 269, 3, 65, 32, 0, 268, 267, 1, 0, 0, 0, 269, 270, 1, 0, 0, 0, 270, 268, 1, 0, 0, 0, 270, 271, 1, 0, 0, 0, 271, 72, 1, 0, 0, 0, 272, 273, 2, 65, 90, 0, 273, 74, 1, 0, 0, 0, 274, 277, 3, 63, 31, 0, 275, 277, 3, 65, 32, 0, 276, 274, 1, 0, 0, 0, 276, 275, 1, 0, 0, 0, 277, 76, 1, 0, 0, 0, 278, 287, 5, 95, 0, 0, 279, 283, 3, 73, 36, 0, 280, 282, 3, 75, 37, 0, 281, 280, 1, 0, 0, 0, 282, 285, 1, 0, 0, 0, 283, 281, 1, 0, 0, 0, 283, 284, 1, 0, 0, 0, 284, 287, 1, 0, 0, 0, 285, 283, 1, 0, 0, 0, 286, 278, 1, 0, 0, 0, 286, 279, 1, 0, 0, 0, 287, 78, 1, 0, 0, 0, 288, 292, 3, 63, 31, 0, 289, 292, 3, 65, 32, 0, 290, 292, 7, 5, 0, 0, 291, 288, 1, 0, 0, 0, 291, 289, 1, 0, 0, 0, 291, 290, 1, 0, 0, 0, 292, 80, 1, 0, 0, 0, 293, 295, 5, 58, 0, 0, 294, 293, 1, 0, 0, 0, 294, 295, 1, 0, 0, 0, 295, 296, 1, 0, 0, 0, 296, 302, 2, 97, 122, 0, 297, 301, 3, 79, 39, 0, 298, 299, 5, 46, 0, 0, 299, 301, 3, 79, 39, 0, 300, 297, 1, 0, 0, 0, 300, 298, 1, 0, 0, 0, 301, 304, 1, 0, 0, 0, 302, 300, 1, 0, 0, 0, 302, 303, 1, 0, 0, 0, 303, 82, 1, 0, 0, 0, 304, 302, 1, 0, 0, 0, 305, 311, 2, 65, 90, 0, 306, 310, 3, 79, 39, 0, 307, 308, 5, 46, 0, 0, 308, 310, 3, 79, 39, 0, 309, 306, 1, 0, 0, 0, 309, 307, 1, 0, 0, 0, 310, 313, 1, 0, 0, 0, 311, 309, 1, 0, 0, 0, 311, 312, 1, 0, 0, 0, 312, 84, 1, 0, 0, 0, 313, 311, 1, 0, 0, 0, 314, 315, 5, 46, 0, 0, 315, 316, 3, 83, 41, 0, 316, 86, 1, 0, 0, 0, 317, 321, 3, 63, 31, 0, 318, 321, 3, 65, 32, 0, 319, 321, 7, 6, 0, 0, 320, 317, 1, 0, 0, 0, 320, 318, 1, 0, 0, 0, 320, 319, 1, 0, 0, 0, 321, 88, 1, 0, 0, 0, 322, 324, 5, 47, 0, 0, 323, 325, 3, 87, 43, 0, 324, 323, 1, 0, 0, 0, 325, 326, 1, 0, 0, 0, 326, 324, 1, 0, 0, 0, 326, 327, 1, 0, 0, 0, 327, 336, 1, 0, 0, 0, 328, 330, 5, 47, 0, 0, 329, 331, 3, 87, 43, 0, 330, 329, 1, 0, 0, 0, 331, 332, 1, 0, 0, 0, 332, 330, 1, 0, 0, 0, 332, 333, 1, 0, 0, 0, 333, 335, 1, 0, 0, 0, 334, 328, 1, 0, 0, 0, 335, 338, 1, 0, 0, 0, 336, 334, 1, 0, 0, 0, 336, 337, 1, 0, 0, 0, 337, 90, 1, 0, 0, 0, 338, 336, 1, 0, 0, 0, 339, 342, 3, 95, 47, 0, 340, 342, 3, 97, 48, 0, 341, 339, 1, 0, 0, 0, 341, 340, 1, 0, 0, 0, 342, 92, 1, 0, 0, 0, 343, 344, 5, 98, 0, 0, 344, 345, 3, 91, 45, 0, 345, 94, 1, 0, 0, 0, 346, 351, 5, 39, 0, 0, 347, 350, 3, 103, 51, 0, 348, 350, 8, 7, 0, 0, 349, 347, 1, 0, 0, 0, 349, 348, 1, 0, 0, 0, 350, 353, 1, 0, 0, 0, 351, 349, 1, 0, 0, 0, 351, 352, 1, 0, 0, 0, 352, 354, 1, 0, 0, 0, 353, 351, 1, 0, 0, 0, 354, 365, 5, 39, 0, 0, 355, 360, 5, 34, 0, 0, 356, 359, 3, 103, 51, 0, 357, 359, 8, 8, 0, 0, 358, 356, 1, 0, 0, 0, 358, 357, 1, 0, 0, 0, 359, 362, 1, 0, 0, 0, 360, 358, 1, 0, 0, 0, 360, 361, 1, 0, 0, 0, 361, 363, 1, 0, 0, 0, 362, 360, 1, 0, 0, 0, 363, 365, 5, 34, 0, 0, 364, 346, 1, 0, 0, 0, 364, 355, 1, 0, 0, 0, 365, 96, 1, 0, 0, 0, 366, 370, 5, 96, 0, 0, 367, 369, 3, 99, 49, 0, 368, 367, 1, 0, 0, 0, 369, 372, 1, 0, 0, 0, 370, 371, 1, 0, 0, 0, 370, 368, 1, 0, 0, 0, 371, 373, 1, 0, 0, 0, 372, 370, 1, 0, 0, 0, 373, 374, 5, 96, 0, 0, 374, 98, 1, 0, 0, 0, 375, 378, 3, 101, 50, 0, 376, 378, 3, 103, 51, 0, 377, 375, 1, 0, 0, 0, 377, 376, 1, 0, 0, 0, 378, 100, 1, 0, 0, 0, 379, 380, 8, 9, 0, 0, 380, 102, 1, 0, 0, 0, 381, 382, 5, 92, 0, 0, 382, 414, 5, 110, 0, 0, 383, 384, 5, 92, 0, 0, 384, 414, 5, 116, 0, 0, 385, 386, 5, 92, 0, 0, 386, 414, 5, 34, 0, 0, 387, 388, 5, 92, 0, 0, 388, 414, 5, 39, 0, 0, 389, 390, 5, 92, 0, 0, 390, 414, 5, 92, 0, 0, 391, 392, 5, 92, 0, 0, 392, 393, 5, 120, 0, 0, 393, 394, 3, 105, 52, 0, 394, 395, 3, 105, 52, 0, 395, 414, 1, 0, 0, 0, 396, 397, 5, 92, 0, 0, 397, 398, 5, 117, 0, 0, 398, 399, 5, 123, 0, 0, 399, 400, 3, 105, 52, 0, 400, 401, 3, 105, 52, 0, 401, 402, 3, 105, 52, 0, 402, 404, 3, 105, 52, 0, 403, 405, 3, 105, 52, 0, 404, 403, 1, 0, 0, 0, 404, 405, 1, 0, 0, 0, 405, 407, 1, 0, 0, 0, 406, 408, 3, 105, 52, 0, 407, 406, 1, 0, 0, 0, 407, 408, 1, 0, 0, 0, 408, 409, 1, 0, 0, 0, 409, 410, 5, 125, 0, 0, 410, 414, 1, 0, 0, 0, 411, 412, 5, 92, 0, 0, 412, 414, 3, 59, 29, 0, 413, 381, 1, 0, 0, 0, 413, 383, 1, 0, 0, 0, 413, 385, 1, 0, 0, 0, 413, 387, 1, 0, 0, 0, 413, 389, 1, 0, 0, 0, 413, 391, 1, 0, 0, 0, 413, 396, 1, 0, 0, 0, 413, 411, 1, 0, 0, 0, 414, 104, 1, 0, 0, 0, 415, 416, 7, 10, 0, 0, 416, 106, 1, 0, 0, 0, 39, 0, 138, 146, 223, 229, 233, 238, 244, 247, 250, 256, 259, 261, 265, 270, 276, 283, 286, 291, 294, 300, 302, 309, 311, 320, 326, 332, 336, 341, 349, 351, 358, 360, 364, 370, 377, 404, 407, 413, 1, 0, 1, 0] \ No newline at end of file +[4, 0, 49, 504, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 4, 7, 164, 8, 7, 11, 7, 12, 7, 165, 1, 7, 1, 7, 1, 8, 1, 8, 5, 8, 172, 8, 8, 10, 8, 12, 8, 175, 9, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 1, 21, 1, 21, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 4, 40, 289, 8, 40, 11, 40, 12, 40, 290, 3, 40, 293, 8, 40, 1, 40, 3, 40, 296, 8, 40, 3, 40, 298, 8, 40, 1, 41, 4, 41, 301, 8, 41, 11, 41, 12, 41, 302, 1, 41, 1, 41, 1, 41, 3, 41, 308, 8, 41, 1, 42, 3, 42, 311, 8, 42, 1, 42, 1, 42, 5, 42, 315, 8, 42, 10, 42, 12, 42, 318, 9, 42, 1, 43, 3, 43, 321, 8, 43, 1, 43, 4, 43, 324, 8, 43, 11, 43, 12, 43, 325, 1, 43, 1, 43, 4, 43, 330, 8, 43, 11, 43, 12, 43, 331, 1, 43, 3, 43, 335, 8, 43, 1, 43, 3, 43, 338, 8, 43, 1, 43, 1, 43, 4, 43, 342, 8, 43, 11, 43, 12, 43, 343, 1, 43, 3, 43, 347, 8, 43, 3, 43, 349, 8, 43, 1, 44, 1, 44, 3, 44, 353, 8, 44, 1, 44, 4, 44, 356, 8, 44, 11, 44, 12, 44, 357, 1, 45, 1, 45, 1, 46, 1, 46, 3, 46, 364, 8, 46, 1, 47, 1, 47, 1, 47, 5, 47, 369, 8, 47, 10, 47, 12, 47, 372, 9, 47, 3, 47, 374, 8, 47, 1, 48, 1, 48, 1, 48, 3, 48, 379, 8, 48, 1, 49, 3, 49, 382, 8, 49, 1, 49, 1, 49, 1, 49, 1, 49, 5, 49, 388, 8, 49, 10, 49, 12, 49, 391, 9, 49, 1, 50, 1, 50, 1, 50, 1, 50, 5, 50, 397, 8, 50, 10, 50, 12, 50, 400, 9, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 3, 52, 408, 8, 52, 1, 53, 1, 53, 4, 53, 412, 8, 53, 11, 53, 12, 53, 413, 1, 53, 1, 53, 4, 53, 418, 8, 53, 11, 53, 12, 53, 419, 5, 53, 422, 8, 53, 10, 53, 12, 53, 425, 9, 53, 1, 54, 1, 54, 3, 54, 429, 8, 54, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 5, 56, 437, 8, 56, 10, 56, 12, 56, 440, 9, 56, 1, 56, 1, 56, 1, 56, 1, 56, 5, 56, 446, 8, 56, 10, 56, 12, 56, 449, 9, 56, 1, 56, 3, 56, 452, 8, 56, 1, 57, 1, 57, 5, 57, 456, 8, 57, 10, 57, 12, 57, 459, 9, 57, 1, 57, 1, 57, 1, 58, 1, 58, 3, 58, 465, 8, 58, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 3, 60, 492, 8, 60, 1, 60, 3, 60, 495, 8, 60, 1, 60, 1, 60, 1, 60, 1, 60, 3, 60, 501, 8, 60, 1, 61, 1, 61, 1, 457, 0, 62, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, 51, 26, 53, 27, 55, 28, 57, 29, 59, 30, 61, 31, 63, 32, 65, 33, 67, 34, 69, 35, 71, 36, 73, 37, 75, 38, 77, 0, 79, 0, 81, 39, 83, 40, 85, 41, 87, 42, 89, 0, 91, 0, 93, 0, 95, 43, 97, 0, 99, 44, 101, 45, 103, 46, 105, 0, 107, 47, 109, 48, 111, 49, 113, 0, 115, 0, 117, 0, 119, 0, 121, 0, 123, 0, 1, 0, 12, 3, 0, 9, 10, 12, 13, 32, 32, 1, 0, 10, 10, 2, 0, 65, 90, 97, 122, 4, 0, 100, 100, 104, 104, 109, 109, 115, 115, 2, 0, 69, 69, 101, 101, 2, 0, 43, 43, 45, 45, 2, 0, 58, 58, 95, 95, 4, 0, 37, 37, 45, 46, 95, 95, 126, 126, 2, 0, 39, 39, 92, 92, 2, 0, 34, 34, 92, 92, 1, 0, 92, 92, 2, 0, 48, 57, 97, 102, 542, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 61, 1, 0, 0, 0, 0, 63, 1, 0, 0, 0, 0, 65, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 71, 1, 0, 0, 0, 0, 73, 1, 0, 0, 0, 0, 75, 1, 0, 0, 0, 0, 81, 1, 0, 0, 0, 0, 83, 1, 0, 0, 0, 0, 85, 1, 0, 0, 0, 0, 87, 1, 0, 0, 0, 0, 95, 1, 0, 0, 0, 0, 99, 1, 0, 0, 0, 0, 101, 1, 0, 0, 0, 0, 103, 1, 0, 0, 0, 0, 107, 1, 0, 0, 0, 0, 109, 1, 0, 0, 0, 0, 111, 1, 0, 0, 0, 1, 125, 1, 0, 0, 0, 3, 134, 1, 0, 0, 0, 5, 136, 1, 0, 0, 0, 7, 142, 1, 0, 0, 0, 9, 152, 1, 0, 0, 0, 11, 156, 1, 0, 0, 0, 13, 158, 1, 0, 0, 0, 15, 163, 1, 0, 0, 0, 17, 169, 1, 0, 0, 0, 19, 178, 1, 0, 0, 0, 21, 180, 1, 0, 0, 0, 23, 188, 1, 0, 0, 0, 25, 192, 1, 0, 0, 0, 27, 197, 1, 0, 0, 0, 29, 203, 1, 0, 0, 0, 31, 207, 1, 0, 0, 0, 33, 210, 1, 0, 0, 0, 35, 212, 1, 0, 0, 0, 37, 214, 1, 0, 0, 0, 39, 216, 1, 0, 0, 0, 41, 218, 1, 0, 0, 0, 43, 220, 1, 0, 0, 0, 45, 222, 1, 0, 0, 0, 47, 224, 1, 0, 0, 0, 49, 227, 1, 0, 0, 0, 51, 229, 1, 0, 0, 0, 53, 231, 1, 0, 0, 0, 55, 234, 1, 0, 0, 0, 57, 236, 1, 0, 0, 0, 59, 239, 1, 0, 0, 0, 61, 241, 1, 0, 0, 0, 63, 244, 1, 0, 0, 0, 65, 246, 1, 0, 0, 0, 67, 249, 1, 0, 0, 0, 69, 251, 1, 0, 0, 0, 71, 254, 1, 0, 0, 0, 73, 257, 1, 0, 0, 0, 75, 260, 1, 0, 0, 0, 77, 263, 1, 0, 0, 0, 79, 265, 1, 0, 0, 0, 81, 267, 1, 0, 0, 0, 83, 300, 1, 0, 0, 0, 85, 310, 1, 0, 0, 0, 87, 348, 1, 0, 0, 0, 89, 350, 1, 0, 0, 0, 91, 359, 1, 0, 0, 0, 93, 363, 1, 0, 0, 0, 95, 373, 1, 0, 0, 0, 97, 378, 1, 0, 0, 0, 99, 381, 1, 0, 0, 0, 101, 392, 1, 0, 0, 0, 103, 401, 1, 0, 0, 0, 105, 407, 1, 0, 0, 0, 107, 409, 1, 0, 0, 0, 109, 428, 1, 0, 0, 0, 111, 430, 1, 0, 0, 0, 113, 451, 1, 0, 0, 0, 115, 453, 1, 0, 0, 0, 117, 464, 1, 0, 0, 0, 119, 466, 1, 0, 0, 0, 121, 500, 1, 0, 0, 0, 123, 502, 1, 0, 0, 0, 125, 126, 5, 116, 0, 0, 126, 127, 5, 101, 0, 0, 127, 128, 5, 109, 0, 0, 128, 129, 5, 112, 0, 0, 129, 130, 5, 111, 0, 0, 130, 131, 5, 114, 0, 0, 131, 132, 5, 97, 0, 0, 132, 133, 5, 108, 0, 0, 133, 2, 1, 0, 0, 0, 134, 135, 5, 46, 0, 0, 135, 4, 1, 0, 0, 0, 136, 137, 5, 100, 0, 0, 137, 138, 5, 101, 0, 0, 138, 139, 5, 115, 0, 0, 139, 140, 5, 99, 0, 0, 140, 141, 5, 114, 0, 0, 141, 6, 1, 0, 0, 0, 142, 143, 5, 105, 0, 0, 143, 144, 5, 110, 0, 0, 144, 145, 5, 99, 0, 0, 145, 146, 5, 108, 0, 0, 146, 147, 5, 117, 0, 0, 147, 148, 5, 115, 0, 0, 148, 149, 5, 105, 0, 0, 149, 150, 5, 111, 0, 0, 150, 151, 5, 110, 0, 0, 151, 8, 1, 0, 0, 0, 152, 153, 5, 110, 0, 0, 153, 154, 5, 111, 0, 0, 154, 155, 5, 119, 0, 0, 155, 10, 1, 0, 0, 0, 156, 157, 5, 58, 0, 0, 157, 12, 1, 0, 0, 0, 158, 159, 5, 111, 0, 0, 159, 160, 5, 112, 0, 0, 160, 161, 5, 116, 0, 0, 161, 14, 1, 0, 0, 0, 162, 164, 7, 0, 0, 0, 163, 162, 1, 0, 0, 0, 164, 165, 1, 0, 0, 0, 165, 163, 1, 0, 0, 0, 165, 166, 1, 0, 0, 0, 166, 167, 1, 0, 0, 0, 167, 168, 6, 7, 0, 0, 168, 16, 1, 0, 0, 0, 169, 173, 5, 35, 0, 0, 170, 172, 8, 1, 0, 0, 171, 170, 1, 0, 0, 0, 172, 175, 1, 0, 0, 0, 173, 171, 1, 0, 0, 0, 173, 174, 1, 0, 0, 0, 174, 176, 1, 0, 0, 0, 175, 173, 1, 0, 0, 0, 176, 177, 6, 8, 0, 0, 177, 18, 1, 0, 0, 0, 178, 179, 5, 10232, 0, 0, 179, 20, 1, 0, 0, 0, 180, 181, 5, 80, 0, 0, 181, 182, 5, 97, 0, 0, 182, 183, 5, 99, 0, 0, 183, 184, 5, 107, 0, 0, 184, 185, 5, 97, 0, 0, 185, 186, 5, 103, 0, 0, 186, 187, 5, 101, 0, 0, 187, 22, 1, 0, 0, 0, 188, 189, 5, 85, 0, 0, 189, 190, 5, 115, 0, 0, 190, 191, 5, 101, 0, 0, 191, 24, 1, 0, 0, 0, 192, 193, 5, 68, 0, 0, 193, 194, 5, 101, 0, 0, 194, 195, 5, 99, 0, 0, 195, 196, 5, 108, 0, 0, 196, 26, 1, 0, 0, 0, 197, 198, 5, 98, 0, 0, 198, 199, 5, 111, 0, 0, 199, 200, 5, 117, 0, 0, 200, 201, 5, 110, 0, 0, 201, 202, 5, 100, 0, 0, 202, 28, 1, 0, 0, 0, 203, 204, 5, 108, 0, 0, 204, 205, 5, 101, 0, 0, 205, 206, 5, 116, 0, 0, 206, 30, 1, 0, 0, 0, 207, 208, 5, 100, 0, 0, 208, 209, 5, 111, 0, 0, 209, 32, 1, 0, 0, 0, 210, 211, 5, 40, 0, 0, 211, 34, 1, 0, 0, 0, 212, 213, 5, 41, 0, 0, 213, 36, 1, 0, 0, 0, 214, 215, 5, 91, 0, 0, 215, 38, 1, 0, 0, 0, 216, 217, 5, 93, 0, 0, 217, 40, 1, 0, 0, 0, 218, 219, 5, 123, 0, 0, 219, 42, 1, 0, 0, 0, 220, 221, 5, 125, 0, 0, 221, 44, 1, 0, 0, 0, 222, 223, 5, 61, 0, 0, 223, 46, 1, 0, 0, 0, 224, 225, 5, 33, 0, 0, 225, 226, 5, 61, 0, 0, 226, 48, 1, 0, 0, 0, 227, 228, 5, 44, 0, 0, 228, 50, 1, 0, 0, 0, 229, 230, 5, 33, 0, 0, 230, 52, 1, 0, 0, 0, 231, 232, 5, 60, 0, 0, 232, 233, 5, 61, 0, 0, 233, 54, 1, 0, 0, 0, 234, 235, 5, 60, 0, 0, 235, 56, 1, 0, 0, 0, 236, 237, 5, 62, 0, 0, 237, 238, 5, 61, 0, 0, 238, 58, 1, 0, 0, 0, 239, 240, 5, 62, 0, 0, 240, 60, 1, 0, 0, 0, 241, 242, 5, 58, 0, 0, 242, 243, 5, 45, 0, 0, 243, 62, 1, 0, 0, 0, 244, 245, 5, 10, 0, 0, 245, 64, 1, 0, 0, 0, 246, 247, 5, 124, 0, 0, 247, 248, 5, 62, 0, 0, 248, 66, 1, 0, 0, 0, 249, 250, 5, 64, 0, 0, 250, 68, 1, 0, 0, 0, 251, 252, 5, 60, 0, 0, 252, 253, 5, 45, 0, 0, 253, 70, 1, 0, 0, 0, 254, 255, 5, 60, 0, 0, 255, 256, 5, 43, 0, 0, 256, 72, 1, 0, 0, 0, 257, 258, 5, 91, 0, 0, 258, 259, 5, 45, 0, 0, 259, 74, 1, 0, 0, 0, 260, 261, 5, 91, 0, 0, 261, 262, 5, 43, 0, 0, 262, 76, 1, 0, 0, 0, 263, 264, 7, 2, 0, 0, 264, 78, 1, 0, 0, 0, 265, 266, 2, 48, 57, 0, 266, 80, 1, 0, 0, 0, 267, 268, 3, 79, 39, 0, 268, 269, 3, 79, 39, 0, 269, 270, 3, 79, 39, 0, 270, 271, 3, 79, 39, 0, 271, 272, 5, 45, 0, 0, 272, 273, 3, 79, 39, 0, 273, 274, 3, 79, 39, 0, 274, 275, 5, 45, 0, 0, 275, 276, 3, 79, 39, 0, 276, 297, 3, 79, 39, 0, 277, 278, 5, 84, 0, 0, 278, 279, 3, 79, 39, 0, 279, 280, 3, 79, 39, 0, 280, 281, 5, 58, 0, 0, 281, 282, 3, 79, 39, 0, 282, 283, 3, 79, 39, 0, 283, 284, 5, 58, 0, 0, 284, 285, 3, 79, 39, 0, 285, 292, 3, 79, 39, 0, 286, 288, 5, 46, 0, 0, 287, 289, 3, 79, 39, 0, 288, 287, 1, 0, 0, 0, 289, 290, 1, 0, 0, 0, 290, 288, 1, 0, 0, 0, 290, 291, 1, 0, 0, 0, 291, 293, 1, 0, 0, 0, 292, 286, 1, 0, 0, 0, 292, 293, 1, 0, 0, 0, 293, 295, 1, 0, 0, 0, 294, 296, 5, 90, 0, 0, 295, 294, 1, 0, 0, 0, 295, 296, 1, 0, 0, 0, 296, 298, 1, 0, 0, 0, 297, 277, 1, 0, 0, 0, 297, 298, 1, 0, 0, 0, 298, 82, 1, 0, 0, 0, 299, 301, 3, 79, 39, 0, 300, 299, 1, 0, 0, 0, 301, 302, 1, 0, 0, 0, 302, 300, 1, 0, 0, 0, 302, 303, 1, 0, 0, 0, 303, 307, 1, 0, 0, 0, 304, 308, 7, 3, 0, 0, 305, 306, 5, 109, 0, 0, 306, 308, 5, 115, 0, 0, 307, 304, 1, 0, 0, 0, 307, 305, 1, 0, 0, 0, 308, 84, 1, 0, 0, 0, 309, 311, 5, 45, 0, 0, 310, 309, 1, 0, 0, 0, 310, 311, 1, 0, 0, 0, 311, 312, 1, 0, 0, 0, 312, 316, 3, 79, 39, 0, 313, 315, 3, 79, 39, 0, 314, 313, 1, 0, 0, 0, 315, 318, 1, 0, 0, 0, 316, 314, 1, 0, 0, 0, 316, 317, 1, 0, 0, 0, 317, 86, 1, 0, 0, 0, 318, 316, 1, 0, 0, 0, 319, 321, 5, 45, 0, 0, 320, 319, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 323, 1, 0, 0, 0, 322, 324, 3, 79, 39, 0, 323, 322, 1, 0, 0, 0, 324, 325, 1, 0, 0, 0, 325, 323, 1, 0, 0, 0, 325, 326, 1, 0, 0, 0, 326, 327, 1, 0, 0, 0, 327, 329, 5, 46, 0, 0, 328, 330, 3, 79, 39, 0, 329, 328, 1, 0, 0, 0, 330, 331, 1, 0, 0, 0, 331, 329, 1, 0, 0, 0, 331, 332, 1, 0, 0, 0, 332, 334, 1, 0, 0, 0, 333, 335, 3, 89, 44, 0, 334, 333, 1, 0, 0, 0, 334, 335, 1, 0, 0, 0, 335, 349, 1, 0, 0, 0, 336, 338, 5, 45, 0, 0, 337, 336, 1, 0, 0, 0, 337, 338, 1, 0, 0, 0, 338, 339, 1, 0, 0, 0, 339, 341, 5, 46, 0, 0, 340, 342, 3, 79, 39, 0, 341, 340, 1, 0, 0, 0, 342, 343, 1, 0, 0, 0, 343, 341, 1, 0, 0, 0, 343, 344, 1, 0, 0, 0, 344, 346, 1, 0, 0, 0, 345, 347, 3, 89, 44, 0, 346, 345, 1, 0, 0, 0, 346, 347, 1, 0, 0, 0, 347, 349, 1, 0, 0, 0, 348, 320, 1, 0, 0, 0, 348, 337, 1, 0, 0, 0, 349, 88, 1, 0, 0, 0, 350, 352, 7, 4, 0, 0, 351, 353, 7, 5, 0, 0, 352, 351, 1, 0, 0, 0, 352, 353, 1, 0, 0, 0, 353, 355, 1, 0, 0, 0, 354, 356, 3, 79, 39, 0, 355, 354, 1, 0, 0, 0, 356, 357, 1, 0, 0, 0, 357, 355, 1, 0, 0, 0, 357, 358, 1, 0, 0, 0, 358, 90, 1, 0, 0, 0, 359, 360, 2, 65, 90, 0, 360, 92, 1, 0, 0, 0, 361, 364, 3, 77, 38, 0, 362, 364, 3, 79, 39, 0, 363, 361, 1, 0, 0, 0, 363, 362, 1, 0, 0, 0, 364, 94, 1, 0, 0, 0, 365, 374, 5, 95, 0, 0, 366, 370, 3, 91, 45, 0, 367, 369, 3, 93, 46, 0, 368, 367, 1, 0, 0, 0, 369, 372, 1, 0, 0, 0, 370, 368, 1, 0, 0, 0, 370, 371, 1, 0, 0, 0, 371, 374, 1, 0, 0, 0, 372, 370, 1, 0, 0, 0, 373, 365, 1, 0, 0, 0, 373, 366, 1, 0, 0, 0, 374, 96, 1, 0, 0, 0, 375, 379, 3, 77, 38, 0, 376, 379, 3, 79, 39, 0, 377, 379, 7, 6, 0, 0, 378, 375, 1, 0, 0, 0, 378, 376, 1, 0, 0, 0, 378, 377, 1, 0, 0, 0, 379, 98, 1, 0, 0, 0, 380, 382, 5, 58, 0, 0, 381, 380, 1, 0, 0, 0, 381, 382, 1, 0, 0, 0, 382, 383, 1, 0, 0, 0, 383, 389, 2, 97, 122, 0, 384, 388, 3, 97, 48, 0, 385, 386, 5, 46, 0, 0, 386, 388, 3, 97, 48, 0, 387, 384, 1, 0, 0, 0, 387, 385, 1, 0, 0, 0, 388, 391, 1, 0, 0, 0, 389, 387, 1, 0, 0, 0, 389, 390, 1, 0, 0, 0, 390, 100, 1, 0, 0, 0, 391, 389, 1, 0, 0, 0, 392, 398, 2, 65, 90, 0, 393, 397, 3, 97, 48, 0, 394, 395, 5, 46, 0, 0, 395, 397, 3, 97, 48, 0, 396, 393, 1, 0, 0, 0, 396, 394, 1, 0, 0, 0, 397, 400, 1, 0, 0, 0, 398, 396, 1, 0, 0, 0, 398, 399, 1, 0, 0, 0, 399, 102, 1, 0, 0, 0, 400, 398, 1, 0, 0, 0, 401, 402, 5, 46, 0, 0, 402, 403, 3, 101, 50, 0, 403, 104, 1, 0, 0, 0, 404, 408, 3, 77, 38, 0, 405, 408, 3, 79, 39, 0, 406, 408, 7, 7, 0, 0, 407, 404, 1, 0, 0, 0, 407, 405, 1, 0, 0, 0, 407, 406, 1, 0, 0, 0, 408, 106, 1, 0, 0, 0, 409, 411, 5, 47, 0, 0, 410, 412, 3, 105, 52, 0, 411, 410, 1, 0, 0, 0, 412, 413, 1, 0, 0, 0, 413, 411, 1, 0, 0, 0, 413, 414, 1, 0, 0, 0, 414, 423, 1, 0, 0, 0, 415, 417, 5, 47, 0, 0, 416, 418, 3, 105, 52, 0, 417, 416, 1, 0, 0, 0, 418, 419, 1, 0, 0, 0, 419, 417, 1, 0, 0, 0, 419, 420, 1, 0, 0, 0, 420, 422, 1, 0, 0, 0, 421, 415, 1, 0, 0, 0, 422, 425, 1, 0, 0, 0, 423, 421, 1, 0, 0, 0, 423, 424, 1, 0, 0, 0, 424, 108, 1, 0, 0, 0, 425, 423, 1, 0, 0, 0, 426, 429, 3, 113, 56, 0, 427, 429, 3, 115, 57, 0, 428, 426, 1, 0, 0, 0, 428, 427, 1, 0, 0, 0, 429, 110, 1, 0, 0, 0, 430, 431, 5, 98, 0, 0, 431, 432, 3, 109, 54, 0, 432, 112, 1, 0, 0, 0, 433, 438, 5, 39, 0, 0, 434, 437, 3, 121, 60, 0, 435, 437, 8, 8, 0, 0, 436, 434, 1, 0, 0, 0, 436, 435, 1, 0, 0, 0, 437, 440, 1, 0, 0, 0, 438, 436, 1, 0, 0, 0, 438, 439, 1, 0, 0, 0, 439, 441, 1, 0, 0, 0, 440, 438, 1, 0, 0, 0, 441, 452, 5, 39, 0, 0, 442, 447, 5, 34, 0, 0, 443, 446, 3, 121, 60, 0, 444, 446, 8, 9, 0, 0, 445, 443, 1, 0, 0, 0, 445, 444, 1, 0, 0, 0, 446, 449, 1, 0, 0, 0, 447, 445, 1, 0, 0, 0, 447, 448, 1, 0, 0, 0, 448, 450, 1, 0, 0, 0, 449, 447, 1, 0, 0, 0, 450, 452, 5, 34, 0, 0, 451, 433, 1, 0, 0, 0, 451, 442, 1, 0, 0, 0, 452, 114, 1, 0, 0, 0, 453, 457, 5, 96, 0, 0, 454, 456, 3, 117, 58, 0, 455, 454, 1, 0, 0, 0, 456, 459, 1, 0, 0, 0, 457, 458, 1, 0, 0, 0, 457, 455, 1, 0, 0, 0, 458, 460, 1, 0, 0, 0, 459, 457, 1, 0, 0, 0, 460, 461, 5, 96, 0, 0, 461, 116, 1, 0, 0, 0, 462, 465, 3, 119, 59, 0, 463, 465, 3, 121, 60, 0, 464, 462, 1, 0, 0, 0, 464, 463, 1, 0, 0, 0, 465, 118, 1, 0, 0, 0, 466, 467, 8, 10, 0, 0, 467, 120, 1, 0, 0, 0, 468, 469, 5, 92, 0, 0, 469, 501, 5, 110, 0, 0, 470, 471, 5, 92, 0, 0, 471, 501, 5, 116, 0, 0, 472, 473, 5, 92, 0, 0, 473, 501, 5, 34, 0, 0, 474, 475, 5, 92, 0, 0, 475, 501, 5, 39, 0, 0, 476, 477, 5, 92, 0, 0, 477, 501, 5, 92, 0, 0, 478, 479, 5, 92, 0, 0, 479, 480, 5, 120, 0, 0, 480, 481, 3, 123, 61, 0, 481, 482, 3, 123, 61, 0, 482, 501, 1, 0, 0, 0, 483, 484, 5, 92, 0, 0, 484, 485, 5, 117, 0, 0, 485, 486, 5, 123, 0, 0, 486, 487, 3, 123, 61, 0, 487, 488, 3, 123, 61, 0, 488, 489, 3, 123, 61, 0, 489, 491, 3, 123, 61, 0, 490, 492, 3, 123, 61, 0, 491, 490, 1, 0, 0, 0, 491, 492, 1, 0, 0, 0, 492, 494, 1, 0, 0, 0, 493, 495, 3, 123, 61, 0, 494, 493, 1, 0, 0, 0, 494, 495, 1, 0, 0, 0, 495, 496, 1, 0, 0, 0, 496, 497, 5, 125, 0, 0, 497, 501, 1, 0, 0, 0, 498, 499, 5, 92, 0, 0, 499, 501, 3, 63, 31, 0, 500, 468, 1, 0, 0, 0, 500, 470, 1, 0, 0, 0, 500, 472, 1, 0, 0, 0, 500, 474, 1, 0, 0, 0, 500, 476, 1, 0, 0, 0, 500, 478, 1, 0, 0, 0, 500, 483, 1, 0, 0, 0, 500, 498, 1, 0, 0, 0, 501, 122, 1, 0, 0, 0, 502, 503, 7, 11, 0, 0, 503, 124, 1, 0, 0, 0, 45, 0, 165, 173, 290, 292, 295, 297, 302, 307, 310, 316, 320, 325, 331, 334, 337, 343, 346, 348, 352, 357, 363, 370, 373, 378, 381, 387, 389, 396, 398, 407, 413, 419, 423, 428, 436, 438, 445, 447, 451, 457, 464, 491, 494, 500, 1, 0, 1, 0] \ No newline at end of file diff --git a/parse/gen/MangleLexer.tokens b/parse/gen/MangleLexer.tokens index 284f734..935aa18 100644 --- a/parse/gen/MangleLexer.tokens +++ b/parse/gen/MangleLexer.tokens @@ -18,34 +18,43 @@ LPAREN=17 RPAREN=18 LBRACKET=19 RBRACKET=20 -EQ=21 -BANGEQ=22 -COMMA=23 -BANG=24 -LESS=25 -LESSEQ=26 -GREATER=27 -GREATEREQ=28 -COLONDASH=29 -NEWLINE=30 -PIPEGREATER=31 -NUMBER=32 -FLOAT=33 -VARIABLE=34 -NAME=35 -TYPENAME=36 -DOT_TYPE=37 -CONSTANT=38 -STRING=39 -BYTESTRING=40 -'.'=1 -'descr'=2 -'inclusion'=3 -':'=4 -'{'=5 -'}'=6 +LBRACE=21 +RBRACE=22 +EQ=23 +BANGEQ=24 +COMMA=25 +BANG=26 +LESSEQ=27 +LESS=28 +GREATEREQ=29 +GREATER=30 +COLONDASH=31 +NEWLINE=32 +PIPEGREATER=33 +AT=34 +DIAMONDMINUS=35 +DIAMONDPLUS=36 +BOXMINUS=37 +BOXPLUS=38 +TIMESTAMP=39 +DURATION=40 +NUMBER=41 +FLOAT=42 +VARIABLE=43 +NAME=44 +TYPENAME=45 +DOT_TYPE=46 +CONSTANT=47 +STRING=48 +BYTESTRING=49 +'temporal'=1 +'.'=2 +'descr'=3 +'inclusion'=4 +'now'=5 +':'=6 'opt'=7 -'\u27F8'=10 +'⟸'=10 'Package'=11 'Use'=12 'Decl'=13 @@ -56,14 +65,21 @@ BYTESTRING=40 ')'=18 '['=19 ']'=20 -'='=21 -'!='=22 -','=23 -'!'=24 -'<'=25 -'<='=26 -'>'=27 -'>='=28 -':-'=29 -'\n'=30 -'|>'=31 +'{'=21 +'}'=22 +'='=23 +'!='=24 +','=25 +'!'=26 +'<='=27 +'<'=28 +'>='=29 +'>'=30 +':-'=31 +'\n'=32 +'|>'=33 +'@'=34 +'<-'=35 +'<+'=36 +'[-'=37 +'[+'=38 diff --git a/parse/gen/mangle_base_listener.go b/parse/gen/mangle_base_listener.go index ceafb33..afd364a 100644 --- a/parse/gen/mangle_base_listener.go +++ b/parse/gen/mangle_base_listener.go @@ -1,4 +1,4 @@ -// Code generated from parse/gen/Mangle.g4 by ANTLR 4.13.1. DO NOT EDIT. +// Code generated from parse/gen/Mangle.g4 by ANTLR 4.13.2. DO NOT EDIT. package gen // Mangle import "github.com/antlr4-go/antlr/v4" @@ -74,6 +74,18 @@ func (s *BaseMangleListener) EnterClause(ctx *ClauseContext) {} // ExitClause is called when production clause is exited. func (s *BaseMangleListener) ExitClause(ctx *ClauseContext) {} +// EnterTemporalAnnotation is called when production temporalAnnotation is entered. +func (s *BaseMangleListener) EnterTemporalAnnotation(ctx *TemporalAnnotationContext) {} + +// ExitTemporalAnnotation is called when production temporalAnnotation is exited. +func (s *BaseMangleListener) ExitTemporalAnnotation(ctx *TemporalAnnotationContext) {} + +// EnterTemporalBound is called when production temporalBound is entered. +func (s *BaseMangleListener) EnterTemporalBound(ctx *TemporalBoundContext) {} + +// ExitTemporalBound is called when production temporalBound is exited. +func (s *BaseMangleListener) ExitTemporalBound(ctx *TemporalBoundContext) {} + // EnterClauseBody is called when production clauseBody is entered. func (s *BaseMangleListener) EnterClauseBody(ctx *ClauseBodyContext) {} @@ -98,6 +110,12 @@ func (s *BaseMangleListener) EnterLiteralOrFml(ctx *LiteralOrFmlContext) {} // ExitLiteralOrFml is called when production literalOrFml is exited. func (s *BaseMangleListener) ExitLiteralOrFml(ctx *LiteralOrFmlContext) {} +// EnterTemporalOperator is called when production temporalOperator is entered. +func (s *BaseMangleListener) EnterTemporalOperator(ctx *TemporalOperatorContext) {} + +// ExitTemporalOperator is called when production temporalOperator is exited. +func (s *BaseMangleListener) ExitTemporalOperator(ctx *TemporalOperatorContext) {} + // EnterVar is called when production Var is entered. func (s *BaseMangleListener) EnterVar(ctx *VarContext) {} diff --git a/parse/gen/mangle_base_visitor.go b/parse/gen/mangle_base_visitor.go index 1fdd322..3262dc7 100644 --- a/parse/gen/mangle_base_visitor.go +++ b/parse/gen/mangle_base_visitor.go @@ -1,4 +1,4 @@ -// Code generated from parse/gen/Mangle.g4 by ANTLR 4.13.1. DO NOT EDIT. +// Code generated from parse/gen/Mangle.g4 by ANTLR 4.13.2. DO NOT EDIT. package gen // Mangle import "github.com/antlr4-go/antlr/v4" @@ -43,6 +43,14 @@ func (v *BaseMangleVisitor) VisitClause(ctx *ClauseContext) interface{} { return v.VisitChildren(ctx) } +func (v *BaseMangleVisitor) VisitTemporalAnnotation(ctx *TemporalAnnotationContext) interface{} { + return v.VisitChildren(ctx) +} + +func (v *BaseMangleVisitor) VisitTemporalBound(ctx *TemporalBoundContext) interface{} { + return v.VisitChildren(ctx) +} + func (v *BaseMangleVisitor) VisitClauseBody(ctx *ClauseBodyContext) interface{} { return v.VisitChildren(ctx) } @@ -59,6 +67,10 @@ func (v *BaseMangleVisitor) VisitLiteralOrFml(ctx *LiteralOrFmlContext) interfac return v.VisitChildren(ctx) } +func (v *BaseMangleVisitor) VisitTemporalOperator(ctx *TemporalOperatorContext) interface{} { + return v.VisitChildren(ctx) +} + func (v *BaseMangleVisitor) VisitVar(ctx *VarContext) interface{} { return v.VisitChildren(ctx) } diff --git a/parse/gen/mangle_lexer.go b/parse/gen/mangle_lexer.go index d43bc33..df42acf 100644 --- a/parse/gen/mangle_lexer.go +++ b/parse/gen/mangle_lexer.go @@ -1,4 +1,4 @@ -// Code generated from parse/gen/Mangle.g4 by ANTLR 4.13.1. DO NOT EDIT. +// Code generated from parse/gen/Mangle.g4 by ANTLR 4.13.2. DO NOT EDIT. package gen @@ -43,33 +43,36 @@ func manglelexerLexerInit() { "DEFAULT_MODE", } staticData.LiteralNames = []string{ - "", "'.'", "'descr'", "'inclusion'", "':'", "'{'", "'}'", "'opt'", "", - "", "'\\u27F8'", "'Package'", "'Use'", "'Decl'", "'bound'", "'let'", - "'do'", "'('", "')'", "'['", "']'", "'='", "'!='", "','", "'!'", "'<'", - "'<='", "'>'", "'>='", "':-'", "'\\n'", "'|>'", + "", "'temporal'", "'.'", "'descr'", "'inclusion'", "'now'", "':'", "'opt'", + "", "", "'\\u27F8'", "'Package'", "'Use'", "'Decl'", "'bound'", "'let'", + "'do'", "'('", "')'", "'['", "']'", "'{'", "'}'", "'='", "'!='", "','", + "'!'", "'<='", "'<'", "'>='", "'>'", "':-'", "'\\n'", "'|>'", "'@'", + "'<-'", "'<+'", "'[-'", "'[+'", } staticData.SymbolicNames = []string{ "", "", "", "", "", "", "", "", "WHITESPACE", "COMMENT", "LONGLEFTDOUBLEARROW", "PACKAGE", "USE", "DECL", "BOUND", "LET", "DO", "LPAREN", "RPAREN", - "LBRACKET", "RBRACKET", "EQ", "BANGEQ", "COMMA", "BANG", "LESS", "LESSEQ", - "GREATER", "GREATEREQ", "COLONDASH", "NEWLINE", "PIPEGREATER", "NUMBER", - "FLOAT", "VARIABLE", "NAME", "TYPENAME", "DOT_TYPE", "CONSTANT", "STRING", - "BYTESTRING", + "LBRACKET", "RBRACKET", "LBRACE", "RBRACE", "EQ", "BANGEQ", "COMMA", + "BANG", "LESSEQ", "LESS", "GREATEREQ", "GREATER", "COLONDASH", "NEWLINE", + "PIPEGREATER", "AT", "DIAMONDMINUS", "DIAMONDPLUS", "BOXMINUS", "BOXPLUS", + "TIMESTAMP", "DURATION", "NUMBER", "FLOAT", "VARIABLE", "NAME", "TYPENAME", + "DOT_TYPE", "CONSTANT", "STRING", "BYTESTRING", } staticData.RuleNames = []string{ "T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6", "WHITESPACE", "COMMENT", "LONGLEFTDOUBLEARROW", "PACKAGE", "USE", "DECL", "BOUND", - "LET", "DO", "LPAREN", "RPAREN", "LBRACKET", "RBRACKET", "EQ", "BANGEQ", - "COMMA", "BANG", "LESS", "LESSEQ", "GREATER", "GREATEREQ", "COLONDASH", - "NEWLINE", "PIPEGREATER", "LETTER", "DIGIT", "NUMBER", "FLOAT", "EXPONENT", - "VARIABLE_START", "VARIABLE_CHAR", "VARIABLE", "NAME_CHAR", "NAME", - "TYPENAME", "DOT_TYPE", "CONSTANT_CHAR", "CONSTANT", "STRING", "BYTESTRING", - "SHORT_STRING", "LONG_STRING", "LONG_STRING_ITEM", "LONG_STRING_CHAR", - "STRING_ESCAPE_SEQ", "HEXDIGIT", + "LET", "DO", "LPAREN", "RPAREN", "LBRACKET", "RBRACKET", "LBRACE", "RBRACE", + "EQ", "BANGEQ", "COMMA", "BANG", "LESSEQ", "LESS", "GREATEREQ", "GREATER", + "COLONDASH", "NEWLINE", "PIPEGREATER", "AT", "DIAMONDMINUS", "DIAMONDPLUS", + "BOXMINUS", "BOXPLUS", "LETTER", "DIGIT", "TIMESTAMP", "DURATION", "NUMBER", + "FLOAT", "EXPONENT", "VARIABLE_START", "VARIABLE_CHAR", "VARIABLE", + "NAME_CHAR", "NAME", "TYPENAME", "DOT_TYPE", "CONSTANT_CHAR", "CONSTANT", + "STRING", "BYTESTRING", "SHORT_STRING", "LONG_STRING", "LONG_STRING_ITEM", + "LONG_STRING_CHAR", "STRING_ESCAPE_SEQ", "HEXDIGIT", } staticData.PredictionContextCache = antlr.NewPredictionContextCache() staticData.serializedATN = []int32{ - 4, 0, 40, 417, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, + 4, 0, 49, 504, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, @@ -79,189 +82,229 @@ func manglelexerLexerInit() { 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, - 52, 7, 52, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, - 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 4, 7, 137, 8, 7, 11, 7, 12, 7, - 138, 1, 7, 1, 7, 1, 8, 1, 8, 5, 8, 145, 8, 8, 10, 8, 12, 8, 148, 9, 8, - 1, 8, 1, 8, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, - 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, - 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, - 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, 1, 18, 1, 18, 1, 19, 1, 19, 1, - 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 23, 1, 23, 1, 24, 1, 24, - 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, - 28, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 32, 1, 32, 1, 33, - 3, 33, 224, 8, 33, 1, 33, 1, 33, 5, 33, 228, 8, 33, 10, 33, 12, 33, 231, - 9, 33, 1, 34, 3, 34, 234, 8, 34, 1, 34, 4, 34, 237, 8, 34, 11, 34, 12, - 34, 238, 1, 34, 1, 34, 4, 34, 243, 8, 34, 11, 34, 12, 34, 244, 1, 34, 3, - 34, 248, 8, 34, 1, 34, 3, 34, 251, 8, 34, 1, 34, 1, 34, 4, 34, 255, 8, - 34, 11, 34, 12, 34, 256, 1, 34, 3, 34, 260, 8, 34, 3, 34, 262, 8, 34, 1, - 35, 1, 35, 3, 35, 266, 8, 35, 1, 35, 4, 35, 269, 8, 35, 11, 35, 12, 35, - 270, 1, 36, 1, 36, 1, 37, 1, 37, 3, 37, 277, 8, 37, 1, 38, 1, 38, 1, 38, - 5, 38, 282, 8, 38, 10, 38, 12, 38, 285, 9, 38, 3, 38, 287, 8, 38, 1, 39, - 1, 39, 1, 39, 3, 39, 292, 8, 39, 1, 40, 3, 40, 295, 8, 40, 1, 40, 1, 40, - 1, 40, 1, 40, 5, 40, 301, 8, 40, 10, 40, 12, 40, 304, 9, 40, 1, 41, 1, - 41, 1, 41, 1, 41, 5, 41, 310, 8, 41, 10, 41, 12, 41, 313, 9, 41, 1, 42, - 1, 42, 1, 42, 1, 43, 1, 43, 1, 43, 3, 43, 321, 8, 43, 1, 44, 1, 44, 4, - 44, 325, 8, 44, 11, 44, 12, 44, 326, 1, 44, 1, 44, 4, 44, 331, 8, 44, 11, - 44, 12, 44, 332, 5, 44, 335, 8, 44, 10, 44, 12, 44, 338, 9, 44, 1, 45, - 1, 45, 3, 45, 342, 8, 45, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 5, - 47, 350, 8, 47, 10, 47, 12, 47, 353, 9, 47, 1, 47, 1, 47, 1, 47, 1, 47, - 5, 47, 359, 8, 47, 10, 47, 12, 47, 362, 9, 47, 1, 47, 3, 47, 365, 8, 47, - 1, 48, 1, 48, 5, 48, 369, 8, 48, 10, 48, 12, 48, 372, 9, 48, 1, 48, 1, - 48, 1, 49, 1, 49, 3, 49, 378, 8, 49, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, - 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, - 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 3, 51, - 405, 8, 51, 1, 51, 3, 51, 408, 8, 51, 1, 51, 1, 51, 1, 51, 1, 51, 3, 51, - 414, 8, 51, 1, 52, 1, 52, 1, 370, 0, 53, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, - 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, - 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, - 24, 49, 25, 51, 26, 53, 27, 55, 28, 57, 29, 59, 30, 61, 31, 63, 0, 65, - 0, 67, 32, 69, 33, 71, 0, 73, 0, 75, 0, 77, 34, 79, 0, 81, 35, 83, 36, - 85, 37, 87, 0, 89, 38, 91, 39, 93, 40, 95, 0, 97, 0, 99, 0, 101, 0, 103, - 0, 105, 0, 1, 0, 11, 3, 0, 9, 10, 12, 13, 32, 32, 1, 0, 10, 10, 2, 0, 65, - 90, 97, 122, 2, 0, 69, 69, 101, 101, 2, 0, 43, 43, 45, 45, 2, 0, 58, 58, - 95, 95, 4, 0, 37, 37, 45, 46, 95, 95, 126, 126, 2, 0, 39, 39, 92, 92, 2, - 0, 34, 34, 92, 92, 1, 0, 92, 92, 2, 0, 48, 57, 97, 102, 449, 0, 1, 1, 0, - 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, - 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, - 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, - 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, - 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, - 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, - 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, - 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 61, 1, 0, 0, 0, 0, 67, 1, - 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 77, 1, 0, 0, 0, 0, 81, 1, 0, 0, 0, 0, 83, - 1, 0, 0, 0, 0, 85, 1, 0, 0, 0, 0, 89, 1, 0, 0, 0, 0, 91, 1, 0, 0, 0, 0, - 93, 1, 0, 0, 0, 1, 107, 1, 0, 0, 0, 3, 109, 1, 0, 0, 0, 5, 115, 1, 0, 0, - 0, 7, 125, 1, 0, 0, 0, 9, 127, 1, 0, 0, 0, 11, 129, 1, 0, 0, 0, 13, 131, - 1, 0, 0, 0, 15, 136, 1, 0, 0, 0, 17, 142, 1, 0, 0, 0, 19, 151, 1, 0, 0, - 0, 21, 153, 1, 0, 0, 0, 23, 161, 1, 0, 0, 0, 25, 165, 1, 0, 0, 0, 27, 170, - 1, 0, 0, 0, 29, 176, 1, 0, 0, 0, 31, 180, 1, 0, 0, 0, 33, 183, 1, 0, 0, - 0, 35, 185, 1, 0, 0, 0, 37, 187, 1, 0, 0, 0, 39, 189, 1, 0, 0, 0, 41, 191, - 1, 0, 0, 0, 43, 193, 1, 0, 0, 0, 45, 196, 1, 0, 0, 0, 47, 198, 1, 0, 0, - 0, 49, 200, 1, 0, 0, 0, 51, 202, 1, 0, 0, 0, 53, 205, 1, 0, 0, 0, 55, 207, - 1, 0, 0, 0, 57, 210, 1, 0, 0, 0, 59, 213, 1, 0, 0, 0, 61, 215, 1, 0, 0, - 0, 63, 218, 1, 0, 0, 0, 65, 220, 1, 0, 0, 0, 67, 223, 1, 0, 0, 0, 69, 261, - 1, 0, 0, 0, 71, 263, 1, 0, 0, 0, 73, 272, 1, 0, 0, 0, 75, 276, 1, 0, 0, - 0, 77, 286, 1, 0, 0, 0, 79, 291, 1, 0, 0, 0, 81, 294, 1, 0, 0, 0, 83, 305, - 1, 0, 0, 0, 85, 314, 1, 0, 0, 0, 87, 320, 1, 0, 0, 0, 89, 322, 1, 0, 0, - 0, 91, 341, 1, 0, 0, 0, 93, 343, 1, 0, 0, 0, 95, 364, 1, 0, 0, 0, 97, 366, - 1, 0, 0, 0, 99, 377, 1, 0, 0, 0, 101, 379, 1, 0, 0, 0, 103, 413, 1, 0, - 0, 0, 105, 415, 1, 0, 0, 0, 107, 108, 5, 46, 0, 0, 108, 2, 1, 0, 0, 0, - 109, 110, 5, 100, 0, 0, 110, 111, 5, 101, 0, 0, 111, 112, 5, 115, 0, 0, - 112, 113, 5, 99, 0, 0, 113, 114, 5, 114, 0, 0, 114, 4, 1, 0, 0, 0, 115, - 116, 5, 105, 0, 0, 116, 117, 5, 110, 0, 0, 117, 118, 5, 99, 0, 0, 118, - 119, 5, 108, 0, 0, 119, 120, 5, 117, 0, 0, 120, 121, 5, 115, 0, 0, 121, - 122, 5, 105, 0, 0, 122, 123, 5, 111, 0, 0, 123, 124, 5, 110, 0, 0, 124, - 6, 1, 0, 0, 0, 125, 126, 5, 58, 0, 0, 126, 8, 1, 0, 0, 0, 127, 128, 5, - 123, 0, 0, 128, 10, 1, 0, 0, 0, 129, 130, 5, 125, 0, 0, 130, 12, 1, 0, - 0, 0, 131, 132, 5, 111, 0, 0, 132, 133, 5, 112, 0, 0, 133, 134, 5, 116, - 0, 0, 134, 14, 1, 0, 0, 0, 135, 137, 7, 0, 0, 0, 136, 135, 1, 0, 0, 0, - 137, 138, 1, 0, 0, 0, 138, 136, 1, 0, 0, 0, 138, 139, 1, 0, 0, 0, 139, - 140, 1, 0, 0, 0, 140, 141, 6, 7, 0, 0, 141, 16, 1, 0, 0, 0, 142, 146, 5, - 35, 0, 0, 143, 145, 8, 1, 0, 0, 144, 143, 1, 0, 0, 0, 145, 148, 1, 0, 0, - 0, 146, 144, 1, 0, 0, 0, 146, 147, 1, 0, 0, 0, 147, 149, 1, 0, 0, 0, 148, - 146, 1, 0, 0, 0, 149, 150, 6, 8, 0, 0, 150, 18, 1, 0, 0, 0, 151, 152, 5, - 10232, 0, 0, 152, 20, 1, 0, 0, 0, 153, 154, 5, 80, 0, 0, 154, 155, 5, 97, - 0, 0, 155, 156, 5, 99, 0, 0, 156, 157, 5, 107, 0, 0, 157, 158, 5, 97, 0, - 0, 158, 159, 5, 103, 0, 0, 159, 160, 5, 101, 0, 0, 160, 22, 1, 0, 0, 0, - 161, 162, 5, 85, 0, 0, 162, 163, 5, 115, 0, 0, 163, 164, 5, 101, 0, 0, - 164, 24, 1, 0, 0, 0, 165, 166, 5, 68, 0, 0, 166, 167, 5, 101, 0, 0, 167, - 168, 5, 99, 0, 0, 168, 169, 5, 108, 0, 0, 169, 26, 1, 0, 0, 0, 170, 171, - 5, 98, 0, 0, 171, 172, 5, 111, 0, 0, 172, 173, 5, 117, 0, 0, 173, 174, - 5, 110, 0, 0, 174, 175, 5, 100, 0, 0, 175, 28, 1, 0, 0, 0, 176, 177, 5, - 108, 0, 0, 177, 178, 5, 101, 0, 0, 178, 179, 5, 116, 0, 0, 179, 30, 1, - 0, 0, 0, 180, 181, 5, 100, 0, 0, 181, 182, 5, 111, 0, 0, 182, 32, 1, 0, - 0, 0, 183, 184, 5, 40, 0, 0, 184, 34, 1, 0, 0, 0, 185, 186, 5, 41, 0, 0, - 186, 36, 1, 0, 0, 0, 187, 188, 5, 91, 0, 0, 188, 38, 1, 0, 0, 0, 189, 190, - 5, 93, 0, 0, 190, 40, 1, 0, 0, 0, 191, 192, 5, 61, 0, 0, 192, 42, 1, 0, - 0, 0, 193, 194, 5, 33, 0, 0, 194, 195, 5, 61, 0, 0, 195, 44, 1, 0, 0, 0, - 196, 197, 5, 44, 0, 0, 197, 46, 1, 0, 0, 0, 198, 199, 5, 33, 0, 0, 199, - 48, 1, 0, 0, 0, 200, 201, 5, 60, 0, 0, 201, 50, 1, 0, 0, 0, 202, 203, 5, - 60, 0, 0, 203, 204, 5, 61, 0, 0, 204, 52, 1, 0, 0, 0, 205, 206, 5, 62, - 0, 0, 206, 54, 1, 0, 0, 0, 207, 208, 5, 62, 0, 0, 208, 209, 5, 61, 0, 0, - 209, 56, 1, 0, 0, 0, 210, 211, 5, 58, 0, 0, 211, 212, 5, 45, 0, 0, 212, - 58, 1, 0, 0, 0, 213, 214, 5, 10, 0, 0, 214, 60, 1, 0, 0, 0, 215, 216, 5, - 124, 0, 0, 216, 217, 5, 62, 0, 0, 217, 62, 1, 0, 0, 0, 218, 219, 7, 2, - 0, 0, 219, 64, 1, 0, 0, 0, 220, 221, 2, 48, 57, 0, 221, 66, 1, 0, 0, 0, - 222, 224, 5, 45, 0, 0, 223, 222, 1, 0, 0, 0, 223, 224, 1, 0, 0, 0, 224, - 225, 1, 0, 0, 0, 225, 229, 3, 65, 32, 0, 226, 228, 3, 65, 32, 0, 227, 226, - 1, 0, 0, 0, 228, 231, 1, 0, 0, 0, 229, 227, 1, 0, 0, 0, 229, 230, 1, 0, - 0, 0, 230, 68, 1, 0, 0, 0, 231, 229, 1, 0, 0, 0, 232, 234, 5, 45, 0, 0, - 233, 232, 1, 0, 0, 0, 233, 234, 1, 0, 0, 0, 234, 236, 1, 0, 0, 0, 235, - 237, 3, 65, 32, 0, 236, 235, 1, 0, 0, 0, 237, 238, 1, 0, 0, 0, 238, 236, - 1, 0, 0, 0, 238, 239, 1, 0, 0, 0, 239, 240, 1, 0, 0, 0, 240, 242, 5, 46, - 0, 0, 241, 243, 3, 65, 32, 0, 242, 241, 1, 0, 0, 0, 243, 244, 1, 0, 0, - 0, 244, 242, 1, 0, 0, 0, 244, 245, 1, 0, 0, 0, 245, 247, 1, 0, 0, 0, 246, - 248, 3, 71, 35, 0, 247, 246, 1, 0, 0, 0, 247, 248, 1, 0, 0, 0, 248, 262, - 1, 0, 0, 0, 249, 251, 5, 45, 0, 0, 250, 249, 1, 0, 0, 0, 250, 251, 1, 0, - 0, 0, 251, 252, 1, 0, 0, 0, 252, 254, 5, 46, 0, 0, 253, 255, 3, 65, 32, - 0, 254, 253, 1, 0, 0, 0, 255, 256, 1, 0, 0, 0, 256, 254, 1, 0, 0, 0, 256, - 257, 1, 0, 0, 0, 257, 259, 1, 0, 0, 0, 258, 260, 3, 71, 35, 0, 259, 258, - 1, 0, 0, 0, 259, 260, 1, 0, 0, 0, 260, 262, 1, 0, 0, 0, 261, 233, 1, 0, - 0, 0, 261, 250, 1, 0, 0, 0, 262, 70, 1, 0, 0, 0, 263, 265, 7, 3, 0, 0, - 264, 266, 7, 4, 0, 0, 265, 264, 1, 0, 0, 0, 265, 266, 1, 0, 0, 0, 266, - 268, 1, 0, 0, 0, 267, 269, 3, 65, 32, 0, 268, 267, 1, 0, 0, 0, 269, 270, - 1, 0, 0, 0, 270, 268, 1, 0, 0, 0, 270, 271, 1, 0, 0, 0, 271, 72, 1, 0, - 0, 0, 272, 273, 2, 65, 90, 0, 273, 74, 1, 0, 0, 0, 274, 277, 3, 63, 31, - 0, 275, 277, 3, 65, 32, 0, 276, 274, 1, 0, 0, 0, 276, 275, 1, 0, 0, 0, - 277, 76, 1, 0, 0, 0, 278, 287, 5, 95, 0, 0, 279, 283, 3, 73, 36, 0, 280, - 282, 3, 75, 37, 0, 281, 280, 1, 0, 0, 0, 282, 285, 1, 0, 0, 0, 283, 281, - 1, 0, 0, 0, 283, 284, 1, 0, 0, 0, 284, 287, 1, 0, 0, 0, 285, 283, 1, 0, - 0, 0, 286, 278, 1, 0, 0, 0, 286, 279, 1, 0, 0, 0, 287, 78, 1, 0, 0, 0, - 288, 292, 3, 63, 31, 0, 289, 292, 3, 65, 32, 0, 290, 292, 7, 5, 0, 0, 291, - 288, 1, 0, 0, 0, 291, 289, 1, 0, 0, 0, 291, 290, 1, 0, 0, 0, 292, 80, 1, - 0, 0, 0, 293, 295, 5, 58, 0, 0, 294, 293, 1, 0, 0, 0, 294, 295, 1, 0, 0, - 0, 295, 296, 1, 0, 0, 0, 296, 302, 2, 97, 122, 0, 297, 301, 3, 79, 39, - 0, 298, 299, 5, 46, 0, 0, 299, 301, 3, 79, 39, 0, 300, 297, 1, 0, 0, 0, - 300, 298, 1, 0, 0, 0, 301, 304, 1, 0, 0, 0, 302, 300, 1, 0, 0, 0, 302, - 303, 1, 0, 0, 0, 303, 82, 1, 0, 0, 0, 304, 302, 1, 0, 0, 0, 305, 311, 2, - 65, 90, 0, 306, 310, 3, 79, 39, 0, 307, 308, 5, 46, 0, 0, 308, 310, 3, - 79, 39, 0, 309, 306, 1, 0, 0, 0, 309, 307, 1, 0, 0, 0, 310, 313, 1, 0, - 0, 0, 311, 309, 1, 0, 0, 0, 311, 312, 1, 0, 0, 0, 312, 84, 1, 0, 0, 0, - 313, 311, 1, 0, 0, 0, 314, 315, 5, 46, 0, 0, 315, 316, 3, 83, 41, 0, 316, - 86, 1, 0, 0, 0, 317, 321, 3, 63, 31, 0, 318, 321, 3, 65, 32, 0, 319, 321, - 7, 6, 0, 0, 320, 317, 1, 0, 0, 0, 320, 318, 1, 0, 0, 0, 320, 319, 1, 0, - 0, 0, 321, 88, 1, 0, 0, 0, 322, 324, 5, 47, 0, 0, 323, 325, 3, 87, 43, - 0, 324, 323, 1, 0, 0, 0, 325, 326, 1, 0, 0, 0, 326, 324, 1, 0, 0, 0, 326, - 327, 1, 0, 0, 0, 327, 336, 1, 0, 0, 0, 328, 330, 5, 47, 0, 0, 329, 331, - 3, 87, 43, 0, 330, 329, 1, 0, 0, 0, 331, 332, 1, 0, 0, 0, 332, 330, 1, - 0, 0, 0, 332, 333, 1, 0, 0, 0, 333, 335, 1, 0, 0, 0, 334, 328, 1, 0, 0, - 0, 335, 338, 1, 0, 0, 0, 336, 334, 1, 0, 0, 0, 336, 337, 1, 0, 0, 0, 337, - 90, 1, 0, 0, 0, 338, 336, 1, 0, 0, 0, 339, 342, 3, 95, 47, 0, 340, 342, - 3, 97, 48, 0, 341, 339, 1, 0, 0, 0, 341, 340, 1, 0, 0, 0, 342, 92, 1, 0, - 0, 0, 343, 344, 5, 98, 0, 0, 344, 345, 3, 91, 45, 0, 345, 94, 1, 0, 0, - 0, 346, 351, 5, 39, 0, 0, 347, 350, 3, 103, 51, 0, 348, 350, 8, 7, 0, 0, - 349, 347, 1, 0, 0, 0, 349, 348, 1, 0, 0, 0, 350, 353, 1, 0, 0, 0, 351, - 349, 1, 0, 0, 0, 351, 352, 1, 0, 0, 0, 352, 354, 1, 0, 0, 0, 353, 351, - 1, 0, 0, 0, 354, 365, 5, 39, 0, 0, 355, 360, 5, 34, 0, 0, 356, 359, 3, - 103, 51, 0, 357, 359, 8, 8, 0, 0, 358, 356, 1, 0, 0, 0, 358, 357, 1, 0, - 0, 0, 359, 362, 1, 0, 0, 0, 360, 358, 1, 0, 0, 0, 360, 361, 1, 0, 0, 0, - 361, 363, 1, 0, 0, 0, 362, 360, 1, 0, 0, 0, 363, 365, 5, 34, 0, 0, 364, - 346, 1, 0, 0, 0, 364, 355, 1, 0, 0, 0, 365, 96, 1, 0, 0, 0, 366, 370, 5, - 96, 0, 0, 367, 369, 3, 99, 49, 0, 368, 367, 1, 0, 0, 0, 369, 372, 1, 0, - 0, 0, 370, 371, 1, 0, 0, 0, 370, 368, 1, 0, 0, 0, 371, 373, 1, 0, 0, 0, - 372, 370, 1, 0, 0, 0, 373, 374, 5, 96, 0, 0, 374, 98, 1, 0, 0, 0, 375, - 378, 3, 101, 50, 0, 376, 378, 3, 103, 51, 0, 377, 375, 1, 0, 0, 0, 377, - 376, 1, 0, 0, 0, 378, 100, 1, 0, 0, 0, 379, 380, 8, 9, 0, 0, 380, 102, - 1, 0, 0, 0, 381, 382, 5, 92, 0, 0, 382, 414, 5, 110, 0, 0, 383, 384, 5, - 92, 0, 0, 384, 414, 5, 116, 0, 0, 385, 386, 5, 92, 0, 0, 386, 414, 5, 34, - 0, 0, 387, 388, 5, 92, 0, 0, 388, 414, 5, 39, 0, 0, 389, 390, 5, 92, 0, - 0, 390, 414, 5, 92, 0, 0, 391, 392, 5, 92, 0, 0, 392, 393, 5, 120, 0, 0, - 393, 394, 3, 105, 52, 0, 394, 395, 3, 105, 52, 0, 395, 414, 1, 0, 0, 0, - 396, 397, 5, 92, 0, 0, 397, 398, 5, 117, 0, 0, 398, 399, 5, 123, 0, 0, - 399, 400, 3, 105, 52, 0, 400, 401, 3, 105, 52, 0, 401, 402, 3, 105, 52, - 0, 402, 404, 3, 105, 52, 0, 403, 405, 3, 105, 52, 0, 404, 403, 1, 0, 0, - 0, 404, 405, 1, 0, 0, 0, 405, 407, 1, 0, 0, 0, 406, 408, 3, 105, 52, 0, - 407, 406, 1, 0, 0, 0, 407, 408, 1, 0, 0, 0, 408, 409, 1, 0, 0, 0, 409, - 410, 5, 125, 0, 0, 410, 414, 1, 0, 0, 0, 411, 412, 5, 92, 0, 0, 412, 414, - 3, 59, 29, 0, 413, 381, 1, 0, 0, 0, 413, 383, 1, 0, 0, 0, 413, 385, 1, - 0, 0, 0, 413, 387, 1, 0, 0, 0, 413, 389, 1, 0, 0, 0, 413, 391, 1, 0, 0, - 0, 413, 396, 1, 0, 0, 0, 413, 411, 1, 0, 0, 0, 414, 104, 1, 0, 0, 0, 415, - 416, 7, 10, 0, 0, 416, 106, 1, 0, 0, 0, 39, 0, 138, 146, 223, 229, 233, - 238, 244, 247, 250, 256, 259, 261, 265, 270, 276, 283, 286, 291, 294, 300, - 302, 309, 311, 320, 326, 332, 336, 341, 349, 351, 358, 360, 364, 370, 377, - 404, 407, 413, 1, 0, 1, 0, + 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, + 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, + 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, + 7, 4, 7, 164, 8, 7, 11, 7, 12, 7, 165, 1, 7, 1, 7, 1, 8, 1, 8, 5, 8, 172, + 8, 8, 10, 8, 12, 8, 175, 9, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 10, 1, 10, 1, + 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, + 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, + 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 17, 1, 17, + 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 1, 21, 1, 21, 1, 22, 1, 22, 1, + 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 27, + 1, 27, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 31, 1, + 31, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, + 1, 35, 1, 36, 1, 36, 1, 36, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 39, 1, + 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, + 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, + 40, 4, 40, 289, 8, 40, 11, 40, 12, 40, 290, 3, 40, 293, 8, 40, 1, 40, 3, + 40, 296, 8, 40, 3, 40, 298, 8, 40, 1, 41, 4, 41, 301, 8, 41, 11, 41, 12, + 41, 302, 1, 41, 1, 41, 1, 41, 3, 41, 308, 8, 41, 1, 42, 3, 42, 311, 8, + 42, 1, 42, 1, 42, 5, 42, 315, 8, 42, 10, 42, 12, 42, 318, 9, 42, 1, 43, + 3, 43, 321, 8, 43, 1, 43, 4, 43, 324, 8, 43, 11, 43, 12, 43, 325, 1, 43, + 1, 43, 4, 43, 330, 8, 43, 11, 43, 12, 43, 331, 1, 43, 3, 43, 335, 8, 43, + 1, 43, 3, 43, 338, 8, 43, 1, 43, 1, 43, 4, 43, 342, 8, 43, 11, 43, 12, + 43, 343, 1, 43, 3, 43, 347, 8, 43, 3, 43, 349, 8, 43, 1, 44, 1, 44, 3, + 44, 353, 8, 44, 1, 44, 4, 44, 356, 8, 44, 11, 44, 12, 44, 357, 1, 45, 1, + 45, 1, 46, 1, 46, 3, 46, 364, 8, 46, 1, 47, 1, 47, 1, 47, 5, 47, 369, 8, + 47, 10, 47, 12, 47, 372, 9, 47, 3, 47, 374, 8, 47, 1, 48, 1, 48, 1, 48, + 3, 48, 379, 8, 48, 1, 49, 3, 49, 382, 8, 49, 1, 49, 1, 49, 1, 49, 1, 49, + 5, 49, 388, 8, 49, 10, 49, 12, 49, 391, 9, 49, 1, 50, 1, 50, 1, 50, 1, + 50, 5, 50, 397, 8, 50, 10, 50, 12, 50, 400, 9, 50, 1, 51, 1, 51, 1, 51, + 1, 52, 1, 52, 1, 52, 3, 52, 408, 8, 52, 1, 53, 1, 53, 4, 53, 412, 8, 53, + 11, 53, 12, 53, 413, 1, 53, 1, 53, 4, 53, 418, 8, 53, 11, 53, 12, 53, 419, + 5, 53, 422, 8, 53, 10, 53, 12, 53, 425, 9, 53, 1, 54, 1, 54, 3, 54, 429, + 8, 54, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 5, 56, 437, 8, 56, 10, + 56, 12, 56, 440, 9, 56, 1, 56, 1, 56, 1, 56, 1, 56, 5, 56, 446, 8, 56, + 10, 56, 12, 56, 449, 9, 56, 1, 56, 3, 56, 452, 8, 56, 1, 57, 1, 57, 5, + 57, 456, 8, 57, 10, 57, 12, 57, 459, 9, 57, 1, 57, 1, 57, 1, 58, 1, 58, + 3, 58, 465, 8, 58, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, + 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, + 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 3, 60, 492, 8, 60, 1, + 60, 3, 60, 495, 8, 60, 1, 60, 1, 60, 1, 60, 1, 60, 3, 60, 501, 8, 60, 1, + 61, 1, 61, 1, 457, 0, 62, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, + 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, + 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, 51, 26, + 53, 27, 55, 28, 57, 29, 59, 30, 61, 31, 63, 32, 65, 33, 67, 34, 69, 35, + 71, 36, 73, 37, 75, 38, 77, 0, 79, 0, 81, 39, 83, 40, 85, 41, 87, 42, 89, + 0, 91, 0, 93, 0, 95, 43, 97, 0, 99, 44, 101, 45, 103, 46, 105, 0, 107, + 47, 109, 48, 111, 49, 113, 0, 115, 0, 117, 0, 119, 0, 121, 0, 123, 0, 1, + 0, 12, 3, 0, 9, 10, 12, 13, 32, 32, 1, 0, 10, 10, 2, 0, 65, 90, 97, 122, + 4, 0, 100, 100, 104, 104, 109, 109, 115, 115, 2, 0, 69, 69, 101, 101, 2, + 0, 43, 43, 45, 45, 2, 0, 58, 58, 95, 95, 4, 0, 37, 37, 45, 46, 95, 95, + 126, 126, 2, 0, 39, 39, 92, 92, 2, 0, 34, 34, 92, 92, 1, 0, 92, 92, 2, + 0, 48, 57, 97, 102, 542, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, + 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, + 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, + 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, + 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, + 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, + 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, + 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, + 0, 0, 0, 0, 61, 1, 0, 0, 0, 0, 63, 1, 0, 0, 0, 0, 65, 1, 0, 0, 0, 0, 67, + 1, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 71, 1, 0, 0, 0, 0, 73, 1, 0, 0, 0, 0, + 75, 1, 0, 0, 0, 0, 81, 1, 0, 0, 0, 0, 83, 1, 0, 0, 0, 0, 85, 1, 0, 0, 0, + 0, 87, 1, 0, 0, 0, 0, 95, 1, 0, 0, 0, 0, 99, 1, 0, 0, 0, 0, 101, 1, 0, + 0, 0, 0, 103, 1, 0, 0, 0, 0, 107, 1, 0, 0, 0, 0, 109, 1, 0, 0, 0, 0, 111, + 1, 0, 0, 0, 1, 125, 1, 0, 0, 0, 3, 134, 1, 0, 0, 0, 5, 136, 1, 0, 0, 0, + 7, 142, 1, 0, 0, 0, 9, 152, 1, 0, 0, 0, 11, 156, 1, 0, 0, 0, 13, 158, 1, + 0, 0, 0, 15, 163, 1, 0, 0, 0, 17, 169, 1, 0, 0, 0, 19, 178, 1, 0, 0, 0, + 21, 180, 1, 0, 0, 0, 23, 188, 1, 0, 0, 0, 25, 192, 1, 0, 0, 0, 27, 197, + 1, 0, 0, 0, 29, 203, 1, 0, 0, 0, 31, 207, 1, 0, 0, 0, 33, 210, 1, 0, 0, + 0, 35, 212, 1, 0, 0, 0, 37, 214, 1, 0, 0, 0, 39, 216, 1, 0, 0, 0, 41, 218, + 1, 0, 0, 0, 43, 220, 1, 0, 0, 0, 45, 222, 1, 0, 0, 0, 47, 224, 1, 0, 0, + 0, 49, 227, 1, 0, 0, 0, 51, 229, 1, 0, 0, 0, 53, 231, 1, 0, 0, 0, 55, 234, + 1, 0, 0, 0, 57, 236, 1, 0, 0, 0, 59, 239, 1, 0, 0, 0, 61, 241, 1, 0, 0, + 0, 63, 244, 1, 0, 0, 0, 65, 246, 1, 0, 0, 0, 67, 249, 1, 0, 0, 0, 69, 251, + 1, 0, 0, 0, 71, 254, 1, 0, 0, 0, 73, 257, 1, 0, 0, 0, 75, 260, 1, 0, 0, + 0, 77, 263, 1, 0, 0, 0, 79, 265, 1, 0, 0, 0, 81, 267, 1, 0, 0, 0, 83, 300, + 1, 0, 0, 0, 85, 310, 1, 0, 0, 0, 87, 348, 1, 0, 0, 0, 89, 350, 1, 0, 0, + 0, 91, 359, 1, 0, 0, 0, 93, 363, 1, 0, 0, 0, 95, 373, 1, 0, 0, 0, 97, 378, + 1, 0, 0, 0, 99, 381, 1, 0, 0, 0, 101, 392, 1, 0, 0, 0, 103, 401, 1, 0, + 0, 0, 105, 407, 1, 0, 0, 0, 107, 409, 1, 0, 0, 0, 109, 428, 1, 0, 0, 0, + 111, 430, 1, 0, 0, 0, 113, 451, 1, 0, 0, 0, 115, 453, 1, 0, 0, 0, 117, + 464, 1, 0, 0, 0, 119, 466, 1, 0, 0, 0, 121, 500, 1, 0, 0, 0, 123, 502, + 1, 0, 0, 0, 125, 126, 5, 116, 0, 0, 126, 127, 5, 101, 0, 0, 127, 128, 5, + 109, 0, 0, 128, 129, 5, 112, 0, 0, 129, 130, 5, 111, 0, 0, 130, 131, 5, + 114, 0, 0, 131, 132, 5, 97, 0, 0, 132, 133, 5, 108, 0, 0, 133, 2, 1, 0, + 0, 0, 134, 135, 5, 46, 0, 0, 135, 4, 1, 0, 0, 0, 136, 137, 5, 100, 0, 0, + 137, 138, 5, 101, 0, 0, 138, 139, 5, 115, 0, 0, 139, 140, 5, 99, 0, 0, + 140, 141, 5, 114, 0, 0, 141, 6, 1, 0, 0, 0, 142, 143, 5, 105, 0, 0, 143, + 144, 5, 110, 0, 0, 144, 145, 5, 99, 0, 0, 145, 146, 5, 108, 0, 0, 146, + 147, 5, 117, 0, 0, 147, 148, 5, 115, 0, 0, 148, 149, 5, 105, 0, 0, 149, + 150, 5, 111, 0, 0, 150, 151, 5, 110, 0, 0, 151, 8, 1, 0, 0, 0, 152, 153, + 5, 110, 0, 0, 153, 154, 5, 111, 0, 0, 154, 155, 5, 119, 0, 0, 155, 10, + 1, 0, 0, 0, 156, 157, 5, 58, 0, 0, 157, 12, 1, 0, 0, 0, 158, 159, 5, 111, + 0, 0, 159, 160, 5, 112, 0, 0, 160, 161, 5, 116, 0, 0, 161, 14, 1, 0, 0, + 0, 162, 164, 7, 0, 0, 0, 163, 162, 1, 0, 0, 0, 164, 165, 1, 0, 0, 0, 165, + 163, 1, 0, 0, 0, 165, 166, 1, 0, 0, 0, 166, 167, 1, 0, 0, 0, 167, 168, + 6, 7, 0, 0, 168, 16, 1, 0, 0, 0, 169, 173, 5, 35, 0, 0, 170, 172, 8, 1, + 0, 0, 171, 170, 1, 0, 0, 0, 172, 175, 1, 0, 0, 0, 173, 171, 1, 0, 0, 0, + 173, 174, 1, 0, 0, 0, 174, 176, 1, 0, 0, 0, 175, 173, 1, 0, 0, 0, 176, + 177, 6, 8, 0, 0, 177, 18, 1, 0, 0, 0, 178, 179, 5, 10232, 0, 0, 179, 20, + 1, 0, 0, 0, 180, 181, 5, 80, 0, 0, 181, 182, 5, 97, 0, 0, 182, 183, 5, + 99, 0, 0, 183, 184, 5, 107, 0, 0, 184, 185, 5, 97, 0, 0, 185, 186, 5, 103, + 0, 0, 186, 187, 5, 101, 0, 0, 187, 22, 1, 0, 0, 0, 188, 189, 5, 85, 0, + 0, 189, 190, 5, 115, 0, 0, 190, 191, 5, 101, 0, 0, 191, 24, 1, 0, 0, 0, + 192, 193, 5, 68, 0, 0, 193, 194, 5, 101, 0, 0, 194, 195, 5, 99, 0, 0, 195, + 196, 5, 108, 0, 0, 196, 26, 1, 0, 0, 0, 197, 198, 5, 98, 0, 0, 198, 199, + 5, 111, 0, 0, 199, 200, 5, 117, 0, 0, 200, 201, 5, 110, 0, 0, 201, 202, + 5, 100, 0, 0, 202, 28, 1, 0, 0, 0, 203, 204, 5, 108, 0, 0, 204, 205, 5, + 101, 0, 0, 205, 206, 5, 116, 0, 0, 206, 30, 1, 0, 0, 0, 207, 208, 5, 100, + 0, 0, 208, 209, 5, 111, 0, 0, 209, 32, 1, 0, 0, 0, 210, 211, 5, 40, 0, + 0, 211, 34, 1, 0, 0, 0, 212, 213, 5, 41, 0, 0, 213, 36, 1, 0, 0, 0, 214, + 215, 5, 91, 0, 0, 215, 38, 1, 0, 0, 0, 216, 217, 5, 93, 0, 0, 217, 40, + 1, 0, 0, 0, 218, 219, 5, 123, 0, 0, 219, 42, 1, 0, 0, 0, 220, 221, 5, 125, + 0, 0, 221, 44, 1, 0, 0, 0, 222, 223, 5, 61, 0, 0, 223, 46, 1, 0, 0, 0, + 224, 225, 5, 33, 0, 0, 225, 226, 5, 61, 0, 0, 226, 48, 1, 0, 0, 0, 227, + 228, 5, 44, 0, 0, 228, 50, 1, 0, 0, 0, 229, 230, 5, 33, 0, 0, 230, 52, + 1, 0, 0, 0, 231, 232, 5, 60, 0, 0, 232, 233, 5, 61, 0, 0, 233, 54, 1, 0, + 0, 0, 234, 235, 5, 60, 0, 0, 235, 56, 1, 0, 0, 0, 236, 237, 5, 62, 0, 0, + 237, 238, 5, 61, 0, 0, 238, 58, 1, 0, 0, 0, 239, 240, 5, 62, 0, 0, 240, + 60, 1, 0, 0, 0, 241, 242, 5, 58, 0, 0, 242, 243, 5, 45, 0, 0, 243, 62, + 1, 0, 0, 0, 244, 245, 5, 10, 0, 0, 245, 64, 1, 0, 0, 0, 246, 247, 5, 124, + 0, 0, 247, 248, 5, 62, 0, 0, 248, 66, 1, 0, 0, 0, 249, 250, 5, 64, 0, 0, + 250, 68, 1, 0, 0, 0, 251, 252, 5, 60, 0, 0, 252, 253, 5, 45, 0, 0, 253, + 70, 1, 0, 0, 0, 254, 255, 5, 60, 0, 0, 255, 256, 5, 43, 0, 0, 256, 72, + 1, 0, 0, 0, 257, 258, 5, 91, 0, 0, 258, 259, 5, 45, 0, 0, 259, 74, 1, 0, + 0, 0, 260, 261, 5, 91, 0, 0, 261, 262, 5, 43, 0, 0, 262, 76, 1, 0, 0, 0, + 263, 264, 7, 2, 0, 0, 264, 78, 1, 0, 0, 0, 265, 266, 2, 48, 57, 0, 266, + 80, 1, 0, 0, 0, 267, 268, 3, 79, 39, 0, 268, 269, 3, 79, 39, 0, 269, 270, + 3, 79, 39, 0, 270, 271, 3, 79, 39, 0, 271, 272, 5, 45, 0, 0, 272, 273, + 3, 79, 39, 0, 273, 274, 3, 79, 39, 0, 274, 275, 5, 45, 0, 0, 275, 276, + 3, 79, 39, 0, 276, 297, 3, 79, 39, 0, 277, 278, 5, 84, 0, 0, 278, 279, + 3, 79, 39, 0, 279, 280, 3, 79, 39, 0, 280, 281, 5, 58, 0, 0, 281, 282, + 3, 79, 39, 0, 282, 283, 3, 79, 39, 0, 283, 284, 5, 58, 0, 0, 284, 285, + 3, 79, 39, 0, 285, 292, 3, 79, 39, 0, 286, 288, 5, 46, 0, 0, 287, 289, + 3, 79, 39, 0, 288, 287, 1, 0, 0, 0, 289, 290, 1, 0, 0, 0, 290, 288, 1, + 0, 0, 0, 290, 291, 1, 0, 0, 0, 291, 293, 1, 0, 0, 0, 292, 286, 1, 0, 0, + 0, 292, 293, 1, 0, 0, 0, 293, 295, 1, 0, 0, 0, 294, 296, 5, 90, 0, 0, 295, + 294, 1, 0, 0, 0, 295, 296, 1, 0, 0, 0, 296, 298, 1, 0, 0, 0, 297, 277, + 1, 0, 0, 0, 297, 298, 1, 0, 0, 0, 298, 82, 1, 0, 0, 0, 299, 301, 3, 79, + 39, 0, 300, 299, 1, 0, 0, 0, 301, 302, 1, 0, 0, 0, 302, 300, 1, 0, 0, 0, + 302, 303, 1, 0, 0, 0, 303, 307, 1, 0, 0, 0, 304, 308, 7, 3, 0, 0, 305, + 306, 5, 109, 0, 0, 306, 308, 5, 115, 0, 0, 307, 304, 1, 0, 0, 0, 307, 305, + 1, 0, 0, 0, 308, 84, 1, 0, 0, 0, 309, 311, 5, 45, 0, 0, 310, 309, 1, 0, + 0, 0, 310, 311, 1, 0, 0, 0, 311, 312, 1, 0, 0, 0, 312, 316, 3, 79, 39, + 0, 313, 315, 3, 79, 39, 0, 314, 313, 1, 0, 0, 0, 315, 318, 1, 0, 0, 0, + 316, 314, 1, 0, 0, 0, 316, 317, 1, 0, 0, 0, 317, 86, 1, 0, 0, 0, 318, 316, + 1, 0, 0, 0, 319, 321, 5, 45, 0, 0, 320, 319, 1, 0, 0, 0, 320, 321, 1, 0, + 0, 0, 321, 323, 1, 0, 0, 0, 322, 324, 3, 79, 39, 0, 323, 322, 1, 0, 0, + 0, 324, 325, 1, 0, 0, 0, 325, 323, 1, 0, 0, 0, 325, 326, 1, 0, 0, 0, 326, + 327, 1, 0, 0, 0, 327, 329, 5, 46, 0, 0, 328, 330, 3, 79, 39, 0, 329, 328, + 1, 0, 0, 0, 330, 331, 1, 0, 0, 0, 331, 329, 1, 0, 0, 0, 331, 332, 1, 0, + 0, 0, 332, 334, 1, 0, 0, 0, 333, 335, 3, 89, 44, 0, 334, 333, 1, 0, 0, + 0, 334, 335, 1, 0, 0, 0, 335, 349, 1, 0, 0, 0, 336, 338, 5, 45, 0, 0, 337, + 336, 1, 0, 0, 0, 337, 338, 1, 0, 0, 0, 338, 339, 1, 0, 0, 0, 339, 341, + 5, 46, 0, 0, 340, 342, 3, 79, 39, 0, 341, 340, 1, 0, 0, 0, 342, 343, 1, + 0, 0, 0, 343, 341, 1, 0, 0, 0, 343, 344, 1, 0, 0, 0, 344, 346, 1, 0, 0, + 0, 345, 347, 3, 89, 44, 0, 346, 345, 1, 0, 0, 0, 346, 347, 1, 0, 0, 0, + 347, 349, 1, 0, 0, 0, 348, 320, 1, 0, 0, 0, 348, 337, 1, 0, 0, 0, 349, + 88, 1, 0, 0, 0, 350, 352, 7, 4, 0, 0, 351, 353, 7, 5, 0, 0, 352, 351, 1, + 0, 0, 0, 352, 353, 1, 0, 0, 0, 353, 355, 1, 0, 0, 0, 354, 356, 3, 79, 39, + 0, 355, 354, 1, 0, 0, 0, 356, 357, 1, 0, 0, 0, 357, 355, 1, 0, 0, 0, 357, + 358, 1, 0, 0, 0, 358, 90, 1, 0, 0, 0, 359, 360, 2, 65, 90, 0, 360, 92, + 1, 0, 0, 0, 361, 364, 3, 77, 38, 0, 362, 364, 3, 79, 39, 0, 363, 361, 1, + 0, 0, 0, 363, 362, 1, 0, 0, 0, 364, 94, 1, 0, 0, 0, 365, 374, 5, 95, 0, + 0, 366, 370, 3, 91, 45, 0, 367, 369, 3, 93, 46, 0, 368, 367, 1, 0, 0, 0, + 369, 372, 1, 0, 0, 0, 370, 368, 1, 0, 0, 0, 370, 371, 1, 0, 0, 0, 371, + 374, 1, 0, 0, 0, 372, 370, 1, 0, 0, 0, 373, 365, 1, 0, 0, 0, 373, 366, + 1, 0, 0, 0, 374, 96, 1, 0, 0, 0, 375, 379, 3, 77, 38, 0, 376, 379, 3, 79, + 39, 0, 377, 379, 7, 6, 0, 0, 378, 375, 1, 0, 0, 0, 378, 376, 1, 0, 0, 0, + 378, 377, 1, 0, 0, 0, 379, 98, 1, 0, 0, 0, 380, 382, 5, 58, 0, 0, 381, + 380, 1, 0, 0, 0, 381, 382, 1, 0, 0, 0, 382, 383, 1, 0, 0, 0, 383, 389, + 2, 97, 122, 0, 384, 388, 3, 97, 48, 0, 385, 386, 5, 46, 0, 0, 386, 388, + 3, 97, 48, 0, 387, 384, 1, 0, 0, 0, 387, 385, 1, 0, 0, 0, 388, 391, 1, + 0, 0, 0, 389, 387, 1, 0, 0, 0, 389, 390, 1, 0, 0, 0, 390, 100, 1, 0, 0, + 0, 391, 389, 1, 0, 0, 0, 392, 398, 2, 65, 90, 0, 393, 397, 3, 97, 48, 0, + 394, 395, 5, 46, 0, 0, 395, 397, 3, 97, 48, 0, 396, 393, 1, 0, 0, 0, 396, + 394, 1, 0, 0, 0, 397, 400, 1, 0, 0, 0, 398, 396, 1, 0, 0, 0, 398, 399, + 1, 0, 0, 0, 399, 102, 1, 0, 0, 0, 400, 398, 1, 0, 0, 0, 401, 402, 5, 46, + 0, 0, 402, 403, 3, 101, 50, 0, 403, 104, 1, 0, 0, 0, 404, 408, 3, 77, 38, + 0, 405, 408, 3, 79, 39, 0, 406, 408, 7, 7, 0, 0, 407, 404, 1, 0, 0, 0, + 407, 405, 1, 0, 0, 0, 407, 406, 1, 0, 0, 0, 408, 106, 1, 0, 0, 0, 409, + 411, 5, 47, 0, 0, 410, 412, 3, 105, 52, 0, 411, 410, 1, 0, 0, 0, 412, 413, + 1, 0, 0, 0, 413, 411, 1, 0, 0, 0, 413, 414, 1, 0, 0, 0, 414, 423, 1, 0, + 0, 0, 415, 417, 5, 47, 0, 0, 416, 418, 3, 105, 52, 0, 417, 416, 1, 0, 0, + 0, 418, 419, 1, 0, 0, 0, 419, 417, 1, 0, 0, 0, 419, 420, 1, 0, 0, 0, 420, + 422, 1, 0, 0, 0, 421, 415, 1, 0, 0, 0, 422, 425, 1, 0, 0, 0, 423, 421, + 1, 0, 0, 0, 423, 424, 1, 0, 0, 0, 424, 108, 1, 0, 0, 0, 425, 423, 1, 0, + 0, 0, 426, 429, 3, 113, 56, 0, 427, 429, 3, 115, 57, 0, 428, 426, 1, 0, + 0, 0, 428, 427, 1, 0, 0, 0, 429, 110, 1, 0, 0, 0, 430, 431, 5, 98, 0, 0, + 431, 432, 3, 109, 54, 0, 432, 112, 1, 0, 0, 0, 433, 438, 5, 39, 0, 0, 434, + 437, 3, 121, 60, 0, 435, 437, 8, 8, 0, 0, 436, 434, 1, 0, 0, 0, 436, 435, + 1, 0, 0, 0, 437, 440, 1, 0, 0, 0, 438, 436, 1, 0, 0, 0, 438, 439, 1, 0, + 0, 0, 439, 441, 1, 0, 0, 0, 440, 438, 1, 0, 0, 0, 441, 452, 5, 39, 0, 0, + 442, 447, 5, 34, 0, 0, 443, 446, 3, 121, 60, 0, 444, 446, 8, 9, 0, 0, 445, + 443, 1, 0, 0, 0, 445, 444, 1, 0, 0, 0, 446, 449, 1, 0, 0, 0, 447, 445, + 1, 0, 0, 0, 447, 448, 1, 0, 0, 0, 448, 450, 1, 0, 0, 0, 449, 447, 1, 0, + 0, 0, 450, 452, 5, 34, 0, 0, 451, 433, 1, 0, 0, 0, 451, 442, 1, 0, 0, 0, + 452, 114, 1, 0, 0, 0, 453, 457, 5, 96, 0, 0, 454, 456, 3, 117, 58, 0, 455, + 454, 1, 0, 0, 0, 456, 459, 1, 0, 0, 0, 457, 458, 1, 0, 0, 0, 457, 455, + 1, 0, 0, 0, 458, 460, 1, 0, 0, 0, 459, 457, 1, 0, 0, 0, 460, 461, 5, 96, + 0, 0, 461, 116, 1, 0, 0, 0, 462, 465, 3, 119, 59, 0, 463, 465, 3, 121, + 60, 0, 464, 462, 1, 0, 0, 0, 464, 463, 1, 0, 0, 0, 465, 118, 1, 0, 0, 0, + 466, 467, 8, 10, 0, 0, 467, 120, 1, 0, 0, 0, 468, 469, 5, 92, 0, 0, 469, + 501, 5, 110, 0, 0, 470, 471, 5, 92, 0, 0, 471, 501, 5, 116, 0, 0, 472, + 473, 5, 92, 0, 0, 473, 501, 5, 34, 0, 0, 474, 475, 5, 92, 0, 0, 475, 501, + 5, 39, 0, 0, 476, 477, 5, 92, 0, 0, 477, 501, 5, 92, 0, 0, 478, 479, 5, + 92, 0, 0, 479, 480, 5, 120, 0, 0, 480, 481, 3, 123, 61, 0, 481, 482, 3, + 123, 61, 0, 482, 501, 1, 0, 0, 0, 483, 484, 5, 92, 0, 0, 484, 485, 5, 117, + 0, 0, 485, 486, 5, 123, 0, 0, 486, 487, 3, 123, 61, 0, 487, 488, 3, 123, + 61, 0, 488, 489, 3, 123, 61, 0, 489, 491, 3, 123, 61, 0, 490, 492, 3, 123, + 61, 0, 491, 490, 1, 0, 0, 0, 491, 492, 1, 0, 0, 0, 492, 494, 1, 0, 0, 0, + 493, 495, 3, 123, 61, 0, 494, 493, 1, 0, 0, 0, 494, 495, 1, 0, 0, 0, 495, + 496, 1, 0, 0, 0, 496, 497, 5, 125, 0, 0, 497, 501, 1, 0, 0, 0, 498, 499, + 5, 92, 0, 0, 499, 501, 3, 63, 31, 0, 500, 468, 1, 0, 0, 0, 500, 470, 1, + 0, 0, 0, 500, 472, 1, 0, 0, 0, 500, 474, 1, 0, 0, 0, 500, 476, 1, 0, 0, + 0, 500, 478, 1, 0, 0, 0, 500, 483, 1, 0, 0, 0, 500, 498, 1, 0, 0, 0, 501, + 122, 1, 0, 0, 0, 502, 503, 7, 11, 0, 0, 503, 124, 1, 0, 0, 0, 45, 0, 165, + 173, 290, 292, 295, 297, 302, 307, 310, 316, 320, 325, 331, 334, 337, 343, + 346, 348, 352, 357, 363, 370, 373, 378, 381, 387, 389, 396, 398, 407, 413, + 419, 423, 428, 436, 438, 445, 447, 451, 457, 464, 491, 494, 500, 1, 0, + 1, 0, } deserializer := antlr.NewATNDeserializer(nil) staticData.atn = deserializer.Deserialize(staticData.serializedATN) @@ -322,24 +365,33 @@ const ( MangleLexerRPAREN = 18 MangleLexerLBRACKET = 19 MangleLexerRBRACKET = 20 - MangleLexerEQ = 21 - MangleLexerBANGEQ = 22 - MangleLexerCOMMA = 23 - MangleLexerBANG = 24 - MangleLexerLESS = 25 - MangleLexerLESSEQ = 26 - MangleLexerGREATER = 27 - MangleLexerGREATEREQ = 28 - MangleLexerCOLONDASH = 29 - MangleLexerNEWLINE = 30 - MangleLexerPIPEGREATER = 31 - MangleLexerNUMBER = 32 - MangleLexerFLOAT = 33 - MangleLexerVARIABLE = 34 - MangleLexerNAME = 35 - MangleLexerTYPENAME = 36 - MangleLexerDOT_TYPE = 37 - MangleLexerCONSTANT = 38 - MangleLexerSTRING = 39 - MangleLexerBYTESTRING = 40 + MangleLexerLBRACE = 21 + MangleLexerRBRACE = 22 + MangleLexerEQ = 23 + MangleLexerBANGEQ = 24 + MangleLexerCOMMA = 25 + MangleLexerBANG = 26 + MangleLexerLESSEQ = 27 + MangleLexerLESS = 28 + MangleLexerGREATEREQ = 29 + MangleLexerGREATER = 30 + MangleLexerCOLONDASH = 31 + MangleLexerNEWLINE = 32 + MangleLexerPIPEGREATER = 33 + MangleLexerAT = 34 + MangleLexerDIAMONDMINUS = 35 + MangleLexerDIAMONDPLUS = 36 + MangleLexerBOXMINUS = 37 + MangleLexerBOXPLUS = 38 + MangleLexerTIMESTAMP = 39 + MangleLexerDURATION = 40 + MangleLexerNUMBER = 41 + MangleLexerFLOAT = 42 + MangleLexerVARIABLE = 43 + MangleLexerNAME = 44 + MangleLexerTYPENAME = 45 + MangleLexerDOT_TYPE = 46 + MangleLexerCONSTANT = 47 + MangleLexerSTRING = 48 + MangleLexerBYTESTRING = 49 ) diff --git a/parse/gen/mangle_listener.go b/parse/gen/mangle_listener.go index 2b98c9b..a3c1fe7 100644 --- a/parse/gen/mangle_listener.go +++ b/parse/gen/mangle_listener.go @@ -1,4 +1,4 @@ -// Code generated from parse/gen/Mangle.g4 by ANTLR 4.13.1. DO NOT EDIT. +// Code generated from parse/gen/Mangle.g4 by ANTLR 4.13.2. DO NOT EDIT. package gen // Mangle import "github.com/antlr4-go/antlr/v4" @@ -34,6 +34,12 @@ type MangleListener interface { // EnterClause is called when entering the clause production. EnterClause(c *ClauseContext) + // EnterTemporalAnnotation is called when entering the temporalAnnotation production. + EnterTemporalAnnotation(c *TemporalAnnotationContext) + + // EnterTemporalBound is called when entering the temporalBound production. + EnterTemporalBound(c *TemporalBoundContext) + // EnterClauseBody is called when entering the clauseBody production. EnterClauseBody(c *ClauseBodyContext) @@ -46,6 +52,9 @@ type MangleListener interface { // EnterLiteralOrFml is called when entering the literalOrFml production. EnterLiteralOrFml(c *LiteralOrFmlContext) + // EnterTemporalOperator is called when entering the temporalOperator production. + EnterTemporalOperator(c *TemporalOperatorContext) + // EnterVar is called when entering the Var production. EnterVar(c *VarContext) @@ -115,6 +124,12 @@ type MangleListener interface { // ExitClause is called when exiting the clause production. ExitClause(c *ClauseContext) + // ExitTemporalAnnotation is called when exiting the temporalAnnotation production. + ExitTemporalAnnotation(c *TemporalAnnotationContext) + + // ExitTemporalBound is called when exiting the temporalBound production. + ExitTemporalBound(c *TemporalBoundContext) + // ExitClauseBody is called when exiting the clauseBody production. ExitClauseBody(c *ClauseBodyContext) @@ -127,6 +142,9 @@ type MangleListener interface { // ExitLiteralOrFml is called when exiting the literalOrFml production. ExitLiteralOrFml(c *LiteralOrFmlContext) + // ExitTemporalOperator is called when exiting the temporalOperator production. + ExitTemporalOperator(c *TemporalOperatorContext) + // ExitVar is called when exiting the Var production. ExitVar(c *VarContext) diff --git a/parse/gen/mangle_parser.go b/parse/gen/mangle_parser.go index 119c090..9258bce 100644 --- a/parse/gen/mangle_parser.go +++ b/parse/gen/mangle_parser.go @@ -1,4 +1,4 @@ -// Code generated from parse/gen/Mangle.g4 by ANTLR 4.13.1. DO NOT EDIT. +// Code generated from parse/gen/Mangle.g4 by ANTLR 4.13.2. DO NOT EDIT. package gen // Mangle import ( @@ -32,154 +32,184 @@ var MangleParserStaticData struct { func mangleParserInit() { staticData := &MangleParserStaticData staticData.LiteralNames = []string{ - "", "'.'", "'descr'", "'inclusion'", "':'", "'{'", "'}'", "'opt'", "", - "", "'\\u27F8'", "'Package'", "'Use'", "'Decl'", "'bound'", "'let'", - "'do'", "'('", "')'", "'['", "']'", "'='", "'!='", "','", "'!'", "'<'", - "'<='", "'>'", "'>='", "':-'", "'\\n'", "'|>'", + "", "'temporal'", "'.'", "'descr'", "'inclusion'", "'now'", "':'", "'opt'", + "", "", "'\\u27F8'", "'Package'", "'Use'", "'Decl'", "'bound'", "'let'", + "'do'", "'('", "')'", "'['", "']'", "'{'", "'}'", "'='", "'!='", "','", + "'!'", "'<='", "'<'", "'>='", "'>'", "':-'", "'\\n'", "'|>'", "'@'", + "'<-'", "'<+'", "'[-'", "'[+'", } staticData.SymbolicNames = []string{ "", "", "", "", "", "", "", "", "WHITESPACE", "COMMENT", "LONGLEFTDOUBLEARROW", "PACKAGE", "USE", "DECL", "BOUND", "LET", "DO", "LPAREN", "RPAREN", - "LBRACKET", "RBRACKET", "EQ", "BANGEQ", "COMMA", "BANG", "LESS", "LESSEQ", - "GREATER", "GREATEREQ", "COLONDASH", "NEWLINE", "PIPEGREATER", "NUMBER", - "FLOAT", "VARIABLE", "NAME", "TYPENAME", "DOT_TYPE", "CONSTANT", "STRING", - "BYTESTRING", + "LBRACKET", "RBRACKET", "LBRACE", "RBRACE", "EQ", "BANGEQ", "COMMA", + "BANG", "LESSEQ", "LESS", "GREATEREQ", "GREATER", "COLONDASH", "NEWLINE", + "PIPEGREATER", "AT", "DIAMONDMINUS", "DIAMONDPLUS", "BOXMINUS", "BOXPLUS", + "TIMESTAMP", "DURATION", "NUMBER", "FLOAT", "VARIABLE", "NAME", "TYPENAME", + "DOT_TYPE", "CONSTANT", "STRING", "BYTESTRING", } staticData.RuleNames = []string{ "start", "program", "packageDecl", "useDecl", "decl", "descrBlock", - "boundsBlock", "constraintsBlock", "clause", "clauseBody", "transform", - "letStmt", "literalOrFml", "term", "member", "atom", "atoms", + "boundsBlock", "constraintsBlock", "clause", "temporalAnnotation", "temporalBound", + "clauseBody", "transform", "letStmt", "literalOrFml", "temporalOperator", + "term", "member", "atom", "atoms", } staticData.PredictionContextCache = antlr.NewPredictionContextCache() staticData.serializedATN = []int32{ - 4, 1, 40, 283, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, + 4, 1, 49, 342, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, - 2, 16, 7, 16, 1, 0, 1, 0, 1, 0, 1, 1, 3, 1, 39, 8, 1, 1, 1, 5, 1, 42, 8, - 1, 10, 1, 12, 1, 45, 9, 1, 1, 1, 1, 1, 5, 1, 49, 8, 1, 10, 1, 12, 1, 52, - 9, 1, 1, 2, 1, 2, 1, 2, 3, 2, 57, 8, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 3, - 3, 64, 8, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 3, 4, 71, 8, 4, 1, 4, 5, 4, - 74, 8, 4, 10, 4, 12, 4, 77, 9, 4, 1, 4, 3, 4, 80, 8, 4, 1, 4, 1, 4, 1, - 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 5, 6, 92, 8, 6, 10, 6, 12, - 6, 95, 9, 6, 1, 6, 3, 6, 98, 8, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 8, - 1, 8, 1, 8, 3, 8, 108, 8, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 5, 9, 115, 8, - 9, 10, 9, 12, 9, 118, 9, 9, 1, 9, 3, 9, 121, 8, 9, 1, 9, 1, 9, 5, 9, 125, - 8, 9, 10, 9, 12, 9, 128, 9, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, - 5, 10, 136, 8, 10, 10, 10, 12, 10, 139, 9, 10, 3, 10, 141, 8, 10, 1, 10, - 1, 10, 1, 10, 5, 10, 146, 8, 10, 10, 10, 12, 10, 149, 9, 10, 3, 10, 151, - 8, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 3, 12, 161, - 8, 12, 1, 12, 1, 12, 3, 12, 165, 8, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, - 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 5, 13, 177, 8, 13, 10, 13, 12, 13, - 180, 9, 13, 1, 13, 3, 13, 183, 8, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, - 1, 13, 1, 13, 5, 13, 192, 8, 13, 10, 13, 12, 13, 195, 9, 13, 1, 13, 1, - 13, 1, 13, 1, 13, 3, 13, 201, 8, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, - 1, 13, 1, 13, 5, 13, 210, 8, 13, 10, 13, 12, 13, 213, 9, 13, 1, 13, 1, - 13, 1, 13, 1, 13, 3, 13, 219, 8, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, - 1, 13, 5, 13, 227, 8, 13, 10, 13, 12, 13, 230, 9, 13, 1, 13, 1, 13, 3, - 13, 234, 8, 13, 3, 13, 236, 8, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, - 13, 5, 13, 244, 8, 13, 10, 13, 12, 13, 247, 9, 13, 1, 13, 3, 13, 250, 8, - 13, 1, 13, 3, 13, 253, 8, 13, 1, 14, 1, 14, 1, 14, 3, 14, 258, 8, 14, 1, - 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 265, 8, 14, 1, 15, 1, 15, 1, 16, - 1, 16, 1, 16, 1, 16, 5, 16, 273, 8, 16, 10, 16, 12, 16, 276, 9, 16, 1, - 16, 3, 16, 279, 8, 16, 1, 16, 1, 16, 1, 16, 0, 0, 17, 0, 2, 4, 6, 8, 10, - 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 0, 2, 2, 0, 10, 10, 29, 29, - 2, 0, 21, 22, 25, 28, 311, 0, 34, 1, 0, 0, 0, 2, 38, 1, 0, 0, 0, 4, 53, - 1, 0, 0, 0, 6, 60, 1, 0, 0, 0, 8, 67, 1, 0, 0, 0, 10, 83, 1, 0, 0, 0, 12, - 86, 1, 0, 0, 0, 14, 101, 1, 0, 0, 0, 16, 104, 1, 0, 0, 0, 18, 111, 1, 0, - 0, 0, 20, 150, 1, 0, 0, 0, 22, 152, 1, 0, 0, 0, 24, 164, 1, 0, 0, 0, 26, - 252, 1, 0, 0, 0, 28, 264, 1, 0, 0, 0, 30, 266, 1, 0, 0, 0, 32, 268, 1, - 0, 0, 0, 34, 35, 3, 2, 1, 0, 35, 36, 5, 0, 0, 1, 36, 1, 1, 0, 0, 0, 37, - 39, 3, 4, 2, 0, 38, 37, 1, 0, 0, 0, 38, 39, 1, 0, 0, 0, 39, 43, 1, 0, 0, - 0, 40, 42, 3, 6, 3, 0, 41, 40, 1, 0, 0, 0, 42, 45, 1, 0, 0, 0, 43, 41, - 1, 0, 0, 0, 43, 44, 1, 0, 0, 0, 44, 50, 1, 0, 0, 0, 45, 43, 1, 0, 0, 0, - 46, 49, 3, 8, 4, 0, 47, 49, 3, 16, 8, 0, 48, 46, 1, 0, 0, 0, 48, 47, 1, - 0, 0, 0, 49, 52, 1, 0, 0, 0, 50, 48, 1, 0, 0, 0, 50, 51, 1, 0, 0, 0, 51, - 3, 1, 0, 0, 0, 52, 50, 1, 0, 0, 0, 53, 54, 5, 11, 0, 0, 54, 56, 5, 35, - 0, 0, 55, 57, 3, 32, 16, 0, 56, 55, 1, 0, 0, 0, 56, 57, 1, 0, 0, 0, 57, - 58, 1, 0, 0, 0, 58, 59, 5, 24, 0, 0, 59, 5, 1, 0, 0, 0, 60, 61, 5, 12, - 0, 0, 61, 63, 5, 35, 0, 0, 62, 64, 3, 32, 16, 0, 63, 62, 1, 0, 0, 0, 63, - 64, 1, 0, 0, 0, 64, 65, 1, 0, 0, 0, 65, 66, 5, 24, 0, 0, 66, 7, 1, 0, 0, - 0, 67, 68, 5, 13, 0, 0, 68, 70, 3, 30, 15, 0, 69, 71, 3, 10, 5, 0, 70, - 69, 1, 0, 0, 0, 70, 71, 1, 0, 0, 0, 71, 75, 1, 0, 0, 0, 72, 74, 3, 12, - 6, 0, 73, 72, 1, 0, 0, 0, 74, 77, 1, 0, 0, 0, 75, 73, 1, 0, 0, 0, 75, 76, - 1, 0, 0, 0, 76, 79, 1, 0, 0, 0, 77, 75, 1, 0, 0, 0, 78, 80, 3, 14, 7, 0, - 79, 78, 1, 0, 0, 0, 79, 80, 1, 0, 0, 0, 80, 81, 1, 0, 0, 0, 81, 82, 5, - 1, 0, 0, 82, 9, 1, 0, 0, 0, 83, 84, 5, 2, 0, 0, 84, 85, 3, 32, 16, 0, 85, - 11, 1, 0, 0, 0, 86, 87, 5, 14, 0, 0, 87, 93, 5, 19, 0, 0, 88, 89, 3, 26, - 13, 0, 89, 90, 5, 23, 0, 0, 90, 92, 1, 0, 0, 0, 91, 88, 1, 0, 0, 0, 92, - 95, 1, 0, 0, 0, 93, 91, 1, 0, 0, 0, 93, 94, 1, 0, 0, 0, 94, 97, 1, 0, 0, - 0, 95, 93, 1, 0, 0, 0, 96, 98, 3, 26, 13, 0, 97, 96, 1, 0, 0, 0, 97, 98, - 1, 0, 0, 0, 98, 99, 1, 0, 0, 0, 99, 100, 5, 20, 0, 0, 100, 13, 1, 0, 0, - 0, 101, 102, 5, 3, 0, 0, 102, 103, 3, 32, 16, 0, 103, 15, 1, 0, 0, 0, 104, - 107, 3, 30, 15, 0, 105, 106, 7, 0, 0, 0, 106, 108, 3, 18, 9, 0, 107, 105, - 1, 0, 0, 0, 107, 108, 1, 0, 0, 0, 108, 109, 1, 0, 0, 0, 109, 110, 5, 1, - 0, 0, 110, 17, 1, 0, 0, 0, 111, 116, 3, 24, 12, 0, 112, 113, 5, 23, 0, - 0, 113, 115, 3, 24, 12, 0, 114, 112, 1, 0, 0, 0, 115, 118, 1, 0, 0, 0, - 116, 114, 1, 0, 0, 0, 116, 117, 1, 0, 0, 0, 117, 120, 1, 0, 0, 0, 118, - 116, 1, 0, 0, 0, 119, 121, 5, 23, 0, 0, 120, 119, 1, 0, 0, 0, 120, 121, - 1, 0, 0, 0, 121, 126, 1, 0, 0, 0, 122, 123, 5, 31, 0, 0, 123, 125, 3, 20, - 10, 0, 124, 122, 1, 0, 0, 0, 125, 128, 1, 0, 0, 0, 126, 124, 1, 0, 0, 0, - 126, 127, 1, 0, 0, 0, 127, 19, 1, 0, 0, 0, 128, 126, 1, 0, 0, 0, 129, 130, - 5, 16, 0, 0, 130, 140, 3, 26, 13, 0, 131, 132, 5, 23, 0, 0, 132, 137, 3, - 22, 11, 0, 133, 134, 5, 23, 0, 0, 134, 136, 3, 22, 11, 0, 135, 133, 1, - 0, 0, 0, 136, 139, 1, 0, 0, 0, 137, 135, 1, 0, 0, 0, 137, 138, 1, 0, 0, - 0, 138, 141, 1, 0, 0, 0, 139, 137, 1, 0, 0, 0, 140, 131, 1, 0, 0, 0, 140, - 141, 1, 0, 0, 0, 141, 151, 1, 0, 0, 0, 142, 147, 3, 22, 11, 0, 143, 144, - 5, 23, 0, 0, 144, 146, 3, 22, 11, 0, 145, 143, 1, 0, 0, 0, 146, 149, 1, - 0, 0, 0, 147, 145, 1, 0, 0, 0, 147, 148, 1, 0, 0, 0, 148, 151, 1, 0, 0, - 0, 149, 147, 1, 0, 0, 0, 150, 129, 1, 0, 0, 0, 150, 142, 1, 0, 0, 0, 151, - 21, 1, 0, 0, 0, 152, 153, 5, 15, 0, 0, 153, 154, 5, 34, 0, 0, 154, 155, - 5, 21, 0, 0, 155, 156, 3, 26, 13, 0, 156, 23, 1, 0, 0, 0, 157, 160, 3, - 26, 13, 0, 158, 159, 7, 1, 0, 0, 159, 161, 3, 26, 13, 0, 160, 158, 1, 0, - 0, 0, 160, 161, 1, 0, 0, 0, 161, 165, 1, 0, 0, 0, 162, 163, 5, 24, 0, 0, - 163, 165, 3, 26, 13, 0, 164, 157, 1, 0, 0, 0, 164, 162, 1, 0, 0, 0, 165, - 25, 1, 0, 0, 0, 166, 253, 5, 34, 0, 0, 167, 253, 5, 38, 0, 0, 168, 253, - 5, 32, 0, 0, 169, 253, 5, 33, 0, 0, 170, 253, 5, 39, 0, 0, 171, 253, 5, - 40, 0, 0, 172, 178, 5, 19, 0, 0, 173, 174, 3, 26, 13, 0, 174, 175, 5, 23, - 0, 0, 175, 177, 1, 0, 0, 0, 176, 173, 1, 0, 0, 0, 177, 180, 1, 0, 0, 0, - 178, 176, 1, 0, 0, 0, 178, 179, 1, 0, 0, 0, 179, 182, 1, 0, 0, 0, 180, - 178, 1, 0, 0, 0, 181, 183, 3, 26, 13, 0, 182, 181, 1, 0, 0, 0, 182, 183, - 1, 0, 0, 0, 183, 184, 1, 0, 0, 0, 184, 253, 5, 20, 0, 0, 185, 193, 5, 19, - 0, 0, 186, 187, 3, 26, 13, 0, 187, 188, 5, 4, 0, 0, 188, 189, 3, 26, 13, - 0, 189, 190, 5, 23, 0, 0, 190, 192, 1, 0, 0, 0, 191, 186, 1, 0, 0, 0, 192, - 195, 1, 0, 0, 0, 193, 191, 1, 0, 0, 0, 193, 194, 1, 0, 0, 0, 194, 200, - 1, 0, 0, 0, 195, 193, 1, 0, 0, 0, 196, 197, 3, 26, 13, 0, 197, 198, 5, - 4, 0, 0, 198, 199, 3, 26, 13, 0, 199, 201, 1, 0, 0, 0, 200, 196, 1, 0, - 0, 0, 200, 201, 1, 0, 0, 0, 201, 202, 1, 0, 0, 0, 202, 253, 5, 20, 0, 0, - 203, 211, 5, 5, 0, 0, 204, 205, 3, 26, 13, 0, 205, 206, 5, 4, 0, 0, 206, - 207, 3, 26, 13, 0, 207, 208, 5, 23, 0, 0, 208, 210, 1, 0, 0, 0, 209, 204, - 1, 0, 0, 0, 210, 213, 1, 0, 0, 0, 211, 209, 1, 0, 0, 0, 211, 212, 1, 0, - 0, 0, 212, 218, 1, 0, 0, 0, 213, 211, 1, 0, 0, 0, 214, 215, 3, 26, 13, - 0, 215, 216, 5, 4, 0, 0, 216, 217, 3, 26, 13, 0, 217, 219, 1, 0, 0, 0, - 218, 214, 1, 0, 0, 0, 218, 219, 1, 0, 0, 0, 219, 220, 1, 0, 0, 0, 220, - 253, 5, 6, 0, 0, 221, 222, 5, 37, 0, 0, 222, 228, 5, 25, 0, 0, 223, 224, - 3, 28, 14, 0, 224, 225, 5, 23, 0, 0, 225, 227, 1, 0, 0, 0, 226, 223, 1, - 0, 0, 0, 227, 230, 1, 0, 0, 0, 228, 226, 1, 0, 0, 0, 228, 229, 1, 0, 0, - 0, 229, 235, 1, 0, 0, 0, 230, 228, 1, 0, 0, 0, 231, 233, 3, 28, 14, 0, - 232, 234, 5, 23, 0, 0, 233, 232, 1, 0, 0, 0, 233, 234, 1, 0, 0, 0, 234, - 236, 1, 0, 0, 0, 235, 231, 1, 0, 0, 0, 235, 236, 1, 0, 0, 0, 236, 237, - 1, 0, 0, 0, 237, 253, 5, 27, 0, 0, 238, 239, 5, 35, 0, 0, 239, 245, 5, - 17, 0, 0, 240, 241, 3, 26, 13, 0, 241, 242, 5, 23, 0, 0, 242, 244, 1, 0, - 0, 0, 243, 240, 1, 0, 0, 0, 244, 247, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, - 245, 246, 1, 0, 0, 0, 246, 249, 1, 0, 0, 0, 247, 245, 1, 0, 0, 0, 248, - 250, 3, 26, 13, 0, 249, 248, 1, 0, 0, 0, 249, 250, 1, 0, 0, 0, 250, 251, - 1, 0, 0, 0, 251, 253, 5, 18, 0, 0, 252, 166, 1, 0, 0, 0, 252, 167, 1, 0, - 0, 0, 252, 168, 1, 0, 0, 0, 252, 169, 1, 0, 0, 0, 252, 170, 1, 0, 0, 0, - 252, 171, 1, 0, 0, 0, 252, 172, 1, 0, 0, 0, 252, 185, 1, 0, 0, 0, 252, - 203, 1, 0, 0, 0, 252, 221, 1, 0, 0, 0, 252, 238, 1, 0, 0, 0, 253, 27, 1, - 0, 0, 0, 254, 257, 3, 26, 13, 0, 255, 256, 5, 4, 0, 0, 256, 258, 3, 26, - 13, 0, 257, 255, 1, 0, 0, 0, 257, 258, 1, 0, 0, 0, 258, 265, 1, 0, 0, 0, - 259, 260, 5, 7, 0, 0, 260, 261, 3, 26, 13, 0, 261, 262, 5, 4, 0, 0, 262, - 263, 3, 26, 13, 0, 263, 265, 1, 0, 0, 0, 264, 254, 1, 0, 0, 0, 264, 259, - 1, 0, 0, 0, 265, 29, 1, 0, 0, 0, 266, 267, 3, 26, 13, 0, 267, 31, 1, 0, - 0, 0, 268, 274, 5, 19, 0, 0, 269, 270, 3, 30, 15, 0, 270, 271, 5, 23, 0, - 0, 271, 273, 1, 0, 0, 0, 272, 269, 1, 0, 0, 0, 273, 276, 1, 0, 0, 0, 274, - 272, 1, 0, 0, 0, 274, 275, 1, 0, 0, 0, 275, 278, 1, 0, 0, 0, 276, 274, - 1, 0, 0, 0, 277, 279, 3, 30, 15, 0, 278, 277, 1, 0, 0, 0, 278, 279, 1, - 0, 0, 0, 279, 280, 1, 0, 0, 0, 280, 281, 5, 20, 0, 0, 281, 33, 1, 0, 0, - 0, 37, 38, 43, 48, 50, 56, 63, 70, 75, 79, 93, 97, 107, 116, 120, 126, - 137, 140, 147, 150, 160, 164, 178, 182, 193, 200, 211, 218, 228, 233, 235, - 245, 249, 252, 257, 264, 274, 278, + 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 1, 0, 1, 0, 1, + 0, 1, 1, 3, 1, 45, 8, 1, 1, 1, 5, 1, 48, 8, 1, 10, 1, 12, 1, 51, 9, 1, + 1, 1, 1, 1, 5, 1, 55, 8, 1, 10, 1, 12, 1, 58, 9, 1, 1, 2, 1, 2, 1, 2, 3, + 2, 63, 8, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 3, 3, 70, 8, 3, 1, 3, 1, 3, + 1, 4, 1, 4, 1, 4, 3, 4, 77, 8, 4, 1, 4, 3, 4, 80, 8, 4, 1, 4, 5, 4, 83, + 8, 4, 10, 4, 12, 4, 86, 9, 4, 1, 4, 3, 4, 89, 8, 4, 1, 4, 1, 4, 1, 5, 1, + 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 5, 6, 101, 8, 6, 10, 6, 12, 6, 104, + 9, 6, 1, 6, 3, 6, 107, 8, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, + 3, 8, 116, 8, 8, 1, 8, 1, 8, 3, 8, 120, 8, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, + 9, 1, 9, 1, 9, 3, 9, 129, 8, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, + 1, 11, 5, 11, 138, 8, 11, 10, 11, 12, 11, 141, 9, 11, 1, 11, 3, 11, 144, + 8, 11, 1, 11, 1, 11, 5, 11, 148, 8, 11, 10, 11, 12, 11, 151, 9, 11, 1, + 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 5, 12, 159, 8, 12, 10, 12, 12, 12, + 162, 9, 12, 3, 12, 164, 8, 12, 1, 12, 1, 12, 1, 12, 5, 12, 169, 8, 12, + 10, 12, 12, 12, 172, 9, 12, 3, 12, 174, 8, 12, 1, 13, 1, 13, 1, 13, 1, + 13, 1, 13, 1, 14, 3, 14, 182, 8, 14, 1, 14, 1, 14, 3, 14, 186, 8, 14, 1, + 14, 1, 14, 3, 14, 190, 8, 14, 1, 14, 1, 14, 3, 14, 194, 8, 14, 1, 15, 1, + 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, + 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, + 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 3, 15, 224, 8, 15, 1, 16, 1, 16, + 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 5, 16, 236, 8, + 16, 10, 16, 12, 16, 239, 9, 16, 1, 16, 3, 16, 242, 8, 16, 1, 16, 1, 16, + 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 5, 16, 251, 8, 16, 10, 16, 12, 16, 254, + 9, 16, 1, 16, 1, 16, 1, 16, 1, 16, 3, 16, 260, 8, 16, 1, 16, 1, 16, 1, + 16, 1, 16, 1, 16, 1, 16, 1, 16, 5, 16, 269, 8, 16, 10, 16, 12, 16, 272, + 9, 16, 1, 16, 1, 16, 1, 16, 1, 16, 3, 16, 278, 8, 16, 1, 16, 1, 16, 1, + 16, 1, 16, 1, 16, 1, 16, 5, 16, 286, 8, 16, 10, 16, 12, 16, 289, 9, 16, + 1, 16, 1, 16, 3, 16, 293, 8, 16, 3, 16, 295, 8, 16, 1, 16, 1, 16, 1, 16, + 1, 16, 1, 16, 1, 16, 5, 16, 303, 8, 16, 10, 16, 12, 16, 306, 9, 16, 1, + 16, 3, 16, 309, 8, 16, 1, 16, 3, 16, 312, 8, 16, 1, 17, 1, 17, 1, 17, 3, + 17, 317, 8, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 324, 8, 17, 1, + 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 5, 19, 332, 8, 19, 10, 19, 12, 19, + 335, 9, 19, 1, 19, 3, 19, 338, 8, 19, 1, 19, 1, 19, 1, 19, 0, 0, 20, 0, + 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, + 0, 3, 2, 0, 10, 10, 31, 31, 3, 0, 5, 5, 39, 40, 43, 43, 2, 0, 23, 24, 27, + 30, 375, 0, 40, 1, 0, 0, 0, 2, 44, 1, 0, 0, 0, 4, 59, 1, 0, 0, 0, 6, 66, + 1, 0, 0, 0, 8, 73, 1, 0, 0, 0, 10, 92, 1, 0, 0, 0, 12, 95, 1, 0, 0, 0, + 14, 110, 1, 0, 0, 0, 16, 113, 1, 0, 0, 0, 18, 123, 1, 0, 0, 0, 20, 132, + 1, 0, 0, 0, 22, 134, 1, 0, 0, 0, 24, 173, 1, 0, 0, 0, 26, 175, 1, 0, 0, + 0, 28, 193, 1, 0, 0, 0, 30, 223, 1, 0, 0, 0, 32, 311, 1, 0, 0, 0, 34, 323, + 1, 0, 0, 0, 36, 325, 1, 0, 0, 0, 38, 327, 1, 0, 0, 0, 40, 41, 3, 2, 1, + 0, 41, 42, 5, 0, 0, 1, 42, 1, 1, 0, 0, 0, 43, 45, 3, 4, 2, 0, 44, 43, 1, + 0, 0, 0, 44, 45, 1, 0, 0, 0, 45, 49, 1, 0, 0, 0, 46, 48, 3, 6, 3, 0, 47, + 46, 1, 0, 0, 0, 48, 51, 1, 0, 0, 0, 49, 47, 1, 0, 0, 0, 49, 50, 1, 0, 0, + 0, 50, 56, 1, 0, 0, 0, 51, 49, 1, 0, 0, 0, 52, 55, 3, 8, 4, 0, 53, 55, + 3, 16, 8, 0, 54, 52, 1, 0, 0, 0, 54, 53, 1, 0, 0, 0, 55, 58, 1, 0, 0, 0, + 56, 54, 1, 0, 0, 0, 56, 57, 1, 0, 0, 0, 57, 3, 1, 0, 0, 0, 58, 56, 1, 0, + 0, 0, 59, 60, 5, 11, 0, 0, 60, 62, 5, 44, 0, 0, 61, 63, 3, 38, 19, 0, 62, + 61, 1, 0, 0, 0, 62, 63, 1, 0, 0, 0, 63, 64, 1, 0, 0, 0, 64, 65, 5, 26, + 0, 0, 65, 5, 1, 0, 0, 0, 66, 67, 5, 12, 0, 0, 67, 69, 5, 44, 0, 0, 68, + 70, 3, 38, 19, 0, 69, 68, 1, 0, 0, 0, 69, 70, 1, 0, 0, 0, 70, 71, 1, 0, + 0, 0, 71, 72, 5, 26, 0, 0, 72, 7, 1, 0, 0, 0, 73, 74, 5, 13, 0, 0, 74, + 76, 3, 36, 18, 0, 75, 77, 5, 1, 0, 0, 76, 75, 1, 0, 0, 0, 76, 77, 1, 0, + 0, 0, 77, 79, 1, 0, 0, 0, 78, 80, 3, 10, 5, 0, 79, 78, 1, 0, 0, 0, 79, + 80, 1, 0, 0, 0, 80, 84, 1, 0, 0, 0, 81, 83, 3, 12, 6, 0, 82, 81, 1, 0, + 0, 0, 83, 86, 1, 0, 0, 0, 84, 82, 1, 0, 0, 0, 84, 85, 1, 0, 0, 0, 85, 88, + 1, 0, 0, 0, 86, 84, 1, 0, 0, 0, 87, 89, 3, 14, 7, 0, 88, 87, 1, 0, 0, 0, + 88, 89, 1, 0, 0, 0, 89, 90, 1, 0, 0, 0, 90, 91, 5, 2, 0, 0, 91, 9, 1, 0, + 0, 0, 92, 93, 5, 3, 0, 0, 93, 94, 3, 38, 19, 0, 94, 11, 1, 0, 0, 0, 95, + 96, 5, 14, 0, 0, 96, 102, 5, 19, 0, 0, 97, 98, 3, 32, 16, 0, 98, 99, 5, + 25, 0, 0, 99, 101, 1, 0, 0, 0, 100, 97, 1, 0, 0, 0, 101, 104, 1, 0, 0, + 0, 102, 100, 1, 0, 0, 0, 102, 103, 1, 0, 0, 0, 103, 106, 1, 0, 0, 0, 104, + 102, 1, 0, 0, 0, 105, 107, 3, 32, 16, 0, 106, 105, 1, 0, 0, 0, 106, 107, + 1, 0, 0, 0, 107, 108, 1, 0, 0, 0, 108, 109, 5, 20, 0, 0, 109, 13, 1, 0, + 0, 0, 110, 111, 5, 4, 0, 0, 111, 112, 3, 38, 19, 0, 112, 15, 1, 0, 0, 0, + 113, 115, 3, 36, 18, 0, 114, 116, 3, 18, 9, 0, 115, 114, 1, 0, 0, 0, 115, + 116, 1, 0, 0, 0, 116, 119, 1, 0, 0, 0, 117, 118, 7, 0, 0, 0, 118, 120, + 3, 22, 11, 0, 119, 117, 1, 0, 0, 0, 119, 120, 1, 0, 0, 0, 120, 121, 1, + 0, 0, 0, 121, 122, 5, 2, 0, 0, 122, 17, 1, 0, 0, 0, 123, 124, 5, 34, 0, + 0, 124, 125, 5, 19, 0, 0, 125, 128, 3, 20, 10, 0, 126, 127, 5, 25, 0, 0, + 127, 129, 3, 20, 10, 0, 128, 126, 1, 0, 0, 0, 128, 129, 1, 0, 0, 0, 129, + 130, 1, 0, 0, 0, 130, 131, 5, 20, 0, 0, 131, 19, 1, 0, 0, 0, 132, 133, + 7, 1, 0, 0, 133, 21, 1, 0, 0, 0, 134, 139, 3, 28, 14, 0, 135, 136, 5, 25, + 0, 0, 136, 138, 3, 28, 14, 0, 137, 135, 1, 0, 0, 0, 138, 141, 1, 0, 0, + 0, 139, 137, 1, 0, 0, 0, 139, 140, 1, 0, 0, 0, 140, 143, 1, 0, 0, 0, 141, + 139, 1, 0, 0, 0, 142, 144, 5, 25, 0, 0, 143, 142, 1, 0, 0, 0, 143, 144, + 1, 0, 0, 0, 144, 149, 1, 0, 0, 0, 145, 146, 5, 33, 0, 0, 146, 148, 3, 24, + 12, 0, 147, 145, 1, 0, 0, 0, 148, 151, 1, 0, 0, 0, 149, 147, 1, 0, 0, 0, + 149, 150, 1, 0, 0, 0, 150, 23, 1, 0, 0, 0, 151, 149, 1, 0, 0, 0, 152, 153, + 5, 16, 0, 0, 153, 163, 3, 32, 16, 0, 154, 155, 5, 25, 0, 0, 155, 160, 3, + 26, 13, 0, 156, 157, 5, 25, 0, 0, 157, 159, 3, 26, 13, 0, 158, 156, 1, + 0, 0, 0, 159, 162, 1, 0, 0, 0, 160, 158, 1, 0, 0, 0, 160, 161, 1, 0, 0, + 0, 161, 164, 1, 0, 0, 0, 162, 160, 1, 0, 0, 0, 163, 154, 1, 0, 0, 0, 163, + 164, 1, 0, 0, 0, 164, 174, 1, 0, 0, 0, 165, 170, 3, 26, 13, 0, 166, 167, + 5, 25, 0, 0, 167, 169, 3, 26, 13, 0, 168, 166, 1, 0, 0, 0, 169, 172, 1, + 0, 0, 0, 170, 168, 1, 0, 0, 0, 170, 171, 1, 0, 0, 0, 171, 174, 1, 0, 0, + 0, 172, 170, 1, 0, 0, 0, 173, 152, 1, 0, 0, 0, 173, 165, 1, 0, 0, 0, 174, + 25, 1, 0, 0, 0, 175, 176, 5, 15, 0, 0, 176, 177, 5, 43, 0, 0, 177, 178, + 5, 23, 0, 0, 178, 179, 3, 32, 16, 0, 179, 27, 1, 0, 0, 0, 180, 182, 3, + 30, 15, 0, 181, 180, 1, 0, 0, 0, 181, 182, 1, 0, 0, 0, 182, 183, 1, 0, + 0, 0, 183, 185, 3, 32, 16, 0, 184, 186, 3, 18, 9, 0, 185, 184, 1, 0, 0, + 0, 185, 186, 1, 0, 0, 0, 186, 189, 1, 0, 0, 0, 187, 188, 7, 2, 0, 0, 188, + 190, 3, 32, 16, 0, 189, 187, 1, 0, 0, 0, 189, 190, 1, 0, 0, 0, 190, 194, + 1, 0, 0, 0, 191, 192, 5, 26, 0, 0, 192, 194, 3, 32, 16, 0, 193, 181, 1, + 0, 0, 0, 193, 191, 1, 0, 0, 0, 194, 29, 1, 0, 0, 0, 195, 196, 5, 35, 0, + 0, 196, 197, 5, 19, 0, 0, 197, 198, 3, 20, 10, 0, 198, 199, 5, 25, 0, 0, + 199, 200, 3, 20, 10, 0, 200, 201, 5, 20, 0, 0, 201, 224, 1, 0, 0, 0, 202, + 203, 5, 37, 0, 0, 203, 204, 5, 19, 0, 0, 204, 205, 3, 20, 10, 0, 205, 206, + 5, 25, 0, 0, 206, 207, 3, 20, 10, 0, 207, 208, 5, 20, 0, 0, 208, 224, 1, + 0, 0, 0, 209, 210, 5, 36, 0, 0, 210, 211, 5, 19, 0, 0, 211, 212, 3, 20, + 10, 0, 212, 213, 5, 25, 0, 0, 213, 214, 3, 20, 10, 0, 214, 215, 5, 20, + 0, 0, 215, 224, 1, 0, 0, 0, 216, 217, 5, 38, 0, 0, 217, 218, 5, 19, 0, + 0, 218, 219, 3, 20, 10, 0, 219, 220, 5, 25, 0, 0, 220, 221, 3, 20, 10, + 0, 221, 222, 5, 20, 0, 0, 222, 224, 1, 0, 0, 0, 223, 195, 1, 0, 0, 0, 223, + 202, 1, 0, 0, 0, 223, 209, 1, 0, 0, 0, 223, 216, 1, 0, 0, 0, 224, 31, 1, + 0, 0, 0, 225, 312, 5, 43, 0, 0, 226, 312, 5, 47, 0, 0, 227, 312, 5, 41, + 0, 0, 228, 312, 5, 42, 0, 0, 229, 312, 5, 48, 0, 0, 230, 312, 5, 49, 0, + 0, 231, 237, 5, 19, 0, 0, 232, 233, 3, 32, 16, 0, 233, 234, 5, 25, 0, 0, + 234, 236, 1, 0, 0, 0, 235, 232, 1, 0, 0, 0, 236, 239, 1, 0, 0, 0, 237, + 235, 1, 0, 0, 0, 237, 238, 1, 0, 0, 0, 238, 241, 1, 0, 0, 0, 239, 237, + 1, 0, 0, 0, 240, 242, 3, 32, 16, 0, 241, 240, 1, 0, 0, 0, 241, 242, 1, + 0, 0, 0, 242, 243, 1, 0, 0, 0, 243, 312, 5, 20, 0, 0, 244, 252, 5, 19, + 0, 0, 245, 246, 3, 32, 16, 0, 246, 247, 5, 6, 0, 0, 247, 248, 3, 32, 16, + 0, 248, 249, 5, 25, 0, 0, 249, 251, 1, 0, 0, 0, 250, 245, 1, 0, 0, 0, 251, + 254, 1, 0, 0, 0, 252, 250, 1, 0, 0, 0, 252, 253, 1, 0, 0, 0, 253, 259, + 1, 0, 0, 0, 254, 252, 1, 0, 0, 0, 255, 256, 3, 32, 16, 0, 256, 257, 5, + 6, 0, 0, 257, 258, 3, 32, 16, 0, 258, 260, 1, 0, 0, 0, 259, 255, 1, 0, + 0, 0, 259, 260, 1, 0, 0, 0, 260, 261, 1, 0, 0, 0, 261, 312, 5, 20, 0, 0, + 262, 270, 5, 21, 0, 0, 263, 264, 3, 32, 16, 0, 264, 265, 5, 6, 0, 0, 265, + 266, 3, 32, 16, 0, 266, 267, 5, 25, 0, 0, 267, 269, 1, 0, 0, 0, 268, 263, + 1, 0, 0, 0, 269, 272, 1, 0, 0, 0, 270, 268, 1, 0, 0, 0, 270, 271, 1, 0, + 0, 0, 271, 277, 1, 0, 0, 0, 272, 270, 1, 0, 0, 0, 273, 274, 3, 32, 16, + 0, 274, 275, 5, 6, 0, 0, 275, 276, 3, 32, 16, 0, 276, 278, 1, 0, 0, 0, + 277, 273, 1, 0, 0, 0, 277, 278, 1, 0, 0, 0, 278, 279, 1, 0, 0, 0, 279, + 312, 5, 22, 0, 0, 280, 281, 5, 46, 0, 0, 281, 287, 5, 28, 0, 0, 282, 283, + 3, 34, 17, 0, 283, 284, 5, 25, 0, 0, 284, 286, 1, 0, 0, 0, 285, 282, 1, + 0, 0, 0, 286, 289, 1, 0, 0, 0, 287, 285, 1, 0, 0, 0, 287, 288, 1, 0, 0, + 0, 288, 294, 1, 0, 0, 0, 289, 287, 1, 0, 0, 0, 290, 292, 3, 34, 17, 0, + 291, 293, 5, 25, 0, 0, 292, 291, 1, 0, 0, 0, 292, 293, 1, 0, 0, 0, 293, + 295, 1, 0, 0, 0, 294, 290, 1, 0, 0, 0, 294, 295, 1, 0, 0, 0, 295, 296, + 1, 0, 0, 0, 296, 312, 5, 30, 0, 0, 297, 298, 5, 44, 0, 0, 298, 304, 5, + 17, 0, 0, 299, 300, 3, 32, 16, 0, 300, 301, 5, 25, 0, 0, 301, 303, 1, 0, + 0, 0, 302, 299, 1, 0, 0, 0, 303, 306, 1, 0, 0, 0, 304, 302, 1, 0, 0, 0, + 304, 305, 1, 0, 0, 0, 305, 308, 1, 0, 0, 0, 306, 304, 1, 0, 0, 0, 307, + 309, 3, 32, 16, 0, 308, 307, 1, 0, 0, 0, 308, 309, 1, 0, 0, 0, 309, 310, + 1, 0, 0, 0, 310, 312, 5, 18, 0, 0, 311, 225, 1, 0, 0, 0, 311, 226, 1, 0, + 0, 0, 311, 227, 1, 0, 0, 0, 311, 228, 1, 0, 0, 0, 311, 229, 1, 0, 0, 0, + 311, 230, 1, 0, 0, 0, 311, 231, 1, 0, 0, 0, 311, 244, 1, 0, 0, 0, 311, + 262, 1, 0, 0, 0, 311, 280, 1, 0, 0, 0, 311, 297, 1, 0, 0, 0, 312, 33, 1, + 0, 0, 0, 313, 316, 3, 32, 16, 0, 314, 315, 5, 6, 0, 0, 315, 317, 3, 32, + 16, 0, 316, 314, 1, 0, 0, 0, 316, 317, 1, 0, 0, 0, 317, 324, 1, 0, 0, 0, + 318, 319, 5, 7, 0, 0, 319, 320, 3, 32, 16, 0, 320, 321, 5, 6, 0, 0, 321, + 322, 3, 32, 16, 0, 322, 324, 1, 0, 0, 0, 323, 313, 1, 0, 0, 0, 323, 318, + 1, 0, 0, 0, 324, 35, 1, 0, 0, 0, 325, 326, 3, 32, 16, 0, 326, 37, 1, 0, + 0, 0, 327, 333, 5, 19, 0, 0, 328, 329, 3, 36, 18, 0, 329, 330, 5, 25, 0, + 0, 330, 332, 1, 0, 0, 0, 331, 328, 1, 0, 0, 0, 332, 335, 1, 0, 0, 0, 333, + 331, 1, 0, 0, 0, 333, 334, 1, 0, 0, 0, 334, 337, 1, 0, 0, 0, 335, 333, + 1, 0, 0, 0, 336, 338, 3, 36, 18, 0, 337, 336, 1, 0, 0, 0, 337, 338, 1, + 0, 0, 0, 338, 339, 1, 0, 0, 0, 339, 340, 5, 20, 0, 0, 340, 39, 1, 0, 0, + 0, 43, 44, 49, 54, 56, 62, 69, 76, 79, 84, 88, 102, 106, 115, 119, 128, + 139, 143, 149, 160, 163, 170, 173, 181, 185, 189, 193, 223, 237, 241, 252, + 259, 270, 277, 287, 292, 294, 304, 308, 311, 316, 323, 333, 337, } deserializer := antlr.NewATNDeserializer(nil) staticData.atn = deserializer.Deserialize(staticData.serializedATN) @@ -238,47 +268,59 @@ const ( MangleParserRPAREN = 18 MangleParserLBRACKET = 19 MangleParserRBRACKET = 20 - MangleParserEQ = 21 - MangleParserBANGEQ = 22 - MangleParserCOMMA = 23 - MangleParserBANG = 24 - MangleParserLESS = 25 - MangleParserLESSEQ = 26 - MangleParserGREATER = 27 - MangleParserGREATEREQ = 28 - MangleParserCOLONDASH = 29 - MangleParserNEWLINE = 30 - MangleParserPIPEGREATER = 31 - MangleParserNUMBER = 32 - MangleParserFLOAT = 33 - MangleParserVARIABLE = 34 - MangleParserNAME = 35 - MangleParserTYPENAME = 36 - MangleParserDOT_TYPE = 37 - MangleParserCONSTANT = 38 - MangleParserSTRING = 39 - MangleParserBYTESTRING = 40 + MangleParserLBRACE = 21 + MangleParserRBRACE = 22 + MangleParserEQ = 23 + MangleParserBANGEQ = 24 + MangleParserCOMMA = 25 + MangleParserBANG = 26 + MangleParserLESSEQ = 27 + MangleParserLESS = 28 + MangleParserGREATEREQ = 29 + MangleParserGREATER = 30 + MangleParserCOLONDASH = 31 + MangleParserNEWLINE = 32 + MangleParserPIPEGREATER = 33 + MangleParserAT = 34 + MangleParserDIAMONDMINUS = 35 + MangleParserDIAMONDPLUS = 36 + MangleParserBOXMINUS = 37 + MangleParserBOXPLUS = 38 + MangleParserTIMESTAMP = 39 + MangleParserDURATION = 40 + MangleParserNUMBER = 41 + MangleParserFLOAT = 42 + MangleParserVARIABLE = 43 + MangleParserNAME = 44 + MangleParserTYPENAME = 45 + MangleParserDOT_TYPE = 46 + MangleParserCONSTANT = 47 + MangleParserSTRING = 48 + MangleParserBYTESTRING = 49 ) // MangleParser rules. const ( - MangleParserRULE_start = 0 - MangleParserRULE_program = 1 - MangleParserRULE_packageDecl = 2 - MangleParserRULE_useDecl = 3 - MangleParserRULE_decl = 4 - MangleParserRULE_descrBlock = 5 - MangleParserRULE_boundsBlock = 6 - MangleParserRULE_constraintsBlock = 7 - MangleParserRULE_clause = 8 - MangleParserRULE_clauseBody = 9 - MangleParserRULE_transform = 10 - MangleParserRULE_letStmt = 11 - MangleParserRULE_literalOrFml = 12 - MangleParserRULE_term = 13 - MangleParserRULE_member = 14 - MangleParserRULE_atom = 15 - MangleParserRULE_atoms = 16 + MangleParserRULE_start = 0 + MangleParserRULE_program = 1 + MangleParserRULE_packageDecl = 2 + MangleParserRULE_useDecl = 3 + MangleParserRULE_decl = 4 + MangleParserRULE_descrBlock = 5 + MangleParserRULE_boundsBlock = 6 + MangleParserRULE_constraintsBlock = 7 + MangleParserRULE_clause = 8 + MangleParserRULE_temporalAnnotation = 9 + MangleParserRULE_temporalBound = 10 + MangleParserRULE_clauseBody = 11 + MangleParserRULE_transform = 12 + MangleParserRULE_letStmt = 13 + MangleParserRULE_literalOrFml = 14 + MangleParserRULE_temporalOperator = 15 + MangleParserRULE_term = 16 + MangleParserRULE_member = 17 + MangleParserRULE_atom = 18 + MangleParserRULE_atoms = 19 ) // IStartContext is an interface to support dynamic dispatch. @@ -383,11 +425,11 @@ func (p *MangleParser) Start_() (localctx IStartContext) { p.EnterRule(localctx, 0, MangleParserRULE_start) p.EnterOuterAlt(localctx, 1) { - p.SetState(34) + p.SetState(40) p.Program() } { - p.SetState(35) + p.SetState(41) p.Match(MangleParserEOF) if p.HasError() { // Recognition error - abort rule @@ -635,7 +677,7 @@ func (p *MangleParser) Program() (localctx IProgramContext) { var _la int p.EnterOuterAlt(localctx, 1) - p.SetState(38) + p.SetState(44) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -644,12 +686,12 @@ func (p *MangleParser) Program() (localctx IProgramContext) { if _la == MangleParserPACKAGE { { - p.SetState(37) + p.SetState(43) p.PackageDecl() } } - p.SetState(43) + p.SetState(49) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -658,26 +700,26 @@ func (p *MangleParser) Program() (localctx IProgramContext) { for _la == MangleParserUSE { { - p.SetState(40) + p.SetState(46) p.UseDecl() } - p.SetState(45) + p.SetState(51) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } _la = p.GetTokenStream().LA(1) } - p.SetState(50) + p.SetState(56) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } _la = p.GetTokenStream().LA(1) - for (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&2126009344032) != 0 { - p.SetState(48) + for (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&1088516514127872) != 0 { + p.SetState(54) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -686,13 +728,13 @@ func (p *MangleParser) Program() (localctx IProgramContext) { switch p.GetTokenStream().LA(1) { case MangleParserDECL: { - p.SetState(46) + p.SetState(52) p.Decl() } - case MangleParserT__4, MangleParserLBRACKET, MangleParserNUMBER, MangleParserFLOAT, MangleParserVARIABLE, MangleParserNAME, MangleParserDOT_TYPE, MangleParserCONSTANT, MangleParserSTRING, MangleParserBYTESTRING: + case MangleParserLBRACKET, MangleParserLBRACE, MangleParserNUMBER, MangleParserFLOAT, MangleParserVARIABLE, MangleParserNAME, MangleParserDOT_TYPE, MangleParserCONSTANT, MangleParserSTRING, MangleParserBYTESTRING: { - p.SetState(47) + p.SetState(53) p.Clause() } @@ -701,7 +743,7 @@ func (p *MangleParser) Program() (localctx IProgramContext) { goto errorExit } - p.SetState(52) + p.SetState(58) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -836,7 +878,7 @@ func (p *MangleParser) PackageDecl() (localctx IPackageDeclContext) { p.EnterOuterAlt(localctx, 1) { - p.SetState(53) + p.SetState(59) p.Match(MangleParserPACKAGE) if p.HasError() { // Recognition error - abort rule @@ -844,14 +886,14 @@ func (p *MangleParser) PackageDecl() (localctx IPackageDeclContext) { } } { - p.SetState(54) + p.SetState(60) p.Match(MangleParserNAME) if p.HasError() { // Recognition error - abort rule goto errorExit } } - p.SetState(56) + p.SetState(62) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -860,13 +902,13 @@ func (p *MangleParser) PackageDecl() (localctx IPackageDeclContext) { if _la == MangleParserLBRACKET { { - p.SetState(55) + p.SetState(61) p.Atoms() } } { - p.SetState(58) + p.SetState(64) p.Match(MangleParserBANG) if p.HasError() { // Recognition error - abort rule @@ -1001,7 +1043,7 @@ func (p *MangleParser) UseDecl() (localctx IUseDeclContext) { p.EnterOuterAlt(localctx, 1) { - p.SetState(60) + p.SetState(66) p.Match(MangleParserUSE) if p.HasError() { // Recognition error - abort rule @@ -1009,14 +1051,14 @@ func (p *MangleParser) UseDecl() (localctx IUseDeclContext) { } } { - p.SetState(61) + p.SetState(67) p.Match(MangleParserNAME) if p.HasError() { // Recognition error - abort rule goto errorExit } } - p.SetState(63) + p.SetState(69) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -1025,13 +1067,13 @@ func (p *MangleParser) UseDecl() (localctx IUseDeclContext) { if _la == MangleParserLBRACKET { { - p.SetState(62) + p.SetState(68) p.Atoms() } } { - p.SetState(65) + p.SetState(71) p.Match(MangleParserBANG) if p.HasError() { // Recognition error - abort rule @@ -1233,7 +1275,7 @@ func (p *MangleParser) Decl() (localctx IDeclContext) { p.EnterOuterAlt(localctx, 1) { - p.SetState(67) + p.SetState(73) p.Match(MangleParserDECL) if p.HasError() { // Recognition error - abort rule @@ -1241,24 +1283,42 @@ func (p *MangleParser) Decl() (localctx IDeclContext) { } } { - p.SetState(68) + p.SetState(74) p.Atom() } - p.SetState(70) + p.SetState(76) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == MangleParserT__0 { + { + p.SetState(75) + p.Match(MangleParserT__0) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + p.SetState(79) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } _la = p.GetTokenStream().LA(1) - if _la == MangleParserT__1 { + if _la == MangleParserT__2 { { - p.SetState(69) + p.SetState(78) p.DescrBlock() } } - p.SetState(75) + p.SetState(84) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -1267,34 +1327,34 @@ func (p *MangleParser) Decl() (localctx IDeclContext) { for _la == MangleParserBOUND { { - p.SetState(72) + p.SetState(81) p.BoundsBlock() } - p.SetState(77) + p.SetState(86) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } _la = p.GetTokenStream().LA(1) } - p.SetState(79) + p.SetState(88) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } _la = p.GetTokenStream().LA(1) - if _la == MangleParserT__2 { + if _la == MangleParserT__3 { { - p.SetState(78) + p.SetState(87) p.ConstraintsBlock() } } { - p.SetState(81) - p.Match(MangleParserT__0) + p.SetState(90) + p.Match(MangleParserT__1) if p.HasError() { // Recognition error - abort rule goto errorExit @@ -1411,15 +1471,15 @@ func (p *MangleParser) DescrBlock() (localctx IDescrBlockContext) { p.EnterRule(localctx, 10, MangleParserRULE_descrBlock) p.EnterOuterAlt(localctx, 1) { - p.SetState(83) - p.Match(MangleParserT__1) + p.SetState(92) + p.Match(MangleParserT__2) if p.HasError() { // Recognition error - abort rule goto errorExit } } { - p.SetState(84) + p.SetState(93) p.Atoms() } @@ -1588,7 +1648,7 @@ func (p *MangleParser) BoundsBlock() (localctx IBoundsBlockContext) { p.EnterOuterAlt(localctx, 1) { - p.SetState(86) + p.SetState(95) p.Match(MangleParserBOUND) if p.HasError() { // Recognition error - abort rule @@ -1596,30 +1656,30 @@ func (p *MangleParser) BoundsBlock() (localctx IBoundsBlockContext) { } } { - p.SetState(87) + p.SetState(96) p.Match(MangleParserLBRACKET) if p.HasError() { // Recognition error - abort rule goto errorExit } } - p.SetState(93) + p.SetState(102) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } - _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 9, p.GetParserRuleContext()) + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 10, p.GetParserRuleContext()) if p.HasError() { goto errorExit } for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { if _alt == 1 { { - p.SetState(88) + p.SetState(97) p.Term() } { - p.SetState(89) + p.SetState(98) p.Match(MangleParserCOMMA) if p.HasError() { // Recognition error - abort rule @@ -1628,32 +1688,32 @@ func (p *MangleParser) BoundsBlock() (localctx IBoundsBlockContext) { } } - p.SetState(95) + p.SetState(104) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } - _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 9, p.GetParserRuleContext()) + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 10, p.GetParserRuleContext()) if p.HasError() { goto errorExit } } - p.SetState(97) + p.SetState(106) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } _la = p.GetTokenStream().LA(1) - if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&2126009335840) != 0 { + if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&1088516514119680) != 0 { { - p.SetState(96) + p.SetState(105) p.Term() } } { - p.SetState(99) + p.SetState(108) p.Match(MangleParserRBRACKET) if p.HasError() { // Recognition error - abort rule @@ -1771,15 +1831,15 @@ func (p *MangleParser) ConstraintsBlock() (localctx IConstraintsBlockContext) { p.EnterRule(localctx, 14, MangleParserRULE_constraintsBlock) p.EnterOuterAlt(localctx, 1) { - p.SetState(101) - p.Match(MangleParserT__2) + p.SetState(110) + p.Match(MangleParserT__3) if p.HasError() { // Recognition error - abort rule goto errorExit } } { - p.SetState(102) + p.SetState(111) p.Atoms() } @@ -1805,6 +1865,7 @@ type IClauseContext interface { // Getter signatures Atom() IAtomContext + TemporalAnnotation() ITemporalAnnotationContext ClauseBody() IClauseBodyContext COLONDASH() antlr.TerminalNode LONGLEFTDOUBLEARROW() antlr.TerminalNode @@ -1861,6 +1922,22 @@ func (s *ClauseContext) Atom() IAtomContext { return t.(IAtomContext) } +func (s *ClauseContext) TemporalAnnotation() ITemporalAnnotationContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITemporalAnnotationContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(ITemporalAnnotationContext) +} + func (s *ClauseContext) ClauseBody() IClauseBodyContext { var t antlr.RuleContext for _, ctx := range s.GetChildren() { @@ -1922,10 +1999,24 @@ func (p *MangleParser) Clause() (localctx IClauseContext) { p.EnterOuterAlt(localctx, 1) { - p.SetState(104) + p.SetState(113) p.Atom() } - p.SetState(107) + p.SetState(115) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == MangleParserAT { + { + p.SetState(114) + p.TemporalAnnotation() + } + + } + p.SetState(119) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -1934,7 +2025,7 @@ func (p *MangleParser) Clause() (localctx IClauseContext) { if _la == MangleParserLONGLEFTDOUBLEARROW || _la == MangleParserCOLONDASH { { - p.SetState(105) + p.SetState(117) _la = p.GetTokenStream().LA(1) if !(_la == MangleParserLONGLEFTDOUBLEARROW || _la == MangleParserCOLONDASH) { @@ -1945,14 +2036,14 @@ func (p *MangleParser) Clause() (localctx IClauseContext) { } } { - p.SetState(106) + p.SetState(118) p.ClauseBody() } } { - p.SetState(109) - p.Match(MangleParserT__0) + p.SetState(121) + p.Match(MangleParserT__1) if p.HasError() { // Recognition error - abort rule goto errorExit @@ -1972,130 +2063,79 @@ errorExit: goto errorExit // Trick to prevent compiler error if the label is not used } -// IClauseBodyContext is an interface to support dynamic dispatch. -type IClauseBodyContext interface { +// ITemporalAnnotationContext is an interface to support dynamic dispatch. +type ITemporalAnnotationContext interface { antlr.ParserRuleContext // GetParser returns the parser. GetParser() antlr.Parser // Getter signatures - AllLiteralOrFml() []ILiteralOrFmlContext - LiteralOrFml(i int) ILiteralOrFmlContext - AllCOMMA() []antlr.TerminalNode - COMMA(i int) antlr.TerminalNode - AllPIPEGREATER() []antlr.TerminalNode - PIPEGREATER(i int) antlr.TerminalNode - AllTransform() []ITransformContext - Transform(i int) ITransformContext + AT() antlr.TerminalNode + LBRACKET() antlr.TerminalNode + AllTemporalBound() []ITemporalBoundContext + TemporalBound(i int) ITemporalBoundContext + RBRACKET() antlr.TerminalNode + COMMA() antlr.TerminalNode - // IsClauseBodyContext differentiates from other interfaces. - IsClauseBodyContext() + // IsTemporalAnnotationContext differentiates from other interfaces. + IsTemporalAnnotationContext() } -type ClauseBodyContext struct { +type TemporalAnnotationContext struct { antlr.BaseParserRuleContext parser antlr.Parser } -func NewEmptyClauseBodyContext() *ClauseBodyContext { - var p = new(ClauseBodyContext) +func NewEmptyTemporalAnnotationContext() *TemporalAnnotationContext { + var p = new(TemporalAnnotationContext) antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) - p.RuleIndex = MangleParserRULE_clauseBody + p.RuleIndex = MangleParserRULE_temporalAnnotation return p } -func InitEmptyClauseBodyContext(p *ClauseBodyContext) { +func InitEmptyTemporalAnnotationContext(p *TemporalAnnotationContext) { antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) - p.RuleIndex = MangleParserRULE_clauseBody + p.RuleIndex = MangleParserRULE_temporalAnnotation } -func (*ClauseBodyContext) IsClauseBodyContext() {} +func (*TemporalAnnotationContext) IsTemporalAnnotationContext() {} -func NewClauseBodyContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ClauseBodyContext { - var p = new(ClauseBodyContext) +func NewTemporalAnnotationContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TemporalAnnotationContext { + var p = new(TemporalAnnotationContext) antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) p.parser = parser - p.RuleIndex = MangleParserRULE_clauseBody + p.RuleIndex = MangleParserRULE_temporalAnnotation return p } -func (s *ClauseBodyContext) GetParser() antlr.Parser { return s.parser } - -func (s *ClauseBodyContext) AllLiteralOrFml() []ILiteralOrFmlContext { - children := s.GetChildren() - len := 0 - for _, ctx := range children { - if _, ok := ctx.(ILiteralOrFmlContext); ok { - len++ - } - } - - tst := make([]ILiteralOrFmlContext, len) - i := 0 - for _, ctx := range children { - if t, ok := ctx.(ILiteralOrFmlContext); ok { - tst[i] = t.(ILiteralOrFmlContext) - i++ - } - } - - return tst -} - -func (s *ClauseBodyContext) LiteralOrFml(i int) ILiteralOrFmlContext { - var t antlr.RuleContext - j := 0 - for _, ctx := range s.GetChildren() { - if _, ok := ctx.(ILiteralOrFmlContext); ok { - if j == i { - t = ctx.(antlr.RuleContext) - break - } - j++ - } - } - - if t == nil { - return nil - } - - return t.(ILiteralOrFmlContext) -} - -func (s *ClauseBodyContext) AllCOMMA() []antlr.TerminalNode { - return s.GetTokens(MangleParserCOMMA) -} - -func (s *ClauseBodyContext) COMMA(i int) antlr.TerminalNode { - return s.GetToken(MangleParserCOMMA, i) -} +func (s *TemporalAnnotationContext) GetParser() antlr.Parser { return s.parser } -func (s *ClauseBodyContext) AllPIPEGREATER() []antlr.TerminalNode { - return s.GetTokens(MangleParserPIPEGREATER) +func (s *TemporalAnnotationContext) AT() antlr.TerminalNode { + return s.GetToken(MangleParserAT, 0) } -func (s *ClauseBodyContext) PIPEGREATER(i int) antlr.TerminalNode { - return s.GetToken(MangleParserPIPEGREATER, i) +func (s *TemporalAnnotationContext) LBRACKET() antlr.TerminalNode { + return s.GetToken(MangleParserLBRACKET, 0) } -func (s *ClauseBodyContext) AllTransform() []ITransformContext { +func (s *TemporalAnnotationContext) AllTemporalBound() []ITemporalBoundContext { children := s.GetChildren() len := 0 for _, ctx := range children { - if _, ok := ctx.(ITransformContext); ok { + if _, ok := ctx.(ITemporalBoundContext); ok { len++ } } - tst := make([]ITransformContext, len) + tst := make([]ITemporalBoundContext, len) i := 0 for _, ctx := range children { - if t, ok := ctx.(ITransformContext); ok { - tst[i] = t.(ITransformContext) + if t, ok := ctx.(ITemporalBoundContext); ok { + tst[i] = t.(ITemporalBoundContext) i++ } } @@ -2103,11 +2143,11 @@ func (s *ClauseBodyContext) AllTransform() []ITransformContext { return tst } -func (s *ClauseBodyContext) Transform(i int) ITransformContext { +func (s *TemporalAnnotationContext) TemporalBound(i int) ITemporalBoundContext { var t antlr.RuleContext j := 0 for _, ctx := range s.GetChildren() { - if _, ok := ctx.(ITransformContext); ok { + if _, ok := ctx.(ITemporalBoundContext); ok { if j == i { t = ctx.(antlr.RuleContext) break @@ -2120,87 +2160,74 @@ func (s *ClauseBodyContext) Transform(i int) ITransformContext { return nil } - return t.(ITransformContext) + return t.(ITemporalBoundContext) } -func (s *ClauseBodyContext) GetRuleContext() antlr.RuleContext { +func (s *TemporalAnnotationContext) RBRACKET() antlr.TerminalNode { + return s.GetToken(MangleParserRBRACKET, 0) +} + +func (s *TemporalAnnotationContext) COMMA() antlr.TerminalNode { + return s.GetToken(MangleParserCOMMA, 0) +} + +func (s *TemporalAnnotationContext) GetRuleContext() antlr.RuleContext { return s } -func (s *ClauseBodyContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { +func (s *TemporalAnnotationContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { return antlr.TreesStringTree(s, ruleNames, recog) } -func (s *ClauseBodyContext) EnterRule(listener antlr.ParseTreeListener) { +func (s *TemporalAnnotationContext) EnterRule(listener antlr.ParseTreeListener) { if listenerT, ok := listener.(MangleListener); ok { - listenerT.EnterClauseBody(s) + listenerT.EnterTemporalAnnotation(s) } } -func (s *ClauseBodyContext) ExitRule(listener antlr.ParseTreeListener) { +func (s *TemporalAnnotationContext) ExitRule(listener antlr.ParseTreeListener) { if listenerT, ok := listener.(MangleListener); ok { - listenerT.ExitClauseBody(s) + listenerT.ExitTemporalAnnotation(s) } } -func (s *ClauseBodyContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { +func (s *TemporalAnnotationContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { switch t := visitor.(type) { case MangleVisitor: - return t.VisitClauseBody(s) + return t.VisitTemporalAnnotation(s) default: return t.VisitChildren(s) } } -func (p *MangleParser) ClauseBody() (localctx IClauseBodyContext) { - localctx = NewClauseBodyContext(p, p.GetParserRuleContext(), p.GetState()) - p.EnterRule(localctx, 18, MangleParserRULE_clauseBody) +func (p *MangleParser) TemporalAnnotation() (localctx ITemporalAnnotationContext) { + localctx = NewTemporalAnnotationContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 18, MangleParserRULE_temporalAnnotation) var _la int - var _alt int - p.EnterOuterAlt(localctx, 1) { - p.SetState(111) - p.LiteralOrFml() - } - p.SetState(116) - p.GetErrorHandler().Sync(p) - if p.HasError() { - goto errorExit - } - _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 12, p.GetParserRuleContext()) - if p.HasError() { - goto errorExit - } - for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { - if _alt == 1 { - { - p.SetState(112) - p.Match(MangleParserCOMMA) - if p.HasError() { - // Recognition error - abort rule - goto errorExit - } - } - { - p.SetState(113) - p.LiteralOrFml() - } - - } - p.SetState(118) - p.GetErrorHandler().Sync(p) + p.SetState(123) + p.Match(MangleParserAT) if p.HasError() { + // Recognition error - abort rule goto errorExit } - _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 12, p.GetParserRuleContext()) + } + { + p.SetState(124) + p.Match(MangleParserLBRACKET) if p.HasError() { + // Recognition error - abort rule goto errorExit } } - p.SetState(120) + { + p.SetState(125) + p.TemporalBound() + } + p.SetState(128) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -2209,42 +2236,26 @@ func (p *MangleParser) ClauseBody() (localctx IClauseBodyContext) { if _la == MangleParserCOMMA { { - p.SetState(119) + p.SetState(126) p.Match(MangleParserCOMMA) if p.HasError() { // Recognition error - abort rule goto errorExit } } - - } - p.SetState(126) - p.GetErrorHandler().Sync(p) - if p.HasError() { - goto errorExit - } - _la = p.GetTokenStream().LA(1) - - for _la == MangleParserPIPEGREATER { - { - p.SetState(122) - p.Match(MangleParserPIPEGREATER) - if p.HasError() { - // Recognition error - abort rule - goto errorExit - } - } { - p.SetState(123) - p.Transform() + p.SetState(127) + p.TemporalBound() } - p.SetState(128) - p.GetErrorHandler().Sync(p) + } + { + p.SetState(130) + p.Match(MangleParserRBRACKET) if p.HasError() { + // Recognition error - abort rule goto errorExit } - _la = p.GetTokenStream().LA(1) } errorExit: @@ -2260,67 +2271,476 @@ errorExit: goto errorExit // Trick to prevent compiler error if the label is not used } -// ITransformContext is an interface to support dynamic dispatch. -type ITransformContext interface { +// ITemporalBoundContext is an interface to support dynamic dispatch. +type ITemporalBoundContext interface { antlr.ParserRuleContext // GetParser returns the parser. GetParser() antlr.Parser // Getter signatures - DO() antlr.TerminalNode - Term() ITermContext - AllCOMMA() []antlr.TerminalNode - COMMA(i int) antlr.TerminalNode - AllLetStmt() []ILetStmtContext - LetStmt(i int) ILetStmtContext + TIMESTAMP() antlr.TerminalNode + DURATION() antlr.TerminalNode + VARIABLE() antlr.TerminalNode - // IsTransformContext differentiates from other interfaces. - IsTransformContext() + // IsTemporalBoundContext differentiates from other interfaces. + IsTemporalBoundContext() } -type TransformContext struct { +type TemporalBoundContext struct { antlr.BaseParserRuleContext parser antlr.Parser } -func NewEmptyTransformContext() *TransformContext { - var p = new(TransformContext) +func NewEmptyTemporalBoundContext() *TemporalBoundContext { + var p = new(TemporalBoundContext) antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) - p.RuleIndex = MangleParserRULE_transform + p.RuleIndex = MangleParserRULE_temporalBound return p } -func InitEmptyTransformContext(p *TransformContext) { +func InitEmptyTemporalBoundContext(p *TemporalBoundContext) { antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) - p.RuleIndex = MangleParserRULE_transform + p.RuleIndex = MangleParserRULE_temporalBound } -func (*TransformContext) IsTransformContext() {} +func (*TemporalBoundContext) IsTemporalBoundContext() {} -func NewTransformContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TransformContext { - var p = new(TransformContext) +func NewTemporalBoundContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TemporalBoundContext { + var p = new(TemporalBoundContext) antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) p.parser = parser - p.RuleIndex = MangleParserRULE_transform + p.RuleIndex = MangleParserRULE_temporalBound return p } -func (s *TransformContext) GetParser() antlr.Parser { return s.parser } +func (s *TemporalBoundContext) GetParser() antlr.Parser { return s.parser } -func (s *TransformContext) DO() antlr.TerminalNode { - return s.GetToken(MangleParserDO, 0) +func (s *TemporalBoundContext) TIMESTAMP() antlr.TerminalNode { + return s.GetToken(MangleParserTIMESTAMP, 0) } -func (s *TransformContext) Term() ITermContext { - var t antlr.RuleContext - for _, ctx := range s.GetChildren() { - if _, ok := ctx.(ITermContext); ok { - t = ctx.(antlr.RuleContext) - break +func (s *TemporalBoundContext) DURATION() antlr.TerminalNode { + return s.GetToken(MangleParserDURATION, 0) +} + +func (s *TemporalBoundContext) VARIABLE() antlr.TerminalNode { + return s.GetToken(MangleParserVARIABLE, 0) +} + +func (s *TemporalBoundContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TemporalBoundContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *TemporalBoundContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MangleListener); ok { + listenerT.EnterTemporalBound(s) + } +} + +func (s *TemporalBoundContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MangleListener); ok { + listenerT.ExitTemporalBound(s) + } +} + +func (s *TemporalBoundContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case MangleVisitor: + return t.VisitTemporalBound(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *MangleParser) TemporalBound() (localctx ITemporalBoundContext) { + localctx = NewTemporalBoundContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 20, MangleParserRULE_temporalBound) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(132) + _la = p.GetTokenStream().LA(1) + + if !((int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&10445360463904) != 0) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IClauseBodyContext is an interface to support dynamic dispatch. +type IClauseBodyContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllLiteralOrFml() []ILiteralOrFmlContext + LiteralOrFml(i int) ILiteralOrFmlContext + AllCOMMA() []antlr.TerminalNode + COMMA(i int) antlr.TerminalNode + AllPIPEGREATER() []antlr.TerminalNode + PIPEGREATER(i int) antlr.TerminalNode + AllTransform() []ITransformContext + Transform(i int) ITransformContext + + // IsClauseBodyContext differentiates from other interfaces. + IsClauseBodyContext() +} + +type ClauseBodyContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyClauseBodyContext() *ClauseBodyContext { + var p = new(ClauseBodyContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MangleParserRULE_clauseBody + return p +} + +func InitEmptyClauseBodyContext(p *ClauseBodyContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MangleParserRULE_clauseBody +} + +func (*ClauseBodyContext) IsClauseBodyContext() {} + +func NewClauseBodyContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ClauseBodyContext { + var p = new(ClauseBodyContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = MangleParserRULE_clauseBody + + return p +} + +func (s *ClauseBodyContext) GetParser() antlr.Parser { return s.parser } + +func (s *ClauseBodyContext) AllLiteralOrFml() []ILiteralOrFmlContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(ILiteralOrFmlContext); ok { + len++ + } + } + + tst := make([]ILiteralOrFmlContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(ILiteralOrFmlContext); ok { + tst[i] = t.(ILiteralOrFmlContext) + i++ + } + } + + return tst +} + +func (s *ClauseBodyContext) LiteralOrFml(i int) ILiteralOrFmlContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ILiteralOrFmlContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(ILiteralOrFmlContext) +} + +func (s *ClauseBodyContext) AllCOMMA() []antlr.TerminalNode { + return s.GetTokens(MangleParserCOMMA) +} + +func (s *ClauseBodyContext) COMMA(i int) antlr.TerminalNode { + return s.GetToken(MangleParserCOMMA, i) +} + +func (s *ClauseBodyContext) AllPIPEGREATER() []antlr.TerminalNode { + return s.GetTokens(MangleParserPIPEGREATER) +} + +func (s *ClauseBodyContext) PIPEGREATER(i int) antlr.TerminalNode { + return s.GetToken(MangleParserPIPEGREATER, i) +} + +func (s *ClauseBodyContext) AllTransform() []ITransformContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(ITransformContext); ok { + len++ + } + } + + tst := make([]ITransformContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(ITransformContext); ok { + tst[i] = t.(ITransformContext) + i++ + } + } + + return tst +} + +func (s *ClauseBodyContext) Transform(i int) ITransformContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITransformContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(ITransformContext) +} + +func (s *ClauseBodyContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ClauseBodyContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *ClauseBodyContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MangleListener); ok { + listenerT.EnterClauseBody(s) + } +} + +func (s *ClauseBodyContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MangleListener); ok { + listenerT.ExitClauseBody(s) + } +} + +func (s *ClauseBodyContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case MangleVisitor: + return t.VisitClauseBody(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *MangleParser) ClauseBody() (localctx IClauseBodyContext) { + localctx = NewClauseBodyContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 22, MangleParserRULE_clauseBody) + var _la int + + var _alt int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(134) + p.LiteralOrFml() + } + p.SetState(139) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 15, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { + if _alt == 1 { + { + p.SetState(135) + p.Match(MangleParserCOMMA) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(136) + p.LiteralOrFml() + } + + } + p.SetState(141) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 15, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + } + p.SetState(143) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == MangleParserCOMMA { + { + p.SetState(142) + p.Match(MangleParserCOMMA) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + p.SetState(149) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for _la == MangleParserPIPEGREATER { + { + p.SetState(145) + p.Match(MangleParserPIPEGREATER) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(146) + p.Transform() + } + + p.SetState(151) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// ITransformContext is an interface to support dynamic dispatch. +type ITransformContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + DO() antlr.TerminalNode + Term() ITermContext + AllCOMMA() []antlr.TerminalNode + COMMA(i int) antlr.TerminalNode + AllLetStmt() []ILetStmtContext + LetStmt(i int) ILetStmtContext + + // IsTransformContext differentiates from other interfaces. + IsTransformContext() +} + +type TransformContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyTransformContext() *TransformContext { + var p = new(TransformContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MangleParserRULE_transform + return p +} + +func InitEmptyTransformContext(p *TransformContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MangleParserRULE_transform +} + +func (*TransformContext) IsTransformContext() {} + +func NewTransformContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TransformContext { + var p = new(TransformContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = MangleParserRULE_transform + + return p +} + +func (s *TransformContext) GetParser() antlr.Parser { return s.parser } + +func (s *TransformContext) DO() antlr.TerminalNode { + return s.GetToken(MangleParserDO, 0) +} + +func (s *TransformContext) Term() ITermContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITermContext); ok { + t = ctx.(antlr.RuleContext) + break } } @@ -2412,10 +2832,10 @@ func (s *TransformContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { func (p *MangleParser) Transform() (localctx ITransformContext) { localctx = NewTransformContext(p, p.GetParserRuleContext(), p.GetState()) - p.EnterRule(localctx, 20, MangleParserRULE_transform) + p.EnterRule(localctx, 24, MangleParserRULE_transform) var _la int - p.SetState(150) + p.SetState(173) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -2425,7 +2845,7 @@ func (p *MangleParser) Transform() (localctx ITransformContext) { case MangleParserDO: p.EnterOuterAlt(localctx, 1) { - p.SetState(129) + p.SetState(152) p.Match(MangleParserDO) if p.HasError() { // Recognition error - abort rule @@ -2433,10 +2853,10 @@ func (p *MangleParser) Transform() (localctx ITransformContext) { } } { - p.SetState(130) + p.SetState(153) p.Term() } - p.SetState(140) + p.SetState(163) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -2445,7 +2865,7 @@ func (p *MangleParser) Transform() (localctx ITransformContext) { if _la == MangleParserCOMMA { { - p.SetState(131) + p.SetState(154) p.Match(MangleParserCOMMA) if p.HasError() { // Recognition error - abort rule @@ -2453,10 +2873,10 @@ func (p *MangleParser) Transform() (localctx ITransformContext) { } } { - p.SetState(132) + p.SetState(155) p.LetStmt() } - p.SetState(137) + p.SetState(160) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -2465,7 +2885,7 @@ func (p *MangleParser) Transform() (localctx ITransformContext) { for _la == MangleParserCOMMA { { - p.SetState(133) + p.SetState(156) p.Match(MangleParserCOMMA) if p.HasError() { // Recognition error - abort rule @@ -2473,11 +2893,11 @@ func (p *MangleParser) Transform() (localctx ITransformContext) { } } { - p.SetState(134) + p.SetState(157) p.LetStmt() } - p.SetState(139) + p.SetState(162) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -2490,41 +2910,194 @@ func (p *MangleParser) Transform() (localctx ITransformContext) { case MangleParserLET: p.EnterOuterAlt(localctx, 2) { - p.SetState(142) + p.SetState(165) p.LetStmt() } - p.SetState(147) - p.GetErrorHandler().Sync(p) + p.SetState(170) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for _la == MangleParserCOMMA { + { + p.SetState(166) + p.Match(MangleParserCOMMA) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(167) + p.LetStmt() + } + + p.SetState(172) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// ILetStmtContext is an interface to support dynamic dispatch. +type ILetStmtContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + LET() antlr.TerminalNode + VARIABLE() antlr.TerminalNode + EQ() antlr.TerminalNode + Term() ITermContext + + // IsLetStmtContext differentiates from other interfaces. + IsLetStmtContext() +} + +type LetStmtContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyLetStmtContext() *LetStmtContext { + var p = new(LetStmtContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MangleParserRULE_letStmt + return p +} + +func InitEmptyLetStmtContext(p *LetStmtContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = MangleParserRULE_letStmt +} + +func (*LetStmtContext) IsLetStmtContext() {} + +func NewLetStmtContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *LetStmtContext { + var p = new(LetStmtContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = MangleParserRULE_letStmt + + return p +} + +func (s *LetStmtContext) GetParser() antlr.Parser { return s.parser } + +func (s *LetStmtContext) LET() antlr.TerminalNode { + return s.GetToken(MangleParserLET, 0) +} + +func (s *LetStmtContext) VARIABLE() antlr.TerminalNode { + return s.GetToken(MangleParserVARIABLE, 0) +} + +func (s *LetStmtContext) EQ() antlr.TerminalNode { + return s.GetToken(MangleParserEQ, 0) +} + +func (s *LetStmtContext) Term() ITermContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITermContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(ITermContext) +} + +func (s *LetStmtContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *LetStmtContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *LetStmtContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MangleListener); ok { + listenerT.EnterLetStmt(s) + } +} + +func (s *LetStmtContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(MangleListener); ok { + listenerT.ExitLetStmt(s) + } +} + +func (s *LetStmtContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { + switch t := visitor.(type) { + case MangleVisitor: + return t.VisitLetStmt(s) + + default: + return t.VisitChildren(s) + } +} + +func (p *MangleParser) LetStmt() (localctx ILetStmtContext) { + localctx = NewLetStmtContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 26, MangleParserRULE_letStmt) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(175) + p.Match(MangleParserLET) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(176) + p.Match(MangleParserVARIABLE) if p.HasError() { + // Recognition error - abort rule goto errorExit } - _la = p.GetTokenStream().LA(1) - - for _la == MangleParserCOMMA { - { - p.SetState(143) - p.Match(MangleParserCOMMA) - if p.HasError() { - // Recognition error - abort rule - goto errorExit - } - } - { - p.SetState(144) - p.LetStmt() - } - - p.SetState(149) - p.GetErrorHandler().Sync(p) - if p.HasError() { - goto errorExit - } - _la = p.GetTokenStream().LA(1) + } + { + p.SetState(177) + p.Match(MangleParserEQ) + if p.HasError() { + // Recognition error - abort rule + goto errorExit } - - default: - p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) - goto errorExit + } + { + p.SetState(178) + p.Term() } errorExit: @@ -2540,71 +3113,123 @@ errorExit: goto errorExit // Trick to prevent compiler error if the label is not used } -// ILetStmtContext is an interface to support dynamic dispatch. -type ILetStmtContext interface { +// ILiteralOrFmlContext is an interface to support dynamic dispatch. +type ILiteralOrFmlContext interface { antlr.ParserRuleContext // GetParser returns the parser. GetParser() antlr.Parser // Getter signatures - LET() antlr.TerminalNode - VARIABLE() antlr.TerminalNode + AllTerm() []ITermContext + Term(i int) ITermContext + TemporalOperator() ITemporalOperatorContext + TemporalAnnotation() ITemporalAnnotationContext EQ() antlr.TerminalNode - Term() ITermContext + BANGEQ() antlr.TerminalNode + LESS() antlr.TerminalNode + LESSEQ() antlr.TerminalNode + GREATER() antlr.TerminalNode + GREATEREQ() antlr.TerminalNode + BANG() antlr.TerminalNode - // IsLetStmtContext differentiates from other interfaces. - IsLetStmtContext() + // IsLiteralOrFmlContext differentiates from other interfaces. + IsLiteralOrFmlContext() } -type LetStmtContext struct { +type LiteralOrFmlContext struct { antlr.BaseParserRuleContext parser antlr.Parser } -func NewEmptyLetStmtContext() *LetStmtContext { - var p = new(LetStmtContext) +func NewEmptyLiteralOrFmlContext() *LiteralOrFmlContext { + var p = new(LiteralOrFmlContext) antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) - p.RuleIndex = MangleParserRULE_letStmt + p.RuleIndex = MangleParserRULE_literalOrFml return p } -func InitEmptyLetStmtContext(p *LetStmtContext) { +func InitEmptyLiteralOrFmlContext(p *LiteralOrFmlContext) { antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) - p.RuleIndex = MangleParserRULE_letStmt + p.RuleIndex = MangleParserRULE_literalOrFml } -func (*LetStmtContext) IsLetStmtContext() {} +func (*LiteralOrFmlContext) IsLiteralOrFmlContext() {} -func NewLetStmtContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *LetStmtContext { - var p = new(LetStmtContext) +func NewLiteralOrFmlContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *LiteralOrFmlContext { + var p = new(LiteralOrFmlContext) antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) p.parser = parser - p.RuleIndex = MangleParserRULE_letStmt + p.RuleIndex = MangleParserRULE_literalOrFml return p } -func (s *LetStmtContext) GetParser() antlr.Parser { return s.parser } +func (s *LiteralOrFmlContext) GetParser() antlr.Parser { return s.parser } -func (s *LetStmtContext) LET() antlr.TerminalNode { - return s.GetToken(MangleParserLET, 0) +func (s *LiteralOrFmlContext) AllTerm() []ITermContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(ITermContext); ok { + len++ + } + } + + tst := make([]ITermContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(ITermContext); ok { + tst[i] = t.(ITermContext) + i++ + } + } + + return tst } -func (s *LetStmtContext) VARIABLE() antlr.TerminalNode { - return s.GetToken(MangleParserVARIABLE, 0) +func (s *LiteralOrFmlContext) Term(i int) ITermContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITermContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(ITermContext) } -func (s *LetStmtContext) EQ() antlr.TerminalNode { - return s.GetToken(MangleParserEQ, 0) +func (s *LiteralOrFmlContext) TemporalOperator() ITemporalOperatorContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITemporalOperatorContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(ITemporalOperatorContext) } -func (s *LetStmtContext) Term() ITermContext { +func (s *LiteralOrFmlContext) TemporalAnnotation() ITemporalAnnotationContext { var t antlr.RuleContext for _, ctx := range s.GetChildren() { - if _, ok := ctx.(ITermContext); ok { + if _, ok := ctx.(ITemporalAnnotationContext); ok { t = ctx.(antlr.RuleContext) break } @@ -2614,70 +3239,157 @@ func (s *LetStmtContext) Term() ITermContext { return nil } - return t.(ITermContext) + return t.(ITemporalAnnotationContext) } -func (s *LetStmtContext) GetRuleContext() antlr.RuleContext { +func (s *LiteralOrFmlContext) EQ() antlr.TerminalNode { + return s.GetToken(MangleParserEQ, 0) +} + +func (s *LiteralOrFmlContext) BANGEQ() antlr.TerminalNode { + return s.GetToken(MangleParserBANGEQ, 0) +} + +func (s *LiteralOrFmlContext) LESS() antlr.TerminalNode { + return s.GetToken(MangleParserLESS, 0) +} + +func (s *LiteralOrFmlContext) LESSEQ() antlr.TerminalNode { + return s.GetToken(MangleParserLESSEQ, 0) +} + +func (s *LiteralOrFmlContext) GREATER() antlr.TerminalNode { + return s.GetToken(MangleParserGREATER, 0) +} + +func (s *LiteralOrFmlContext) GREATEREQ() antlr.TerminalNode { + return s.GetToken(MangleParserGREATEREQ, 0) +} + +func (s *LiteralOrFmlContext) BANG() antlr.TerminalNode { + return s.GetToken(MangleParserBANG, 0) +} + +func (s *LiteralOrFmlContext) GetRuleContext() antlr.RuleContext { return s } -func (s *LetStmtContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { +func (s *LiteralOrFmlContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { return antlr.TreesStringTree(s, ruleNames, recog) } -func (s *LetStmtContext) EnterRule(listener antlr.ParseTreeListener) { +func (s *LiteralOrFmlContext) EnterRule(listener antlr.ParseTreeListener) { if listenerT, ok := listener.(MangleListener); ok { - listenerT.EnterLetStmt(s) + listenerT.EnterLiteralOrFml(s) } } -func (s *LetStmtContext) ExitRule(listener antlr.ParseTreeListener) { +func (s *LiteralOrFmlContext) ExitRule(listener antlr.ParseTreeListener) { if listenerT, ok := listener.(MangleListener); ok { - listenerT.ExitLetStmt(s) + listenerT.ExitLiteralOrFml(s) } } -func (s *LetStmtContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { +func (s *LiteralOrFmlContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { switch t := visitor.(type) { case MangleVisitor: - return t.VisitLetStmt(s) + return t.VisitLiteralOrFml(s) default: return t.VisitChildren(s) } } -func (p *MangleParser) LetStmt() (localctx ILetStmtContext) { - localctx = NewLetStmtContext(p, p.GetParserRuleContext(), p.GetState()) - p.EnterRule(localctx, 22, MangleParserRULE_letStmt) - p.EnterOuterAlt(localctx, 1) - { - p.SetState(152) - p.Match(MangleParserLET) +func (p *MangleParser) LiteralOrFml() (localctx ILiteralOrFmlContext) { + localctx = NewLiteralOrFmlContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 28, MangleParserRULE_literalOrFml) + var _la int + + p.SetState(193) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case MangleParserLBRACKET, MangleParserLBRACE, MangleParserDIAMONDMINUS, MangleParserDIAMONDPLUS, MangleParserBOXMINUS, MangleParserBOXPLUS, MangleParserNUMBER, MangleParserFLOAT, MangleParserVARIABLE, MangleParserNAME, MangleParserDOT_TYPE, MangleParserCONSTANT, MangleParserSTRING, MangleParserBYTESTRING: + p.EnterOuterAlt(localctx, 1) + p.SetState(181) + p.GetErrorHandler().Sync(p) if p.HasError() { - // Recognition error - abort rule goto errorExit } - } - { - p.SetState(153) - p.Match(MangleParserVARIABLE) + _la = p.GetTokenStream().LA(1) + + if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&515396075520) != 0 { + { + p.SetState(180) + p.TemporalOperator() + } + + } + { + p.SetState(183) + p.Term() + } + p.SetState(185) + p.GetErrorHandler().Sync(p) if p.HasError() { - // Recognition error - abort rule goto errorExit } - } - { - p.SetState(154) - p.Match(MangleParserEQ) + _la = p.GetTokenStream().LA(1) + + if _la == MangleParserAT { + { + p.SetState(184) + p.TemporalAnnotation() + } + + } + p.SetState(189) + p.GetErrorHandler().Sync(p) if p.HasError() { - // Recognition error - abort rule goto errorExit } - } - { - p.SetState(155) - p.Term() + _la = p.GetTokenStream().LA(1) + + if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&2038431744) != 0 { + { + p.SetState(187) + _la = p.GetTokenStream().LA(1) + + if !((int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&2038431744) != 0) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + { + p.SetState(188) + p.Term() + } + + } + + case MangleParserBANG: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(191) + p.Match(MangleParserBANG) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(192) + p.Term() + } + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit } errorExit: @@ -2693,74 +3405,82 @@ errorExit: goto errorExit // Trick to prevent compiler error if the label is not used } -// ILiteralOrFmlContext is an interface to support dynamic dispatch. -type ILiteralOrFmlContext interface { +// ITemporalOperatorContext is an interface to support dynamic dispatch. +type ITemporalOperatorContext interface { antlr.ParserRuleContext // GetParser returns the parser. GetParser() antlr.Parser // Getter signatures - AllTerm() []ITermContext - Term(i int) ITermContext - EQ() antlr.TerminalNode - BANGEQ() antlr.TerminalNode - LESS() antlr.TerminalNode - LESSEQ() antlr.TerminalNode - GREATER() antlr.TerminalNode - GREATEREQ() antlr.TerminalNode - BANG() antlr.TerminalNode + DIAMONDMINUS() antlr.TerminalNode + LBRACKET() antlr.TerminalNode + AllTemporalBound() []ITemporalBoundContext + TemporalBound(i int) ITemporalBoundContext + COMMA() antlr.TerminalNode + RBRACKET() antlr.TerminalNode + BOXMINUS() antlr.TerminalNode + DIAMONDPLUS() antlr.TerminalNode + BOXPLUS() antlr.TerminalNode - // IsLiteralOrFmlContext differentiates from other interfaces. - IsLiteralOrFmlContext() + // IsTemporalOperatorContext differentiates from other interfaces. + IsTemporalOperatorContext() } -type LiteralOrFmlContext struct { +type TemporalOperatorContext struct { antlr.BaseParserRuleContext parser antlr.Parser } -func NewEmptyLiteralOrFmlContext() *LiteralOrFmlContext { - var p = new(LiteralOrFmlContext) +func NewEmptyTemporalOperatorContext() *TemporalOperatorContext { + var p = new(TemporalOperatorContext) antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) - p.RuleIndex = MangleParserRULE_literalOrFml + p.RuleIndex = MangleParserRULE_temporalOperator return p } -func InitEmptyLiteralOrFmlContext(p *LiteralOrFmlContext) { +func InitEmptyTemporalOperatorContext(p *TemporalOperatorContext) { antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) - p.RuleIndex = MangleParserRULE_literalOrFml + p.RuleIndex = MangleParserRULE_temporalOperator } -func (*LiteralOrFmlContext) IsLiteralOrFmlContext() {} +func (*TemporalOperatorContext) IsTemporalOperatorContext() {} -func NewLiteralOrFmlContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *LiteralOrFmlContext { - var p = new(LiteralOrFmlContext) +func NewTemporalOperatorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TemporalOperatorContext { + var p = new(TemporalOperatorContext) antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) p.parser = parser - p.RuleIndex = MangleParserRULE_literalOrFml + p.RuleIndex = MangleParserRULE_temporalOperator return p } -func (s *LiteralOrFmlContext) GetParser() antlr.Parser { return s.parser } +func (s *TemporalOperatorContext) GetParser() antlr.Parser { return s.parser } -func (s *LiteralOrFmlContext) AllTerm() []ITermContext { +func (s *TemporalOperatorContext) DIAMONDMINUS() antlr.TerminalNode { + return s.GetToken(MangleParserDIAMONDMINUS, 0) +} + +func (s *TemporalOperatorContext) LBRACKET() antlr.TerminalNode { + return s.GetToken(MangleParserLBRACKET, 0) +} + +func (s *TemporalOperatorContext) AllTemporalBound() []ITemporalBoundContext { children := s.GetChildren() len := 0 for _, ctx := range children { - if _, ok := ctx.(ITermContext); ok { + if _, ok := ctx.(ITemporalBoundContext); ok { len++ } } - tst := make([]ITermContext, len) + tst := make([]ITemporalBoundContext, len) i := 0 for _, ctx := range children { - if t, ok := ctx.(ITermContext); ok { - tst[i] = t.(ITermContext) + if t, ok := ctx.(ITemporalBoundContext); ok { + tst[i] = t.(ITemporalBoundContext) i++ } } @@ -2768,11 +3488,11 @@ func (s *LiteralOrFmlContext) AllTerm() []ITermContext { return tst } -func (s *LiteralOrFmlContext) Term(i int) ITermContext { +func (s *TemporalOperatorContext) TemporalBound(i int) ITemporalBoundContext { var t antlr.RuleContext j := 0 for _, ctx := range s.GetChildren() { - if _, ok := ctx.(ITermContext); ok { + if _, ok := ctx.(ITemporalBoundContext); ok { if j == i { t = ctx.(antlr.RuleContext) break @@ -2785,124 +3505,239 @@ func (s *LiteralOrFmlContext) Term(i int) ITermContext { return nil } - return t.(ITermContext) -} - -func (s *LiteralOrFmlContext) EQ() antlr.TerminalNode { - return s.GetToken(MangleParserEQ, 0) -} - -func (s *LiteralOrFmlContext) BANGEQ() antlr.TerminalNode { - return s.GetToken(MangleParserBANGEQ, 0) + return t.(ITemporalBoundContext) } -func (s *LiteralOrFmlContext) LESS() antlr.TerminalNode { - return s.GetToken(MangleParserLESS, 0) +func (s *TemporalOperatorContext) COMMA() antlr.TerminalNode { + return s.GetToken(MangleParserCOMMA, 0) } -func (s *LiteralOrFmlContext) LESSEQ() antlr.TerminalNode { - return s.GetToken(MangleParserLESSEQ, 0) +func (s *TemporalOperatorContext) RBRACKET() antlr.TerminalNode { + return s.GetToken(MangleParserRBRACKET, 0) } -func (s *LiteralOrFmlContext) GREATER() antlr.TerminalNode { - return s.GetToken(MangleParserGREATER, 0) +func (s *TemporalOperatorContext) BOXMINUS() antlr.TerminalNode { + return s.GetToken(MangleParserBOXMINUS, 0) } -func (s *LiteralOrFmlContext) GREATEREQ() antlr.TerminalNode { - return s.GetToken(MangleParserGREATEREQ, 0) +func (s *TemporalOperatorContext) DIAMONDPLUS() antlr.TerminalNode { + return s.GetToken(MangleParserDIAMONDPLUS, 0) } -func (s *LiteralOrFmlContext) BANG() antlr.TerminalNode { - return s.GetToken(MangleParserBANG, 0) +func (s *TemporalOperatorContext) BOXPLUS() antlr.TerminalNode { + return s.GetToken(MangleParserBOXPLUS, 0) } -func (s *LiteralOrFmlContext) GetRuleContext() antlr.RuleContext { +func (s *TemporalOperatorContext) GetRuleContext() antlr.RuleContext { return s } -func (s *LiteralOrFmlContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { +func (s *TemporalOperatorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { return antlr.TreesStringTree(s, ruleNames, recog) } -func (s *LiteralOrFmlContext) EnterRule(listener antlr.ParseTreeListener) { +func (s *TemporalOperatorContext) EnterRule(listener antlr.ParseTreeListener) { if listenerT, ok := listener.(MangleListener); ok { - listenerT.EnterLiteralOrFml(s) + listenerT.EnterTemporalOperator(s) } } -func (s *LiteralOrFmlContext) ExitRule(listener antlr.ParseTreeListener) { +func (s *TemporalOperatorContext) ExitRule(listener antlr.ParseTreeListener) { if listenerT, ok := listener.(MangleListener); ok { - listenerT.ExitLiteralOrFml(s) + listenerT.ExitTemporalOperator(s) } } -func (s *LiteralOrFmlContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { +func (s *TemporalOperatorContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { switch t := visitor.(type) { case MangleVisitor: - return t.VisitLiteralOrFml(s) + return t.VisitTemporalOperator(s) default: return t.VisitChildren(s) } } -func (p *MangleParser) LiteralOrFml() (localctx ILiteralOrFmlContext) { - localctx = NewLiteralOrFmlContext(p, p.GetParserRuleContext(), p.GetState()) - p.EnterRule(localctx, 24, MangleParserRULE_literalOrFml) - var _la int - - p.SetState(164) +func (p *MangleParser) TemporalOperator() (localctx ITemporalOperatorContext) { + localctx = NewTemporalOperatorContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 30, MangleParserRULE_temporalOperator) + p.SetState(223) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } switch p.GetTokenStream().LA(1) { - case MangleParserT__4, MangleParserLBRACKET, MangleParserNUMBER, MangleParserFLOAT, MangleParserVARIABLE, MangleParserNAME, MangleParserDOT_TYPE, MangleParserCONSTANT, MangleParserSTRING, MangleParserBYTESTRING: + case MangleParserDIAMONDMINUS: p.EnterOuterAlt(localctx, 1) { - p.SetState(157) - p.Term() + p.SetState(195) + p.Match(MangleParserDIAMONDMINUS) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } } - p.SetState(160) - p.GetErrorHandler().Sync(p) - if p.HasError() { - goto errorExit + { + p.SetState(196) + p.Match(MangleParserLBRACKET) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(197) + p.TemporalBound() + } + { + p.SetState(198) + p.Match(MangleParserCOMMA) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(199) + p.TemporalBound() + } + { + p.SetState(200) + p.Match(MangleParserRBRACKET) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } } - _la = p.GetTokenStream().LA(1) - - if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&509607936) != 0 { - { - p.SetState(158) - _la = p.GetTokenStream().LA(1) - if !((int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&509607936) != 0) { - p.GetErrorHandler().RecoverInline(p) - } else { - p.GetErrorHandler().ReportMatch(p) - p.Consume() - } + case MangleParserBOXMINUS: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(202) + p.Match(MangleParserBOXMINUS) + if p.HasError() { + // Recognition error - abort rule + goto errorExit } - { - p.SetState(159) - p.Term() + } + { + p.SetState(203) + p.Match(MangleParserLBRACKET) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(204) + p.TemporalBound() + } + { + p.SetState(205) + p.Match(MangleParserCOMMA) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(206) + p.TemporalBound() + } + { + p.SetState(207) + p.Match(MangleParserRBRACKET) + if p.HasError() { + // Recognition error - abort rule + goto errorExit } + } + case MangleParserDIAMONDPLUS: + p.EnterOuterAlt(localctx, 3) + { + p.SetState(209) + p.Match(MangleParserDIAMONDPLUS) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(210) + p.Match(MangleParserLBRACKET) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(211) + p.TemporalBound() + } + { + p.SetState(212) + p.Match(MangleParserCOMMA) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(213) + p.TemporalBound() + } + { + p.SetState(214) + p.Match(MangleParserRBRACKET) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } } - case MangleParserBANG: - p.EnterOuterAlt(localctx, 2) + case MangleParserBOXPLUS: + p.EnterOuterAlt(localctx, 4) { - p.SetState(162) - p.Match(MangleParserBANG) + p.SetState(216) + p.Match(MangleParserBOXPLUS) if p.HasError() { // Recognition error - abort rule goto errorExit } } { - p.SetState(163) - p.Term() + p.SetState(217) + p.Match(MangleParserLBRACKET) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(218) + p.TemporalBound() + } + { + p.SetState(219) + p.Match(MangleParserCOMMA) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(220) + p.TemporalBound() + } + { + p.SetState(221) + p.Match(MangleParserRBRACKET) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } } default: @@ -3554,6 +4389,14 @@ func (s *StructContext) GetRuleContext() antlr.RuleContext { return s } +func (s *StructContext) LBRACE() antlr.TerminalNode { + return s.GetToken(MangleParserLBRACE, 0) +} + +func (s *StructContext) RBRACE() antlr.TerminalNode { + return s.GetToken(MangleParserRBRACE, 0) +} + func (s *StructContext) AllTerm() []ITermContext { children := s.GetChildren() len := 0 @@ -3728,23 +4571,23 @@ func (s *DotTypeContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { func (p *MangleParser) Term() (localctx ITermContext) { localctx = NewTermContext(p, p.GetParserRuleContext(), p.GetState()) - p.EnterRule(localctx, 26, MangleParserRULE_term) + p.EnterRule(localctx, 32, MangleParserRULE_term) var _la int var _alt int - p.SetState(252) + p.SetState(311) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } - switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 32, p.GetParserRuleContext()) { + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 38, p.GetParserRuleContext()) { case 1: localctx = NewVarContext(p, localctx) p.EnterOuterAlt(localctx, 1) { - p.SetState(166) + p.SetState(225) p.Match(MangleParserVARIABLE) if p.HasError() { // Recognition error - abort rule @@ -3756,7 +4599,7 @@ func (p *MangleParser) Term() (localctx ITermContext) { localctx = NewConstContext(p, localctx) p.EnterOuterAlt(localctx, 2) { - p.SetState(167) + p.SetState(226) p.Match(MangleParserCONSTANT) if p.HasError() { // Recognition error - abort rule @@ -3768,7 +4611,7 @@ func (p *MangleParser) Term() (localctx ITermContext) { localctx = NewNumContext(p, localctx) p.EnterOuterAlt(localctx, 3) { - p.SetState(168) + p.SetState(227) p.Match(MangleParserNUMBER) if p.HasError() { // Recognition error - abort rule @@ -3780,7 +4623,7 @@ func (p *MangleParser) Term() (localctx ITermContext) { localctx = NewFloatContext(p, localctx) p.EnterOuterAlt(localctx, 4) { - p.SetState(169) + p.SetState(228) p.Match(MangleParserFLOAT) if p.HasError() { // Recognition error - abort rule @@ -3792,7 +4635,7 @@ func (p *MangleParser) Term() (localctx ITermContext) { localctx = NewStrContext(p, localctx) p.EnterOuterAlt(localctx, 5) { - p.SetState(170) + p.SetState(229) p.Match(MangleParserSTRING) if p.HasError() { // Recognition error - abort rule @@ -3804,7 +4647,7 @@ func (p *MangleParser) Term() (localctx ITermContext) { localctx = NewBStrContext(p, localctx) p.EnterOuterAlt(localctx, 6) { - p.SetState(171) + p.SetState(230) p.Match(MangleParserBYTESTRING) if p.HasError() { // Recognition error - abort rule @@ -3816,30 +4659,30 @@ func (p *MangleParser) Term() (localctx ITermContext) { localctx = NewListContext(p, localctx) p.EnterOuterAlt(localctx, 7) { - p.SetState(172) + p.SetState(231) p.Match(MangleParserLBRACKET) if p.HasError() { // Recognition error - abort rule goto errorExit } } - p.SetState(178) + p.SetState(237) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } - _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 21, p.GetParserRuleContext()) + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 27, p.GetParserRuleContext()) if p.HasError() { goto errorExit } for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { if _alt == 1 { { - p.SetState(173) + p.SetState(232) p.Term() } { - p.SetState(174) + p.SetState(233) p.Match(MangleParserCOMMA) if p.HasError() { // Recognition error - abort rule @@ -3848,32 +4691,32 @@ func (p *MangleParser) Term() (localctx ITermContext) { } } - p.SetState(180) + p.SetState(239) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } - _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 21, p.GetParserRuleContext()) + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 27, p.GetParserRuleContext()) if p.HasError() { goto errorExit } } - p.SetState(182) + p.SetState(241) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } _la = p.GetTokenStream().LA(1) - if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&2126009335840) != 0 { + if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&1088516514119680) != 0 { { - p.SetState(181) + p.SetState(240) p.Term() } } { - p.SetState(184) + p.SetState(243) p.Match(MangleParserRBRACKET) if p.HasError() { // Recognition error - abort rule @@ -3885,42 +4728,42 @@ func (p *MangleParser) Term() (localctx ITermContext) { localctx = NewMapContext(p, localctx) p.EnterOuterAlt(localctx, 8) { - p.SetState(185) + p.SetState(244) p.Match(MangleParserLBRACKET) if p.HasError() { // Recognition error - abort rule goto errorExit } } - p.SetState(193) + p.SetState(252) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } - _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 23, p.GetParserRuleContext()) + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 29, p.GetParserRuleContext()) if p.HasError() { goto errorExit } for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { if _alt == 1 { { - p.SetState(186) + p.SetState(245) p.Term() } { - p.SetState(187) - p.Match(MangleParserT__3) + p.SetState(246) + p.Match(MangleParserT__5) if p.HasError() { // Recognition error - abort rule goto errorExit } } { - p.SetState(188) + p.SetState(247) p.Term() } { - p.SetState(189) + p.SetState(248) p.Match(MangleParserCOMMA) if p.HasError() { // Recognition error - abort rule @@ -3929,44 +4772,44 @@ func (p *MangleParser) Term() (localctx ITermContext) { } } - p.SetState(195) + p.SetState(254) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } - _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 23, p.GetParserRuleContext()) + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 29, p.GetParserRuleContext()) if p.HasError() { goto errorExit } } - p.SetState(200) + p.SetState(259) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } _la = p.GetTokenStream().LA(1) - if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&2126009335840) != 0 { + if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&1088516514119680) != 0 { { - p.SetState(196) + p.SetState(255) p.Term() } { - p.SetState(197) - p.Match(MangleParserT__3) + p.SetState(256) + p.Match(MangleParserT__5) if p.HasError() { // Recognition error - abort rule goto errorExit } } { - p.SetState(198) + p.SetState(257) p.Term() } } { - p.SetState(202) + p.SetState(261) p.Match(MangleParserRBRACKET) if p.HasError() { // Recognition error - abort rule @@ -3978,42 +4821,42 @@ func (p *MangleParser) Term() (localctx ITermContext) { localctx = NewStructContext(p, localctx) p.EnterOuterAlt(localctx, 9) { - p.SetState(203) - p.Match(MangleParserT__4) + p.SetState(262) + p.Match(MangleParserLBRACE) if p.HasError() { // Recognition error - abort rule goto errorExit } } - p.SetState(211) + p.SetState(270) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } - _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 25, p.GetParserRuleContext()) + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 31, p.GetParserRuleContext()) if p.HasError() { goto errorExit } for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { if _alt == 1 { { - p.SetState(204) + p.SetState(263) p.Term() } { - p.SetState(205) - p.Match(MangleParserT__3) + p.SetState(264) + p.Match(MangleParserT__5) if p.HasError() { // Recognition error - abort rule goto errorExit } } { - p.SetState(206) + p.SetState(265) p.Term() } { - p.SetState(207) + p.SetState(266) p.Match(MangleParserCOMMA) if p.HasError() { // Recognition error - abort rule @@ -4022,45 +4865,45 @@ func (p *MangleParser) Term() (localctx ITermContext) { } } - p.SetState(213) + p.SetState(272) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } - _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 25, p.GetParserRuleContext()) + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 31, p.GetParserRuleContext()) if p.HasError() { goto errorExit } } - p.SetState(218) + p.SetState(277) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } _la = p.GetTokenStream().LA(1) - if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&2126009335840) != 0 { + if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&1088516514119680) != 0 { { - p.SetState(214) + p.SetState(273) p.Term() } { - p.SetState(215) - p.Match(MangleParserT__3) + p.SetState(274) + p.Match(MangleParserT__5) if p.HasError() { // Recognition error - abort rule goto errorExit } } { - p.SetState(216) + p.SetState(275) p.Term() } } { - p.SetState(220) - p.Match(MangleParserT__5) + p.SetState(279) + p.Match(MangleParserRBRACE) if p.HasError() { // Recognition error - abort rule goto errorExit @@ -4071,7 +4914,7 @@ func (p *MangleParser) Term() (localctx ITermContext) { localctx = NewDotTypeContext(p, localctx) p.EnterOuterAlt(localctx, 10) { - p.SetState(221) + p.SetState(280) p.Match(MangleParserDOT_TYPE) if p.HasError() { // Recognition error - abort rule @@ -4079,30 +4922,30 @@ func (p *MangleParser) Term() (localctx ITermContext) { } } { - p.SetState(222) + p.SetState(281) p.Match(MangleParserLESS) if p.HasError() { // Recognition error - abort rule goto errorExit } } - p.SetState(228) + p.SetState(287) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } - _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 27, p.GetParserRuleContext()) + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 33, p.GetParserRuleContext()) if p.HasError() { goto errorExit } for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { if _alt == 1 { { - p.SetState(223) + p.SetState(282) p.Member() } { - p.SetState(224) + p.SetState(283) p.Match(MangleParserCOMMA) if p.HasError() { // Recognition error - abort rule @@ -4111,29 +4954,29 @@ func (p *MangleParser) Term() (localctx ITermContext) { } } - p.SetState(230) + p.SetState(289) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } - _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 27, p.GetParserRuleContext()) + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 33, p.GetParserRuleContext()) if p.HasError() { goto errorExit } } - p.SetState(235) + p.SetState(294) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } _la = p.GetTokenStream().LA(1) - if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&2126009335968) != 0 { + if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&1088516514119808) != 0 { { - p.SetState(231) + p.SetState(290) p.Member() } - p.SetState(233) + p.SetState(292) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit @@ -4142,7 +4985,7 @@ func (p *MangleParser) Term() (localctx ITermContext) { if _la == MangleParserCOMMA { { - p.SetState(232) + p.SetState(291) p.Match(MangleParserCOMMA) if p.HasError() { // Recognition error - abort rule @@ -4154,7 +4997,7 @@ func (p *MangleParser) Term() (localctx ITermContext) { } { - p.SetState(237) + p.SetState(296) p.Match(MangleParserGREATER) if p.HasError() { // Recognition error - abort rule @@ -4166,7 +5009,7 @@ func (p *MangleParser) Term() (localctx ITermContext) { localctx = NewApplContext(p, localctx) p.EnterOuterAlt(localctx, 11) { - p.SetState(238) + p.SetState(297) p.Match(MangleParserNAME) if p.HasError() { // Recognition error - abort rule @@ -4174,30 +5017,30 @@ func (p *MangleParser) Term() (localctx ITermContext) { } } { - p.SetState(239) + p.SetState(298) p.Match(MangleParserLPAREN) if p.HasError() { // Recognition error - abort rule goto errorExit } } - p.SetState(245) + p.SetState(304) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } - _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 30, p.GetParserRuleContext()) + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 36, p.GetParserRuleContext()) if p.HasError() { goto errorExit } for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { if _alt == 1 { { - p.SetState(240) + p.SetState(299) p.Term() } { - p.SetState(241) + p.SetState(300) p.Match(MangleParserCOMMA) if p.HasError() { // Recognition error - abort rule @@ -4206,32 +5049,32 @@ func (p *MangleParser) Term() (localctx ITermContext) { } } - p.SetState(247) + p.SetState(306) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } - _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 30, p.GetParserRuleContext()) + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 36, p.GetParserRuleContext()) if p.HasError() { goto errorExit } } - p.SetState(249) + p.SetState(308) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } _la = p.GetTokenStream().LA(1) - if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&2126009335840) != 0 { + if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&1088516514119680) != 0 { { - p.SetState(248) + p.SetState(307) p.Term() } } { - p.SetState(251) + p.SetState(310) p.Match(MangleParserRPAREN) if p.HasError() { // Recognition error - abort rule @@ -4376,40 +5219,40 @@ func (s *MemberContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { func (p *MangleParser) Member() (localctx IMemberContext) { localctx = NewMemberContext(p, p.GetParserRuleContext(), p.GetState()) - p.EnterRule(localctx, 28, MangleParserRULE_member) + p.EnterRule(localctx, 34, MangleParserRULE_member) var _la int - p.SetState(264) + p.SetState(323) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } switch p.GetTokenStream().LA(1) { - case MangleParserT__4, MangleParserLBRACKET, MangleParserNUMBER, MangleParserFLOAT, MangleParserVARIABLE, MangleParserNAME, MangleParserDOT_TYPE, MangleParserCONSTANT, MangleParserSTRING, MangleParserBYTESTRING: + case MangleParserLBRACKET, MangleParserLBRACE, MangleParserNUMBER, MangleParserFLOAT, MangleParserVARIABLE, MangleParserNAME, MangleParserDOT_TYPE, MangleParserCONSTANT, MangleParserSTRING, MangleParserBYTESTRING: p.EnterOuterAlt(localctx, 1) { - p.SetState(254) + p.SetState(313) p.Term() } - p.SetState(257) + p.SetState(316) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } _la = p.GetTokenStream().LA(1) - if _la == MangleParserT__3 { + if _la == MangleParserT__5 { { - p.SetState(255) - p.Match(MangleParserT__3) + p.SetState(314) + p.Match(MangleParserT__5) if p.HasError() { // Recognition error - abort rule goto errorExit } } { - p.SetState(256) + p.SetState(315) p.Term() } @@ -4418,7 +5261,7 @@ func (p *MangleParser) Member() (localctx IMemberContext) { case MangleParserT__6: p.EnterOuterAlt(localctx, 2) { - p.SetState(259) + p.SetState(318) p.Match(MangleParserT__6) if p.HasError() { // Recognition error - abort rule @@ -4426,19 +5269,19 @@ func (p *MangleParser) Member() (localctx IMemberContext) { } } { - p.SetState(260) + p.SetState(319) p.Term() } { - p.SetState(261) - p.Match(MangleParserT__3) + p.SetState(320) + p.Match(MangleParserT__5) if p.HasError() { // Recognition error - abort rule goto errorExit } } { - p.SetState(262) + p.SetState(321) p.Term() } @@ -4554,10 +5397,10 @@ func (s *AtomContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { func (p *MangleParser) Atom() (localctx IAtomContext) { localctx = NewAtomContext(p, p.GetParserRuleContext(), p.GetState()) - p.EnterRule(localctx, 30, MangleParserRULE_atom) + p.EnterRule(localctx, 36, MangleParserRULE_atom) p.EnterOuterAlt(localctx, 1) { - p.SetState(266) + p.SetState(325) p.Term() } @@ -4714,37 +5557,37 @@ func (s *AtomsContext) Accept(visitor antlr.ParseTreeVisitor) interface{} { func (p *MangleParser) Atoms() (localctx IAtomsContext) { localctx = NewAtomsContext(p, p.GetParserRuleContext(), p.GetState()) - p.EnterRule(localctx, 32, MangleParserRULE_atoms) + p.EnterRule(localctx, 38, MangleParserRULE_atoms) var _la int var _alt int p.EnterOuterAlt(localctx, 1) { - p.SetState(268) + p.SetState(327) p.Match(MangleParserLBRACKET) if p.HasError() { // Recognition error - abort rule goto errorExit } } - p.SetState(274) + p.SetState(333) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } - _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 35, p.GetParserRuleContext()) + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 41, p.GetParserRuleContext()) if p.HasError() { goto errorExit } for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { if _alt == 1 { { - p.SetState(269) + p.SetState(328) p.Atom() } { - p.SetState(270) + p.SetState(329) p.Match(MangleParserCOMMA) if p.HasError() { // Recognition error - abort rule @@ -4753,32 +5596,32 @@ func (p *MangleParser) Atoms() (localctx IAtomsContext) { } } - p.SetState(276) + p.SetState(335) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } - _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 35, p.GetParserRuleContext()) + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 41, p.GetParserRuleContext()) if p.HasError() { goto errorExit } } - p.SetState(278) + p.SetState(337) p.GetErrorHandler().Sync(p) if p.HasError() { goto errorExit } _la = p.GetTokenStream().LA(1) - if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&2126009335840) != 0 { + if (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&1088516514119680) != 0 { { - p.SetState(277) + p.SetState(336) p.Atom() } } { - p.SetState(280) + p.SetState(339) p.Match(MangleParserRBRACKET) if p.HasError() { // Recognition error - abort rule diff --git a/parse/gen/mangle_visitor.go b/parse/gen/mangle_visitor.go index 89af16a..40662fd 100644 --- a/parse/gen/mangle_visitor.go +++ b/parse/gen/mangle_visitor.go @@ -1,4 +1,4 @@ -// Code generated from parse/gen/Mangle.g4 by ANTLR 4.13.1. DO NOT EDIT. +// Code generated from parse/gen/Mangle.g4 by ANTLR 4.13.2. DO NOT EDIT. package gen // Mangle import "github.com/antlr4-go/antlr/v4" @@ -34,6 +34,12 @@ type MangleVisitor interface { // Visit a parse tree produced by MangleParser#clause. VisitClause(ctx *ClauseContext) interface{} + // Visit a parse tree produced by MangleParser#temporalAnnotation. + VisitTemporalAnnotation(ctx *TemporalAnnotationContext) interface{} + + // Visit a parse tree produced by MangleParser#temporalBound. + VisitTemporalBound(ctx *TemporalBoundContext) interface{} + // Visit a parse tree produced by MangleParser#clauseBody. VisitClauseBody(ctx *ClauseBodyContext) interface{} @@ -46,6 +52,9 @@ type MangleVisitor interface { // Visit a parse tree produced by MangleParser#literalOrFml. VisitLiteralOrFml(ctx *LiteralOrFmlContext) interface{} + // Visit a parse tree produced by MangleParser#temporalOperator. + VisitTemporalOperator(ctx *TemporalOperatorContext) interface{} + // Visit a parse tree produced by MangleParser#Var. VisitVar(ctx *VarContext) interface{} diff --git a/parse/parse.go b/parse/parse.go index fdf9e3c..b5186a7 100644 --- a/parse/parse.go +++ b/parse/parse.go @@ -22,6 +22,7 @@ import ( "strconv" "strings" "sync" + "time" antlr "github.com/antlr4-go/antlr/v4" "github.com/google/mangle/ast" @@ -207,6 +208,12 @@ func (p *Parser) Visit(tree antlr.ParseTree) any { return p.VisitDotType(tree.(*gen.DotTypeContext)) case *gen.MemberContext: return p.VisitMember(tree.(*gen.MemberContext)) + case *gen.TemporalAnnotationContext: + return p.VisitTemporalAnnotation(tree.(*gen.TemporalAnnotationContext)) + case *gen.TemporalBoundContext: + return p.VisitTemporalBound(tree.(*gen.TemporalBoundContext)) + case *gen.TemporalOperatorContext: + return p.VisitTemporalOperator(tree.(*gen.TemporalOperatorContext)) } p.errors.Add(fmt.Sprintf("parse error: %q", tree.GetText()), 0, 0) return nil @@ -269,6 +276,10 @@ func (p Parser) VisitDecl(ctx *gen.DeclContext) any { if ctx.DescrBlock() != nil { descrAtoms = p.Visit(ctx.DescrBlock()).([]ast.Atom) } + // Check for 'temporal' keyword (T__0 corresponds to 'temporal' in the grammar) + if ctx.GetToken(gen.MangleParserT__0, 0) != nil { + descrAtoms = append(descrAtoms, ast.NewAtom(ast.DescrTemporal)) + } var bounds []ast.BoundDecl for _, b := range ctx.AllBoundsBlock() { bounds = append(bounds, p.Visit(b).(ast.BoundDecl)) @@ -315,12 +326,20 @@ func (p Parser) VisitConstraintsBlock(ctx *gen.ConstraintsBlockContext) any { // VisitClause visits a parse tree produced by MangleParser#clause. func (p Parser) VisitClause(ctx *gen.ClauseContext) any { head := p.Visit(ctx.Atom()).(ast.Atom) + + // Check for temporal annotation on head + var headTime *ast.Interval + if tempAnnot := ctx.TemporalAnnotation(); tempAnnot != nil { + headTime = p.Visit(tempAnnot).(*ast.Interval) + } + if (ctx.COLONDASH() != nil) || (ctx.LONGLEFTDOUBLEARROW() != nil) { body := p.Visit(ctx.ClauseBody()).(ast.Clause) body.Head = head + body.HeadTime = headTime return body } - return ast.NewClause(head, nil) + return ast.NewTemporalClause(head, headTime, nil) } // VisitClauseBody visits a parse tree produced by MangleParser#clauseBody. @@ -379,19 +398,50 @@ func (p Parser) VisitLetStmt(ctx *gen.LetStmtContext) any { func (p Parser) VisitLiteralOrFml(ctx *gen.LiteralOrFmlContext) any { term := p.Visit(ctx.Term(0)).(ast.Term) atom, ok := term.(ast.Atom) + + // Handle negation if ctx.BANG() != nil { if !ok { p.errors.Add(fmt.Sprintf("not a literal or fml: %v", ctx.Term(0).GetText()), ctx.Term(0).GetStart().GetLine(), ctx.Term(0).GetStart().GetColumn()) } return ast.NegAtom{atom} } + + // Check for temporal operator + var tempOp *ast.TemporalOperator + if tempOpCtx := ctx.TemporalOperator(); tempOpCtx != nil { + tempOp = p.Visit(tempOpCtx).(*ast.TemporalOperator) + } + + // Check for temporal annotation + var tempAnnot *ast.Interval + if tempAnnotCtx := ctx.TemporalAnnotation(); tempAnnotCtx != nil { + tempAnnot = p.Visit(tempAnnotCtx).(*ast.Interval) + } + + // If no comparison operator, it's a simple literal (possibly with temporal annotations) if ctx.EQ() == nil && ctx.BANGEQ() == nil && ctx.LESS() == nil && ctx.LESSEQ() == nil && ctx.GREATER() == nil && ctx.GREATEREQ() == nil { if ok { + // If there's a temporal operator or annotation, wrap in TemporalLiteral + if tempOp != nil || tempAnnot != nil { + var intervalVar *ast.Variable + // If the annotation is a variable, extract it + if tempAnnot != nil && tempAnnot.Start.Type == ast.VariableBound { + intervalVar = &tempAnnot.Start.Variable + } + return ast.TemporalLiteral{ + Literal: atom, + Operator: tempOp, + IntervalVar: intervalVar, + } + } return atom } p.errors.Add(fmt.Sprintf("parse error: %v", ctx.GetText()), ctx.GetStart().GetLine(), ctx.GetStart().GetColumn()) return ast.NewAtom("br0ken") } + + // Handle comparison operators left := p.Visit(ctx.Term(0)).(ast.Term) leftBase, ok := left.(ast.BaseTerm) if !ok { @@ -735,3 +785,151 @@ func (p *Parser) ReportAttemptingFullContext(recognizer antlr.Parser, dfa *antlr func (p *Parser) ReportContextSensitivity(recognizer antlr.Parser, dfa *antlr.DFA, startIndex, stopIndex, prediction int, configs *antlr.ATNConfigSet) { // Intentional } + +// VisitTemporalAnnotation visits a parse tree produced by MangleParser#temporalAnnotation. +// Returns an *ast.Interval. +func (p Parser) VisitTemporalAnnotation(ctx *gen.TemporalAnnotationContext) any { + bounds := ctx.AllTemporalBound() + if len(bounds) == 0 { + p.errors.Add("temporal annotation requires at least one bound", ctx.GetStart().GetLine(), ctx.GetStart().GetColumn()) + return nil + } + + start := p.Visit(bounds[0]).(ast.TemporalBound) + + var end ast.TemporalBound + if len(bounds) > 1 { + end = p.Visit(bounds[1]).(ast.TemporalBound) + } else { + // Point interval: @[t] means @[t, t] + end = start + } + + interval := ast.NewInterval(start, end) + return &interval +} + +// VisitTemporalBound visits a parse tree produced by MangleParser#temporalBound. +// Returns an ast.TemporalBound. +func (p Parser) VisitTemporalBound(ctx *gen.TemporalBoundContext) any { + if ts := ctx.TIMESTAMP(); ts != nil { + text := ts.GetText() + t, err := parseTimestamp(text) + if err != nil { + p.errors.Add(fmt.Sprintf("invalid timestamp %q: %v", text, err), ctx.GetStart().GetLine(), ctx.GetStart().GetColumn()) + return ast.TemporalBound{} + } + return ast.NewTimestampBound(t) + } + + if dur := ctx.DURATION(); dur != nil { + text := dur.GetText() + d, err := parseDuration(text) + if err != nil { + p.errors.Add(fmt.Sprintf("invalid duration %q: %v", text, err), ctx.GetStart().GetLine(), ctx.GetStart().GetColumn()) + return ast.TemporalBound{} + } + // Duration bounds are relative to "now" - store as negative offset from epoch + // This will be resolved during evaluation + return ast.TemporalBound{ + Type: ast.TimestampBound, + Timestamp: -d.Nanoseconds(), // Negative to indicate relative duration + } + } + + if v := ctx.VARIABLE(); v != nil { + text := v.GetText() + if text == "_" { + // Unbounded - determine if positive or negative based on position + // The caller (VisitTemporalAnnotation) will need to handle this + // For now, default to positive infinity (end bound) + return ast.PositiveInfinity() + } + return ast.NewVariableBound(ast.Variable{Symbol: text}) + } + + // Check for 'now' keyword + if ctx.GetText() == "now" { + return ast.Now() + } + + p.errors.Add("unknown temporal bound", ctx.GetStart().GetLine(), ctx.GetStart().GetColumn()) + return ast.TemporalBound{} +} + +// VisitTemporalOperator visits a parse tree produced by MangleParser#temporalOperator. +// Returns an *ast.TemporalOperator. +func (p Parser) VisitTemporalOperator(ctx *gen.TemporalOperatorContext) any { + bounds := ctx.AllTemporalBound() + if len(bounds) != 2 { + p.errors.Add("temporal operator requires exactly two bounds", ctx.GetStart().GetLine(), ctx.GetStart().GetColumn()) + return nil + } + + // Check for negative durations in temporal operator bounds. + // The temporal operator determines direction (past/future), not the duration sign. + // Negative durations are rejected to avoid confusion. + for _, boundCtx := range bounds { + if dur := boundCtx.(*gen.TemporalBoundContext).DURATION(); dur != nil { + text := dur.GetText() + if strings.HasPrefix(text, "-") { + p.errors.Add( + fmt.Sprintf("negative duration %q not allowed in temporal operator; use the operator type to indicate direction (e.g., <-[...] for past, <+[...] for future)", text), + boundCtx.GetStart().GetLine(), boundCtx.GetStart().GetColumn()) + } + } + } + + start := p.Visit(bounds[0]).(ast.TemporalBound) + end := p.Visit(bounds[1]).(ast.TemporalBound) + interval := ast.NewInterval(start, end) + + var opType ast.TemporalOperatorType + if ctx.DIAMONDMINUS() != nil { + opType = ast.DiamondMinus + } else if ctx.BOXMINUS() != nil { + opType = ast.BoxMinus + } else if ctx.DIAMONDPLUS() != nil { + opType = ast.DiamondPlus + } else if ctx.BOXPLUS() != nil { + opType = ast.BoxPlus + } else { + p.errors.Add("unknown temporal operator", ctx.GetStart().GetLine(), ctx.GetStart().GetColumn()) + return nil + } + + return &ast.TemporalOperator{ + Type: opType, + Interval: interval, + } +} + +// parseTimestamp parses a timestamp string in ISO 8601 format. +func parseTimestamp(s string) (time.Time, error) { + // Try various formats + formats := []string{ + "2006-01-02T15:04:05Z", + "2006-01-02T15:04:05", + "2006-01-02", + } + + for _, format := range formats { + if t, err := time.Parse(format, s); err == nil { + return t, nil + } + } + + return time.Time{}, fmt.Errorf("unable to parse timestamp: %s", s) +} + +// parseDuration parses a duration string like "7d", "24h", "30m", "1s", "500ms". +// parseDuration parses a Go-style duration string. +// Supported units: h (hours), m (minutes), s (seconds), ms (milliseconds), us/µs (microseconds), ns (nanoseconds) +// Examples: "1h30m", "500ms", "2h45m30s" +// Note: Days ('d') are NOT supported - use hours instead (e.g., "168h" for 7 days). +func parseDuration(s string) (time.Duration, error) { + if s == "" { + return 0, fmt.Errorf("empty duration string") + } + return time.ParseDuration(s) +} diff --git a/parse/parse_test.go b/parse/parse_test.go index 08756ed..00d6934 100644 --- a/parse/parse_test.go +++ b/parse/parse_test.go @@ -405,6 +405,7 @@ func TestParseUnitPositive(t *testing.T) { str: "foo(X) ⟸ bar(X) |> do fn:party(), let Z = fn:foo(X).", want: []ast.Clause{{ ast.NewAtom("foo", ast.Variable{"X"}), + nil, []ast.Term{ast.NewAtom("bar", ast.Variable{"X"})}, &ast.Transform{ []ast.TransformStmt{ @@ -421,6 +422,7 @@ func TestParseUnitPositive(t *testing.T) { str: "foo(X, ZZ) :- bar(X) |> do fn:party(), let Z = fn:foo(X) |> let ZZ = fn:mul(Z, 2).", want: []ast.Clause{{ ast.NewAtom("foo", ast.Variable{"X"}, ast.Variable{"ZZ"}), + nil, []ast.Term{ast.NewAtom("bar", ast.Variable{"X"})}, &ast.Transform{ []ast.TransformStmt{ @@ -446,6 +448,7 @@ func TestParseUnitPositive(t *testing.T) { str: "foo(X) :- bar(X) |> let Y = fn:plus(X, 1).", want: []ast.Clause{{ ast.NewAtom("foo", ast.Variable{"X"}), + nil, []ast.Term{ast.NewAtom("bar", ast.Variable{"X"})}, &ast.Transform{ []ast.TransformStmt{ diff --git a/parse/temporal_test.go b/parse/temporal_test.go new file mode 100644 index 0000000..97c92fd --- /dev/null +++ b/parse/temporal_test.go @@ -0,0 +1,514 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package parse + +import ( + "strings" + "testing" + "time" + + "github.com/google/mangle/ast" +) + +func TestParseTemporalFact(t *testing.T) { + tests := []struct { + name string + str string + wantHead ast.Atom + wantTime *ast.Interval + wantErr bool + }{ + { + name: "simple fact without temporal annotation", + str: "foo(/bar).", + wantHead: ast.NewAtom("foo", name("/bar")), + wantTime: nil, + wantErr: false, + }, + { + name: "fact with date interval", + str: "foo(/bar)@[2024-01-15, 2024-06-30].", + wantHead: ast.NewAtom("foo", name("/bar")), + wantTime: func() *ast.Interval { + start := ast.NewTimestampBound(time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC)) + end := ast.NewTimestampBound(time.Date(2024, 6, 30, 0, 0, 0, 0, time.UTC)) + i := ast.NewInterval(start, end) + return &i + }(), + wantErr: false, + }, + { + name: "fact with datetime interval", + str: "foo(/bar)@[2024-01-15T10:30:00, 2024-01-15T18:00:00].", + wantHead: ast.NewAtom("foo", name("/bar")), + wantTime: func() *ast.Interval { + start := ast.NewTimestampBound(time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC)) + end := ast.NewTimestampBound(time.Date(2024, 1, 15, 18, 0, 0, 0, time.UTC)) + i := ast.NewInterval(start, end) + return &i + }(), + wantErr: false, + }, + { + name: "fact with point interval (single timestamp)", + str: "login(/alice)@[2024-03-15].", + wantHead: ast.NewAtom("login", name("/alice")), + wantTime: func() *ast.Interval { + t := ast.NewTimestampBound(time.Date(2024, 3, 15, 0, 0, 0, 0, time.UTC)) + i := ast.NewInterval(t, t) + return &i + }(), + wantErr: false, + }, + { + name: "fact with unbounded start (negative infinity)", + str: "admin(/bob)@[_, 2024-12-31].", + wantHead: ast.NewAtom("admin", name("/bob")), + wantTime: func() *ast.Interval { + start := ast.PositiveInfinity() // Note: _ defaults to positive infinity, will need adjustment + end := ast.NewTimestampBound(time.Date(2024, 12, 31, 0, 0, 0, 0, time.UTC)) + i := ast.NewInterval(start, end) + return &i + }(), + wantErr: false, + }, + { + name: "fact with unbounded end (positive infinity)", + str: "employed(/alice)@[2020-01-01, _].", + wantHead: ast.NewAtom("employed", name("/alice")), + wantTime: func() *ast.Interval { + start := ast.NewTimestampBound(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)) + end := ast.PositiveInfinity() + i := ast.NewInterval(start, end) + return &i + }(), + wantErr: false, + }, + { + name: "fact with variable bounds", + str: "active(X)@[T1, T2].", + wantHead: ast.NewAtom("active", ast.Variable{Symbol: "X"}), + wantTime: func() *ast.Interval { + start := ast.NewVariableBound(ast.Variable{Symbol: "T1"}) + end := ast.NewVariableBound(ast.Variable{Symbol: "T2"}) + i := ast.NewInterval(start, end) + return &i + }(), + wantErr: false, + }, + { + name: "fact with 'now' as end bound", + str: "active(/alice)@[2024-01-01, now].", + wantHead: ast.NewAtom("active", name("/alice")), + wantTime: func() *ast.Interval { + start := ast.NewTimestampBound(time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)) + end := ast.Now() + i := ast.NewInterval(start, end) + return &i + }(), + wantErr: false, + }, + { + name: "fact with 'now' as start and end", + str: "event(/login)@[now].", + wantHead: ast.NewAtom("event", name("/login")), + wantTime: func() *ast.Interval { + bound := ast.Now() + i := ast.NewInterval(bound, bound) + return &i + }(), + wantErr: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + clause, err := Clause(test.str) + if (err != nil) != test.wantErr { + t.Fatalf("Clause(%q) error = %v, wantErr = %v", test.str, err, test.wantErr) + } + if err != nil { + return + } + + if !clause.Head.Equals(test.wantHead) { + t.Errorf("Clause(%q).Head = %v, want %v", test.str, clause.Head, test.wantHead) + } + + if test.wantTime == nil { + if clause.HeadTime != nil { + t.Errorf("Clause(%q).HeadTime = %v, want nil", test.str, clause.HeadTime) + } + } else { + if clause.HeadTime == nil { + t.Errorf("Clause(%q).HeadTime = nil, want %v", test.str, test.wantTime) + } else if !clause.HeadTime.Equals(*test.wantTime) { + t.Errorf("Clause(%q).HeadTime = %v, want %v", test.str, clause.HeadTime, test.wantTime) + } + } + }) + } +} + +func TestParseTemporalRule(t *testing.T) { + tests := []struct { + name string + str string + wantHead ast.Atom + wantTime *ast.Interval + wantErr bool + }{ + { + name: "rule without temporal annotation", + str: "foo(X) :- bar(X).", + wantHead: ast.NewAtom("foo", ast.Variable{Symbol: "X"}), + wantTime: nil, + wantErr: false, + }, + { + name: "rule with temporal annotation on head", + str: "active(X)@[T, T] :- login(X).", + wantHead: ast.NewAtom("active", ast.Variable{Symbol: "X"}), + wantTime: func() *ast.Interval { + bound := ast.NewVariableBound(ast.Variable{Symbol: "T"}) + i := ast.NewInterval(bound, bound) + return &i + }(), + wantErr: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + clause, err := Clause(test.str) + if (err != nil) != test.wantErr { + t.Fatalf("Clause(%q) error = %v, wantErr = %v", test.str, err, test.wantErr) + } + if err != nil { + return + } + + if !clause.Head.Equals(test.wantHead) { + t.Errorf("Clause(%q).Head = %v, want %v", test.str, clause.Head, test.wantHead) + } + + if test.wantTime == nil { + if clause.HeadTime != nil { + t.Errorf("Clause(%q).HeadTime = %v, want nil", test.str, clause.HeadTime) + } + } else { + if clause.HeadTime == nil { + t.Errorf("Clause(%q).HeadTime = nil, want %v", test.str, test.wantTime) + } else if !clause.HeadTime.Equals(*test.wantTime) { + t.Errorf("Clause(%q).HeadTime = %v, want %v", test.str, clause.HeadTime, test.wantTime) + } + } + }) + } +} + +func TestParseTimestamp(t *testing.T) { + tests := []struct { + name string + input string + want time.Time + wantErr bool + }{ + { + name: "date only", + input: "2024-01-15", + want: time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC), + wantErr: false, + }, + { + name: "date and time without Z", + input: "2024-01-15T10:30:00", + want: time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC), + wantErr: false, + }, + { + name: "date and time with Z", + input: "2024-01-15T10:30:00Z", + want: time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC), + wantErr: false, + }, + { + name: "invalid format", + input: "not-a-date", + wantErr: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := parseTimestamp(test.input) + if (err != nil) != test.wantErr { + t.Fatalf("parseTimestamp(%q) error = %v, wantErr = %v", test.input, err, test.wantErr) + } + if err == nil && !got.Equal(test.want) { + t.Errorf("parseTimestamp(%q) = %v, want %v", test.input, got, test.want) + } + }) + } +} + +func TestParseDuration(t *testing.T) { + tests := []struct { + name string + input string + want time.Duration + wantErr bool + }{ + { + name: "days not supported", + input: "7d", + wantErr: true, // Go's time.ParseDuration doesn't support days + }, + { + name: "hours", + input: "24h", + want: 24 * time.Hour, + wantErr: false, + }, + { + name: "minutes", + input: "30m", + want: 30 * time.Minute, + wantErr: false, + }, + { + name: "seconds", + input: "60s", + want: 60 * time.Second, + wantErr: false, + }, + { + name: "milliseconds", + input: "500ms", + want: 500 * time.Millisecond, + wantErr: false, + }, + { + name: "invalid suffix", + input: "10x", + wantErr: true, + }, + { + name: "too short", + input: "s", + wantErr: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := parseDuration(test.input) + if (err != nil) != test.wantErr { + t.Fatalf("parseDuration(%q) error = %v, wantErr = %v", test.input, err, test.wantErr) + } + if err == nil && got != test.want { + t.Errorf("parseDuration(%q) = %v, want %v", test.input, got, test.want) + } + }) + } +} + +func TestParseTemporalOperator(t *testing.T) { + tests := []struct { + name string + str string + wantOp ast.TemporalOperatorType + wantErr bool + }{ + { + name: "diamond minus with durations", + str: "recently_active(X) :- <-[0s, 168h] active(X).", + wantOp: ast.DiamondMinus, + wantErr: false, + }, + { + name: "box minus with durations", + str: "stable(X) :- [-[0s, 8760h] employed(X).", + wantOp: ast.BoxMinus, + wantErr: false, + }, + { + name: "diamond plus with durations", + str: "upcoming(X) :- <+[0s, 168h] scheduled(X).", + wantOp: ast.DiamondPlus, + wantErr: false, + }, + { + name: "box plus with durations", + str: "guaranteed(X) :- [+[0s, 720h] available(X).", + wantOp: ast.BoxPlus, + wantErr: false, + }, + { + name: "negative duration rejected", + str: "bad(X) :- <-[-30m, 1h] foo(X).", + wantErr: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + unit, err := Unit(strings.NewReader(test.str)) + if (err != nil) != test.wantErr { + t.Fatalf("Unit(%q) error = %v, wantErr = %v", test.str, err, test.wantErr) + } + if err != nil { + return + } + + if len(unit.Clauses) != 1 { + t.Fatalf("Unit(%q) has %d clauses, want 1", test.str, len(unit.Clauses)) + } + + clause := unit.Clauses[0] + if len(clause.Premises) != 1 { + t.Fatalf("Clause has %d premises, want 1", len(clause.Premises)) + } + + tempLit, ok := clause.Premises[0].(ast.TemporalLiteral) + if !ok { + t.Fatalf("Premise is %T, want ast.TemporalLiteral", clause.Premises[0]) + } + + if tempLit.Operator == nil { + t.Fatalf("TemporalLiteral.Operator is nil") + } + + if tempLit.Operator.Type != test.wantOp { + t.Errorf("Operator.Type = %v, want %v", tempLit.Operator.Type, test.wantOp) + } + }) + } +} + +func TestTemporalFactBackwardCompatibility(t *testing.T) { + // These are existing Mangle programs that should continue to work unchanged + tests := []struct { + name string + str string + }{ + { + name: "simple fact", + str: "foo(/bar).", + }, + { + name: "rule with body", + str: "foo(X) :- bar(X).", + }, + { + name: "recursive rule", + str: "reachable(X, Y) :- edge(X, Y). reachable(X, Z) :- edge(X, Y), reachable(Y, Z).", + }, + { + name: "rule with negation", + str: "active(X) :- registered(X), !deleted(X).", + }, + { + name: "rule with transform", + str: "count(N) :- item(X) |> do fn:group_by(), let N = fn:Count().", + }, + { + name: "rule with comparison", + str: "adult(X) :- person(X, Age), Age >= 18.", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + unit, err := Unit(strings.NewReader(test.str)) + if err != nil { + t.Fatalf("Unit(%q) failed: %v", test.str, err) + } + if len(unit.Clauses) == 0 { + t.Errorf("Unit(%q) has no clauses", test.str) + } + }) + } +} + +func TestParseTemporalDecl(t *testing.T) { + tests := []struct { + name string + str string + wantTemporal bool + wantErr bool + }{ + { + name: "simple temporal declaration", + str: "Decl foo(X) temporal.", + wantTemporal: true, + wantErr: false, + }, + { + name: "temporal declaration with bounds", + str: "Decl event(X, Y) temporal bound [/name, /number].", + wantTemporal: true, + wantErr: false, + }, + { + name: "temporal declaration with descr", + str: `Decl activity(X) temporal descr [doc("A temporal predicate")].`, + wantTemporal: true, + wantErr: false, + }, + { + name: "non-temporal declaration", + str: "Decl bar(X, Y) bound [/string, /number].", + wantTemporal: false, + wantErr: false, + }, + { + name: "temporal declaration with multiple bounds", + str: "Decl status(X, Y, Z) temporal bound [/name, /string, /number].", + wantTemporal: true, + wantErr: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + unit, err := Unit(strings.NewReader(test.str)) + if (err != nil) != test.wantErr { + t.Fatalf("Unit(%q) error = %v, wantErr = %v", test.str, err, test.wantErr) + } + if err != nil { + return + } + + // The parser adds a default Package decl when none specified, so we look for + // the user's declaration (second one) or find the non-Package declaration. + var decl ast.Decl + for _, d := range unit.Decls { + if d.DeclaredAtom.Predicate.Symbol != "Package" { + decl = d + break + } + } + + if decl.DeclaredAtom.Predicate.Symbol == "" { + t.Fatalf("Unit(%q) has no user declarations", test.str) + } + + if decl.IsTemporal() != test.wantTemporal { + t.Errorf("Decl.IsTemporal() = %v, want %v", decl.IsTemporal(), test.wantTemporal) + } + }) + } +} diff --git a/readthedocs/index.md b/readthedocs/index.md index 2ab0b4c..cf15e19 100644 --- a/readthedocs/index.md +++ b/readthedocs/index.md @@ -63,3 +63,4 @@ Aggregation Basic Types Constructed Types Type Expressions +Temporal Reasoning diff --git a/readthedocs/temporal.md b/readthedocs/temporal.md new file mode 100644 index 0000000..c20dc26 --- /dev/null +++ b/readthedocs/temporal.md @@ -0,0 +1,362 @@ +# Temporal Reasoning + +Facts don't always stay true forever. Employees change teams. Certifications +expire. Systems go up and down. Mangle's temporal reasoning extension lets you +track *when* things are true, not just *that* they're true. + +## Temporal Facts + +Add a time interval to any fact with `@[start, end]`: + +``` +# Alice was on engineering from Jan 2020 to June 2023 +team_member(/alice, /engineering)@[2020-01-01, 2023-06-15]. + +# Bob joined engineering in 2019 and is still there +team_member(/bob, /engineering)@[2019-06-01, _]. + +# Something that happened at a specific moment +login(/alice)@[2024-03-15T10:30:00]. + +# Currently active (from 2024 until now) +active(/alice)@[2024-01-01, now]. + +# Something happening right now +logged_in(/bob)@[now]. +``` + +Special bounds: +- `_` means unbounded (beginning of time or ongoing into the future) +- `now` means the current evaluation time + +Facts without `@[...]` work exactly like before - they're eternal truths. + +## Temporal Operators + +Four operators let you reason about time: + +### Past Operators + +**Diamond-minus `<-`** - "was true at some point in the past" + +``` +# Was active sometime in the last 30 days +recently_active(X) :- <-[0d, 30d] active(X). +``` + +**Box-minus `[-`** - "was true continuously in the past" + +``` +# Was continuously active for the last 30 days +reliably_active(X) :- [-[0d, 30d] active(X). +``` + +### Future Operators + +**Diamond-plus `<+`** - "will be true at some point in the future" + +``` +# Will be on maintenance sometime in the next 7 days +maintenance_pending(X) :- <+[0d, 7d] maintenance(X). +``` + +**Box-plus `[+`** - "will be true continuously in the future" + +``` +# Contract will be valid for the entire next 30 days +covered(X) :- [+[0d, 30d] contract(X). +``` + +The interval `[0d, 30d]` means "from now to 30 days ago/ahead". You can use: +- `d` for days +- `h` for hours +- ISO timestamps like `2024-01-15T10:30:00` + +## Allen's Interval Relations + +Built-in predicates for comparing intervals (based on Allen's interval algebra): + +| Predicate | Meaning | +|-----------|---------| +| `:interval:before(T1, T2)` | T1 ends before T2 starts | +| `:interval:after(T1, T2)` | T1 starts after T2 ends | +| `:interval:meets(T1, T2)` | T1 ends exactly when T2 starts | +| `:interval:overlaps(T1, T2)` | T1 and T2 share some time | +| `:interval:during(T1, T2)` | T1 is contained within T2 | +| `:interval:contains(T1, T2)` | T1 contains T2 | +| `:interval:starts(T1, T2)` | T1 and T2 start together | +| `:interval:finishes(T1, T2)` | T1 and T2 end together | +| `:interval:equals(T1, T2)` | T1 and T2 are identical | + +## Interval Functions + +Extract components from bound interval variables: + +| Function | Returns | +|----------|---------| +| `fn:interval:start(T)` | Start time in nanoseconds | +| `fn:interval:end(T)` | End time in nanoseconds | +| `fn:interval:duration(T)` | Duration in nanoseconds | + +Example: +``` +# Get the duration of an event +event_duration(X, D) :- + event(X)@T, + D = fn:interval:duration(T). +``` + +## Interval Variable Binding + +You can bind the interval of a matching fact to a variable: + +``` +# Bind the validity interval to T +event_with_time(X, T) :- event(X)@T. +``` + +The bound variable is a pair of timestamps (start, end) in nanoseconds. + +## Interval Coalescing + +When facts have adjacent or overlapping intervals, they get merged: + +``` +# If you assert: +employed(/alice)@[2020-01-01, 2021-12-31]. +employed(/alice)@[2022-01-01, 2023-12-31]. + +# Coalescing produces: +employed(/alice)@[2020-01-01, 2023-12-31]. +``` + +This prevents interval explosion in recursive rules. + +## Temporal Declarations + +Mark a predicate as temporal using the `temporal` keyword in declarations: + +``` +# Declare a temporal predicate +Decl employee_status(person, status) temporal bound [/name, /string]. + +# Regular (eternal) predicate for comparison +Decl config_setting(key, value) bound [/string, /string]. +``` + +The `temporal` keyword signals that facts for this predicate have validity +intervals. You can check if a declaration is temporal programmatically: + +```go +if decl.IsTemporal() { + // This predicate has temporal semantics +} +``` + +## Example: Checking Certification Compliance + +Here's a realistic use case - make sure operators had certifications before +using equipment: + +``` +# Declare predicates +Decl certified(person, cert) temporal. +Decl operated(person, equipment, timestamp) temporal. + +# Facts +certified(/alice, /forklift)@[2023-01-01, 2024-01-01]. +operated(/alice, /forklift, _)@[2023-06-15]. + +# Rule: Find violations - operated without 30 days of certification +violation(Person, Equipment) :- + operated(Person, Equipment, _)@[Time, Time], + ![-[30d, 30d] certified(Person, Equipment). +``` + +## Programmatic Usage + +To use temporal features from Go: + +```go +import ( + "time" + "github.com/google/mangle/engine" + "github.com/google/mangle/factstore" +) + +// Create a temporal store and add facts +store := factstore.NewTemporalStore() +store.Add(myAtom, interval) + +// Evaluate with temporal support +stats, err := engine.EvalProgramWithStats( + programInfo, + regularStore, + engine.WithTemporalStore(store), + engine.WithEvaluationTime(time.Now()), +) +``` + +### Temporal Store API + +The `TemporalFactStore` interface provides these methods: + +```go +// Add a temporal fact (returns added, error) +store.Add(atom ast.Atom, interval ast.Interval) (bool, error) + +// Add a fact that's always true +store.AddEternal(atom ast.Atom) (bool, error) + +// Query facts valid at a specific time +store.GetFactsAt(query ast.Atom, t time.Time, callback) error + +// Query facts overlapping an interval +store.GetFactsDuring(query ast.Atom, interval ast.Interval, callback) error + +// Merge adjacent/overlapping intervals +store.Coalesce(predicate ast.PredicateSym) error +``` + +### Time and Interval Helpers + +The `ast` package provides convenience functions to reduce verbosity when creating times and intervals: + +```go +// Create dates without typing all the zeros +t := ast.Date(2024, 1, 15) // midnight in default timezone +t := ast.DateTime(2024, 1, 15, 10, 30) // with hour and minute +t := ast.DateTimeSec(2024, 1, 15, 10, 30, 45) // with seconds + +// Create intervals concisely +interval := ast.TimeInterval(startTime, endTime) +interval := ast.DateInterval(2023, 1, 1, 2024, 12, 31) // most concise +``` + +**Before** (verbose): +```go +store.Add(atom, ast.NewInterval( + ast.NewTimestampBound(time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)), + ast.NewTimestampBound(time.Date(2024, 12, 31, 0, 0, 0, 0, time.UTC)), +)) +``` + +**After** (concise): +```go +store.Add(atom, ast.DateInterval(2023, 1, 1, 2024, 12, 31)) +``` + +| Helper | Example | Purpose | +|--------|---------|---------| +| `ast.Date(y, m, d)` | `ast.Date(2024, 1, 15)` | Date at midnight | +| `ast.DateTime(y, m, d, h, min)` | `ast.DateTime(2024, 1, 15, 10, 30)` | Date with time | +| `ast.DateTimeSec(y, m, d, h, min, s)` | `ast.DateTimeSec(2024, 1, 15, 10, 30, 45)` | Date with seconds | +| `ast.DateIn(y, m, d, tz)` | `ast.DateIn(2024, 1, 15, "PST")` | Date in specific timezone | +| `ast.DateTimeIn(y, m, d, h, min, tz)` | `ast.DateTimeIn(2024, 1, 15, 10, 30, "EST")` | Date+time in specific timezone | +| `ast.TimeInterval(start, end)` | `ast.TimeInterval(t1, t2)` | Interval from time.Time values | +| `ast.DateInterval(...)` | `ast.DateInterval(2023, 1, 1, 2024, 12, 31)` | Interval from date components | + +### Timezone Configuration + +All date/time helpers use a configurable default timezone (UTC by default). Set it once at program startup: + +```go +// Default: UTC (no configuration needed) + +ast.SetTimezone("UTC") // Explicit UTC +ast.SetTimezone("Local") // System timezone +ast.SetTimezone("America/New_York") // IANA timezone name +ast.SetTimezone("PST") // Common abbreviations work too + +// For init(), use MustSetTimezone (panics on error) +func init() { + ast.MustSetTimezone("America/New_York") +} +``` + +Supported abbreviations: `EST`, `CST`, `MST`, `PST`, `GMT`, `CET`, `JST`, `IST`, and more. + +**Important:** Set the timezone once at startup before creating any temporal facts. + +For one-off values in a different timezone without changing the default, use `DateIn` or `DateTimeIn`: + +```go +// Default is UTC, but this one event is in NYC time +store.Add(event, ast.TimeInterval( + ast.DateTimeIn(2024, 1, 15, 19, 0, "America/New_York"), // 7pm NYC + ast.DateTimeIn(2024, 1, 15, 22, 0, "EST"), // 10pm EST +)) +``` + +## Time Bridge Functions + +When mixing temporal reasoning with regular data columns containing timestamps: + +| Function | Purpose | +|----------|---------| +| `fn:time:from_nanos(N)` | Convert nanoseconds to temporal-compatible value | +| `fn:time:to_nanos(T)` | Convert temporal bound to nanoseconds | +| `fn:time:add(T, D)` | Add duration (nanos) to timestamp | + +Example: +``` +# Bridge between a column timestamp and temporal queries +valid_order(Order) :- + order(Order, CreatedAtNanos), + Timestamp = fn:time:from_nanos(CreatedAtNanos), + fn:time:after(Timestamp, fn:time:add(fn:time:now, -2592000000000000)). # 30 days in nanos +``` + +## Decidability and Termination + +Temporal reasoning introduces potential non-termination. The implementation includes +safeguards, but understanding safe vs dangerous patterns helps avoid issues. + +### Safe Patterns (Guaranteed to Terminate) + +``` +# SAFE: Past-only lookback, no temporal head +recent_login(User) :- + <-[7d] login(User). + +# SAFE: Temporal head but no recursion through temporal predicates +expires_soon(License) @[now, End] :- + license(License) @[_, End], + :interval:before(End, fn:time:add(now, 2592000000000000)). +``` + +### Dangerous Patterns (May Not Terminate) + +``` +# DANGEROUS: Recursive temporal derivation with expanding intervals +extended(X) @[T1, T2] :- + base(X) @[T1, T0], + extended(X) @[T0, T2]. # Self-reference extends interval + +# DANGEROUS: Unbounded future generation +will_happen(X) @[now, _] :- + trigger(X), + [+[1d] will_happen(X). # Infinite future facts +``` + +### Built-in Safeguards + +1. **Interval Coalescing**: Adjacent/overlapping intervals are merged automatically +2. **Interval Limit**: Default 1000 intervals per atom, configurable via `WithMaxIntervalsPerAtom(n)` +3. **Fact Limits**: Use `engine.WithCreatedFactLimit(n)` to cap total derived facts + +Configure the interval limit: +```go +// Custom limit +store := factstore.NewTemporalStore(factstore.WithMaxIntervalsPerAtom(5000)) + +// No limit (use with caution) +store := factstore.NewTemporalStore(factstore.WithMaxIntervalsPerAtom(-1)) +``` + +### Complexity + +Based on DatalogMTL research: +- Non-recursive temporal queries: AC⁰ data complexity +- Full DatalogMTL: PSPACE-complete data complexity +- Forward-propagating programs: May not terminate without coalescing diff --git a/rewrite/rewrite.go b/rewrite/rewrite.go index 01e94cc..63189ff 100644 --- a/rewrite/rewrite.go +++ b/rewrite/rewrite.go @@ -56,8 +56,8 @@ func Rewrite(stratum analysis.Program) analysis.Program { internalPred := gen.freshPredicateName(p, len(vars)) newHead := makeHead(internalPred, vars) - newRules = append(newRules, ast.Clause{newHead, clause.Premises, nil}) - newRules = append(newRules, ast.Clause{clause.Head, []ast.Term{newHead}, clause.Transform}) + newRules = append(newRules, ast.Clause{newHead, nil, clause.Premises, nil}) + newRules = append(newRules, ast.Clause{clause.Head, clause.HeadTime, []ast.Term{newHead}, clause.Transform}) } return analysis.Program{stratum.EdbPredicates, stratum.IdbPredicates, newRules} } diff --git a/symbols/symbols.go b/symbols/symbols.go index a7aee6a..6b8170a 100644 --- a/symbols/symbols.go +++ b/symbols/symbols.go @@ -91,6 +91,34 @@ var ( // WithinDistance is a relation on numbers X, Y, Z satisfying |X - Y| < Z. WithinDistance = ast.PredicateSym{":within_distance", 3} + // Temporal interval predicates (Allen's interval algebra) + // IntervalBefore checks if interval T1 ends before interval T2 starts. + IntervalBefore = ast.PredicateSym{":interval:before", 2} + // IntervalAfter checks if interval T1 starts after interval T2 ends. + IntervalAfter = ast.PredicateSym{":interval:after", 2} + // IntervalMeets checks if interval T1 ends exactly when T2 starts. + IntervalMeets = ast.PredicateSym{":interval:meets", 2} + // IntervalOverlaps checks if intervals T1 and T2 share some time. + IntervalOverlaps = ast.PredicateSym{":interval:overlaps", 2} + // IntervalDuring checks if interval T1 is contained within T2. + IntervalDuring = ast.PredicateSym{":interval:during", 2} + // IntervalContains checks if interval T1 contains T2. + IntervalContains = ast.PredicateSym{":interval:contains", 2} + // IntervalStarts checks if intervals T1 and T2 start at the same time. + IntervalStarts = ast.PredicateSym{":interval:starts", 2} + // IntervalFinishes checks if intervals T1 and T2 end at the same time. + IntervalFinishes = ast.PredicateSym{":interval:finishes", 2} + // IntervalEquals checks if intervals T1 and T2 are identical. + IntervalEquals = ast.PredicateSym{":interval:equals", 2} + + // Interval functions for extracting components from intervals + // IntervalStart extracts the start time from an interval. + IntervalStart = ast.FunctionSym{"fn:interval:start", 1} + // IntervalEnd extracts the end time from an interval. + IntervalEnd = ast.FunctionSym{"fn:interval:end", 1} + // IntervalDuration calculates the duration of an interval. + IntervalDuration = ast.FunctionSym{"fn:interval:duration", 1} + // Div is a family of functions mapping integer division: X,Y1,.. to (X / Y1) / Y2 ... DIV(X) is 1/x. Div = ast.FunctionSym{"fn:div", -1} // FloatDiv is a family of functions mapping division: X,Y1,.. to (X / Y1) / Y2 ... FloatDiv(X) is 1/x. @@ -196,7 +224,7 @@ var ( // StringReplace replaces old with new in the first n occurrences of a string. StringReplace = ast.FunctionSym{"fn:string:replace", 4} - // Time functions +// Time functions // TimeNow returns the current time as nanoseconds since Unix epoch. TimeNow = ast.FunctionSym{"fn:time:now", 0} @@ -259,6 +287,7 @@ var ( // Units: h (hours), m (minutes), s (seconds), ms (milliseconds), us/µs (microseconds), ns (nanoseconds) DurationParse = ast.FunctionSym{"fn:duration:parse", 1} + // PairType is a constructor for a pair type. PairType = ast.FunctionSym{"fn:Pair", 2} // TupleType is a type-level function that returns a tuple type out of pair types.