eq2go/internal/cps/parser.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
}