321 lines
7.4 KiB
Go
321 lines
7.4 KiB
Go
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
|
|
}
|