package ucxl import ( "fmt" "regexp" "strconv" "strings" ) // Address represents a parsed UCXL address // Format: ucxl://agent:role@project:task/temporal_segment/path type Address struct { // Core components Agent string `json:"agent"` Role string `json:"role"` Project string `json:"project"` Task string `json:"task"` // Temporal component TemporalSegment TemporalSegment `json:"temporal_segment"` // Path component Path string `json:"path"` // Original raw address for reference Raw string `json:"raw"` } // TemporalSegment represents temporal navigation information type TemporalSegment struct { Type TemporalType `json:"type"` Direction Direction `json:"direction,omitempty"` Count int `json:"count,omitempty"` } // TemporalType defines the type of temporal navigation type TemporalType string const ( TemporalLatest TemporalType = "latest" // *^ TemporalAny TemporalType = "any" // *~ TemporalSpecific TemporalType = "specific" // *~N TemporalRelative TemporalType = "relative" // ~~N, ^^N ) // Direction defines temporal navigation direction type Direction string const ( DirectionBackward Direction = "backward" // ~~ DirectionForward Direction = "forward" // ^^ ) // ValidationError represents an address validation error type ValidationError struct { Field string Message string Raw string } func (e ValidationError) Error() string { return fmt.Sprintf("UCXL address validation error in %s: %s (address: %s)", e.Field, e.Message, e.Raw) } // Regular expressions for validation var ( // Component validation patterns componentPattern = regexp.MustCompile(`^[a-zA-Z0-9_\-]+$|^any$`) pathPattern = regexp.MustCompile(`^[a-zA-Z0-9_\-/\.]*$`) // Temporal segment patterns temporalLatestPattern = regexp.MustCompile(`^\*\^$`) // *^ temporalAnyPattern = regexp.MustCompile(`^\*~$`) // *~ temporalSpecificPattern = regexp.MustCompile(`^\*~(\d+)$`) // *~N temporalBackwardPattern = regexp.MustCompile(`^~~(\d+)$`) // ~~N temporalForwardPattern = regexp.MustCompile(`^\^\^(\d+)$`) // ^^N // Full address pattern for initial validation ucxlAddressPattern = regexp.MustCompile(`^ucxl://([^:]+):([^@]+)@([^:]+):([^/]+)/([^/]+)/?(.*)$`) ) // Parse parses a UCXL address string into an Address struct func Parse(address string) (*Address, error) { if address == "" { return nil, &ValidationError{ Field: "address", Message: "address cannot be empty", Raw: address, } } // Normalize the address (trim whitespace, convert to lowercase for scheme) normalized := strings.TrimSpace(address) if !strings.HasPrefix(strings.ToLower(normalized), "ucxl://") { return nil, &ValidationError{ Field: "scheme", Message: "address must start with 'ucxl://'", Raw: address, } } // Check scheme manually since our format doesn't follow standard URL format if !strings.HasPrefix(strings.ToLower(normalized), "ucxl://") { return nil, &ValidationError{ Field: "scheme", Message: "scheme must be 'ucxl'", Raw: address, } } // Use regex for detailed component extraction // Convert to lowercase for scheme but keep original for case-sensitive parts normalizedForPattern := strings.ToLower(normalized[:7]) + normalized[7:] // normalize "ucxl://" part matches := ucxlAddressPattern.FindStringSubmatch(normalizedForPattern) if matches == nil || len(matches) != 7 { return nil, &ValidationError{ Field: "format", Message: "address format must be 'ucxl://agent:role@project:task/temporal_segment/path'", Raw: address, } } addr := &Address{ Agent: normalizeComponent(matches[1]), Role: normalizeComponent(matches[2]), Project: normalizeComponent(matches[3]), Task: normalizeComponent(matches[4]), Path: matches[6], // Path can be empty Raw: address, } // Parse temporal segment temporalSegment, err := parseTemporalSegment(matches[5]) if err != nil { return nil, &ValidationError{ Field: "temporal_segment", Message: err.Error(), Raw: address, } } addr.TemporalSegment = *temporalSegment // Validate all components if err := addr.Validate(); err != nil { return nil, err } return addr, nil } // parseTemporalSegment parses the temporal segment component func parseTemporalSegment(segment string) (*TemporalSegment, error) { if segment == "" { return nil, fmt.Errorf("temporal segment cannot be empty") } // Check for latest (*^) if temporalLatestPattern.MatchString(segment) { return &TemporalSegment{Type: TemporalLatest}, nil } // Check for any (*~) if temporalAnyPattern.MatchString(segment) { return &TemporalSegment{Type: TemporalAny}, nil } // Check for specific version (*~N) if matches := temporalSpecificPattern.FindStringSubmatch(segment); matches != nil { count, err := strconv.Atoi(matches[1]) if err != nil { return nil, fmt.Errorf("invalid version number in specific temporal segment: %s", matches[1]) } if count < 0 { return nil, fmt.Errorf("version number cannot be negative: %d", count) } return &TemporalSegment{ Type: TemporalSpecific, Count: count, }, nil } // Check for backward navigation (~~N) if matches := temporalBackwardPattern.FindStringSubmatch(segment); matches != nil { count, err := strconv.Atoi(matches[1]) if err != nil { return nil, fmt.Errorf("invalid count in backward temporal segment: %s", matches[1]) } if count < 0 { return nil, fmt.Errorf("backward count cannot be negative: %d", count) } return &TemporalSegment{ Type: TemporalRelative, Direction: DirectionBackward, Count: count, }, nil } // Check for forward navigation (^^N) if matches := temporalForwardPattern.FindStringSubmatch(segment); matches != nil { count, err := strconv.Atoi(matches[1]) if err != nil { return nil, fmt.Errorf("invalid count in forward temporal segment: %s", matches[1]) } if count < 0 { return nil, fmt.Errorf("forward count cannot be negative: %d", count) } return &TemporalSegment{ Type: TemporalRelative, Direction: DirectionForward, Count: count, }, nil } return nil, fmt.Errorf("invalid temporal segment format: %s", segment) } // normalizeComponent normalizes address components (case-insensitive) func normalizeComponent(component string) string { return strings.ToLower(strings.TrimSpace(component)) } // Validate validates the Address components according to BNF grammar rules func (a *Address) Validate() error { // Validate agent component if err := validateComponent("agent", a.Agent); err != nil { return &ValidationError{ Field: "agent", Message: err.Error(), Raw: a.Raw, } } // Validate role component if err := validateComponent("role", a.Role); err != nil { return &ValidationError{ Field: "role", Message: err.Error(), Raw: a.Raw, } } // Validate project component if err := validateComponent("project", a.Project); err != nil { return &ValidationError{ Field: "project", Message: err.Error(), Raw: a.Raw, } } // Validate task component if err := validateComponent("task", a.Task); err != nil { return &ValidationError{ Field: "task", Message: err.Error(), Raw: a.Raw, } } // Validate path component (can be empty) if a.Path != "" && !pathPattern.MatchString(a.Path) { return &ValidationError{ Field: "path", Message: "path can only contain alphanumeric characters, underscores, hyphens, forward slashes, and dots", Raw: a.Raw, } } return nil } // validateComponent validates individual address components func validateComponent(name, component string) error { if component == "" { return fmt.Errorf("%s cannot be empty", name) } if !componentPattern.MatchString(component) { return fmt.Errorf("%s can only contain alphanumeric characters, underscores, hyphens, or be 'any'", name) } return nil } // String returns the canonical string representation of the address func (a *Address) String() string { temporalStr := a.TemporalSegment.String() if a.Path != "" { return fmt.Sprintf("ucxl://%s:%s@%s:%s/%s/%s", a.Agent, a.Role, a.Project, a.Task, temporalStr, a.Path) } return fmt.Sprintf("ucxl://%s:%s@%s:%s/%s", a.Agent, a.Role, a.Project, a.Task, temporalStr) } // String returns the string representation of the temporal segment func (ts *TemporalSegment) String() string { switch ts.Type { case TemporalLatest: return "*^" case TemporalAny: return "*~" case TemporalSpecific: return fmt.Sprintf("*~%d", ts.Count) case TemporalRelative: if ts.Direction == DirectionBackward { return fmt.Sprintf("~~%d", ts.Count) } return fmt.Sprintf("^^%d", ts.Count) default: return "*^" // Default to latest } } // IsWildcard returns true if the address uses wildcard patterns func (a *Address) IsWildcard() bool { return a.Agent == "any" || a.Role == "any" || a.Project == "any" || a.Task == "any" } // Matches returns true if this address matches the pattern address // Supports wildcard matching where "any" matches any value func (a *Address) Matches(pattern *Address) bool { if pattern == nil { return false } // Check each component for wildcard or exact match if pattern.Agent != "any" && a.Agent != pattern.Agent { return false } if pattern.Role != "any" && a.Role != pattern.Role { return false } if pattern.Project != "any" && a.Project != pattern.Project { return false } if pattern.Task != "any" && a.Task != pattern.Task { return false } // Path matching (if pattern has path, address must match or be subset) if pattern.Path != "" { if a.Path == "" { return false } // Simple prefix matching for paths if !strings.HasPrefix(a.Path, pattern.Path) { return false } } return true } // Clone creates a deep copy of the address func (a *Address) Clone() *Address { return &Address{ Agent: a.Agent, Role: a.Role, Project: a.Project, Task: a.Task, TemporalSegment: a.TemporalSegment, // TemporalSegment is a value type, safe to copy Path: a.Path, Raw: a.Raw, } } // IsValid performs comprehensive validation and returns true if the address is valid func (a *Address) IsValid() bool { return a.Validate() == nil }