package cps import ( "fmt" "regexp" "strconv" "strings" ) // Parser handles CSS-like packet definition parsing type Parser struct { content string } // NewParser creates a parser for CSS-like packet definitions func NewParser(content string) *Parser { return &Parser{content: content} } // Parse parses the CSS content and returns packet definitions func (p *Parser) Parse() (map[string]*PacketDef, error) { packets := make(map[string]*PacketDef) // Remove comments and normalize cleaned := p.removeComments(p.content) // Parse packets using lexer approach i := 0 for i < len(cleaned) { // Skip whitespace for i < len(cleaned) && (cleaned[i] == ' ' || cleaned[i] == '\t' || cleaned[i] == '\n' || cleaned[i] == '\r') { i++ } if i >= len(cleaned) { break } // Look for packet name (word followed by {) nameStart := i for i < len(cleaned) && ((cleaned[i] >= 'A' && cleaned[i] <= 'Z') || (cleaned[i] >= 'a' && cleaned[i] <= 'z') || (cleaned[i] >= '0' && cleaned[i] <= '9') || cleaned[i] == '_') { i++ } if i == nameStart { i++ continue } packetName := cleaned[nameStart:i] // Skip whitespace to find opening brace for i < len(cleaned) && (cleaned[i] == ' ' || cleaned[i] == '\t' || cleaned[i] == '\n' || cleaned[i] == '\r') { i++ } if i >= len(cleaned) || cleaned[i] != '{' { i++ continue } // Find matching closing brace braceCount := 1 contentStart := i + 1 i++ for i < len(cleaned) && braceCount > 0 { if cleaned[i] == '{' { braceCount++ } else if cleaned[i] == '}' { braceCount-- } if braceCount > 0 { i++ } } if braceCount != 0 { return nil, fmt.Errorf("unmatched brace for packet %s", packetName) } // Extract packet body packetBody := cleaned[contentStart:i] packet, err := p.parsePacket(packetName, packetBody) if err != nil { return nil, fmt.Errorf("parsing packet %s: %w", packetName, err) } packets[packetName] = packet i++ // Skip closing brace } return packets, nil } // removeComments strips CSS-style comments func (p *Parser) removeComments(content string) string { commentRegex := regexp.MustCompile(`/\*.*?\*/`) return commentRegex.ReplaceAllString(content, "") } // parsePacket parses a single packet definition func (p *Parser) parsePacket(name, body string) (*PacketDef, error) { packet := &PacketDef{ Name: name, Versions: make(map[int]*VersionDef), } i := 0 for i < len(body) { // Skip whitespace for i < len(body) && (body[i] == ' ' || body[i] == '\t' || body[i] == '\n' || body[i] == '\r') { i++ } if i >= len(body) { break } // Look for .v pattern if i+2 < len(body) && body[i] == '.' && body[i+1] == 'v' { i += 2 // Parse version number versionStart := i for i < len(body) && body[i] >= '0' && body[i] <= '9' { i++ } if i == versionStart { return nil, fmt.Errorf("invalid version number after .v") } versionNum, err := strconv.Atoi(body[versionStart:i]) if err != nil { return nil, fmt.Errorf("invalid version number: %s", body[versionStart:i]) } // Skip whitespace to find opening brace for i < len(body) && (body[i] == ' ' || body[i] == '\t' || body[i] == '\n' || body[i] == '\r') { i++ } if i >= len(body) || body[i] != '{' { return nil, fmt.Errorf("expected opening brace for version %d", versionNum) } // Find matching closing brace braceCount := 1 contentStart := i + 1 i++ for i < len(body) && braceCount > 0 { if body[i] == '{' { braceCount++ } else if body[i] == '}' { braceCount-- } if braceCount > 0 { i++ } } if braceCount != 0 { return nil, fmt.Errorf("unmatched brace for version %d", versionNum) } // Extract content between braces versionBody := body[contentStart:i] fields, fieldOrder, err := p.parseFields(versionBody) if err != nil { return nil, fmt.Errorf("parsing version %d: %w", versionNum, err) } packet.Versions[versionNum] = &VersionDef{ Version: versionNum, Fields: fields, FieldOrder: fieldOrder, } i++ // Skip closing brace } else { i++ } } return packet, nil } // findMatchingBrace is no longer needed with lexer approach // parseFields parses field definitions within a version block func (p *Parser) parseFields(fieldsBody string) (map[string]*FieldDef, []string, error) { fields := make(map[string]*FieldDef) var fieldOrder []string // Split by semicolon and parse each field fieldLines := strings.Split(fieldsBody, ";") for _, line := range fieldLines { line = strings.TrimSpace(line) if line == "" { continue } field, err := p.parseField(line) if err != nil { return nil, nil, err } if field != nil { fields[field.Name] = field fieldOrder = append(fieldOrder, field.Name) } } return fields, fieldOrder, nil } // parseField parses a single field definition func (p *Parser) parseField(line string) (*FieldDef, error) { // Handle field_name: type @position syntax parts := strings.Split(line, ":") if len(parts) != 2 { return nil, fmt.Errorf("invalid field syntax: %s", line) } name := strings.TrimSpace(parts[0]) typePart := strings.TrimSpace(parts[1]) field := &FieldDef{ Name: name, Position: -1, } // Handle special "none" type for removal if typePart == "none" { field.Remove = true return field, nil } // Parse position modifiers (@0, @after(field), etc.) if strings.Contains(typePart, "@") { parts := strings.Split(typePart, "@") typePart = strings.TrimSpace(parts[0]) positionPart := strings.TrimSpace(parts[1]) if err := p.parsePosition(field, positionPart); err != nil { return nil, err } } // Parse type and array syntax if err := p.parseType(field, typePart); err != nil { return nil, err } return field, nil } // parsePosition parses position modifiers like @0, @after(field) func (p *Parser) parsePosition(field *FieldDef, positionPart string) error { // Try to parse as numeric position if pos, err := strconv.Atoi(positionPart); err == nil { field.Position = pos return nil } if strings.HasPrefix(positionPart, "after(") && strings.HasSuffix(positionPart, ")") { refField := positionPart[6 : len(positionPart)-1] field.After = refField return nil } if strings.HasPrefix(positionPart, "before(") && strings.HasSuffix(positionPart, ")") { refField := positionPart[7 : len(positionPart)-1] field.Before = refField return nil } return fmt.Errorf("invalid position syntax: %s", positionPart) } // parseType parses type definitions including arrays func (p *Parser) parseType(field *FieldDef, typePart string) error { // Handle array syntax type[size] or type[] if strings.Contains(typePart, "[") { arrayRegex := regexp.MustCompile(`(\w+)\[(\d*)\]`) matches := arrayRegex.FindStringSubmatch(typePart) if len(matches) != 3 { return fmt.Errorf("invalid array syntax: %s", typePart) } baseType := matches[1] sizeStr := matches[2] eq2Type, exists := typeMap[baseType] if !exists { return fmt.Errorf("unknown type: %s", baseType) } field.Type = eq2Type if sizeStr == "" { field.Size = -1 // Dynamic array } else { size, err := strconv.Atoi(sizeStr) if err != nil { return fmt.Errorf("invalid array size: %s", sizeStr) } field.Size = size } } else { // Scalar type eq2Type, exists := typeMap[typePart] if !exists { return fmt.Errorf("unknown type: %s", typePart) } field.Type = eq2Type field.Size = 0 } return nil }