package parser import ( "eq2emu/internal/common" "fmt" "strconv" "strings" "sync" ) // Object pools for reducing allocations var ( fieldOrderPool = sync.Pool{ New: func() any { slice := make([]string, 0, 32) return &slice }, } conditionBuilder = sync.Pool{ New: func() any { buf := make([]byte, 0, 128) return &buf }, } stringBuilderPool = sync.Pool{ New: func() any { return &stringBuilder{buf: make([]byte, 0, 64)} }, } ) // String builder for efficient concatenation type stringBuilder struct { buf []byte } func (sb *stringBuilder) reset() { sb.buf = sb.buf[:0] } func (sb *stringBuilder) writeString(s string) { sb.buf = append(sb.buf, s...) } func (sb *stringBuilder) string() string { return string(sb.buf) } // Parses PML into PacketDef structures type Parser struct { lexer *Lexer current *Token input string substructs map[string]*PacketDef tagStack []string fieldNames []string } // Type mapping for efficient lookup var typeMap = map[string]common.EQ2DataType{ "i8": common.TypeInt8, "i16": common.TypeInt16, "i32": common.TypeInt32, "i64": common.TypeInt64, "si8": common.TypeSInt8, "si16": common.TypeSInt16, "si32": common.TypeSInt32, "si64": common.TypeSInt64, "f32": common.TypeFloat, "f64": common.TypeDouble, "double": common.TypeDouble, // XML compatibility "str8": common.TypeString8, "str16": common.TypeString16, "str32": common.TypeString32, "char": common.TypeChar, "color": common.TypeColor, "equip": common.TypeEquipment, "array": common.TypeArray, } // Creates a new parser func NewParser(input string) *Parser { parser := &Parser{ lexer: NewLexer(input), input: input, substructs: make(map[string]*PacketDef), tagStack: make([]string, 0, 16), fieldNames: make([]string, 0, 8), } parser.advance() return parser } // Moves to next token with cleanup func (p *Parser) advance() { if p.current != nil { p.lexer.ReleaseToken(p.current) } p.current = p.lexer.NextToken() // Skip comments for p.current.Type == TokenComment { p.lexer.ReleaseToken(p.current) p.current = p.lexer.NextToken() } } // Cleanup resources func (p *Parser) cleanup() { if p.current != nil { p.lexer.ReleaseToken(p.current) p.current = nil } } // Pushes tag onto stack for validation func (p *Parser) pushTag(tag string) { p.tagStack = append(p.tagStack, tag) } // Pops and validates closing tag func (p *Parser) popTag(expectedTag string) error { if len(p.tagStack) == 0 { return fmt.Errorf("unexpected closing tag '%s' at line %d", expectedTag, p.current.Line) } lastTag := p.tagStack[len(p.tagStack)-1] if lastTag != expectedTag { return fmt.Errorf("mismatched closing tag: expected '%s', got '%s' at line %d", lastTag, expectedTag, p.current.Line) } p.tagStack = p.tagStack[:len(p.tagStack)-1] return nil } // Checks for unclosed tags func (p *Parser) validateAllTagsClosed() error { if len(p.tagStack) > 0 { return fmt.Errorf("unclosed tag '%s'", p.tagStack[len(p.tagStack)-1]) } return nil } // Fast type lookup using first character func getDataType(tag string) (common.EQ2DataType, bool) { if len(tag) == 0 { return 0, false } // Fast path for common types switch tag[0] { case 'i': switch tag { case "i8": return common.TypeInt8, true case "i16": return common.TypeInt16, true case "i32": return common.TypeInt32, true case "i64": return common.TypeInt64, true } case 's': switch tag { case "si8": return common.TypeSInt8, true case "si16": return common.TypeSInt16, true case "si32": return common.TypeSInt32, true case "si64": return common.TypeSInt64, true case "str8": return common.TypeString8, true case "str16": return common.TypeString16, true case "str32": return common.TypeString32, true } case 'f': switch tag { case "f32": return common.TypeFloat, true case "f64": return common.TypeDouble, true } case 'd': if tag == "double" { return common.TypeDouble, true } case 'c': switch tag { case "char": return common.TypeChar, true case "color": return common.TypeColor, true } case 'e': if tag == "equip" { return common.TypeEquipment, true } case 'a': if tag == "array" { return common.TypeArray, true } } // Fallback to map lookup if dataType, exists := typeMap[tag]; exists { return dataType, true } return 0, false } // Parses field names with minimal allocations func (p *Parser) parseFieldNames(nameAttr string) []string { if nameAttr == "" { return nil } // Fast path for single name if strings.IndexByte(nameAttr, ',') == -1 { name := strings.TrimSpace(nameAttr) if name != "" { p.fieldNames = p.fieldNames[:0] p.fieldNames = append(p.fieldNames, name) return p.fieldNames } return nil } // Parse multiple names efficiently p.fieldNames = p.fieldNames[:0] start := 0 for i := 0; i < len(nameAttr); i++ { if nameAttr[i] == ',' { if name := strings.TrimSpace(nameAttr[start:i]); name != "" { p.fieldNames = append(p.fieldNames, name) } start = i + 1 } } if name := strings.TrimSpace(nameAttr[start:]); name != "" { p.fieldNames = append(p.fieldNames, name) } return p.fieldNames } // Builds field name with prefix efficiently func buildFieldName(prefix, name string) string { if prefix == "" { return name } sb := stringBuilderPool.Get().(*stringBuilder) sb.reset() defer stringBuilderPool.Put(sb) sb.writeString(prefix) sb.writeString(name) return sb.string() } // Combines conditions with AND logic func combineConditions(cond1, cond2 string) string { if cond1 == "" { return cond2 } if cond2 == "" { return cond1 } bufPtr := conditionBuilder.Get().(*[]byte) buf := *bufPtr buf = buf[:0] defer conditionBuilder.Put(bufPtr) buf = append(buf, cond1...) buf = append(buf, '&') buf = append(buf, cond2...) *bufPtr = buf return string(buf) } // Creates packet definition with estimated capacity func NewPacketDef(estimatedFields int) *PacketDef { return &PacketDef{ Fields: make(map[string]FieldDesc, estimatedFields), Orders: make(map[uint32][]string, 4), } } // Parses the entire PML document func (p *Parser) Parse() (map[string]*PacketDef, error) { packets := make(map[string]*PacketDef) for p.current.Type != TokenEOF { if p.current.Type == TokenError { return nil, fmt.Errorf("parse error at line %d, col %d", p.current.Line, p.current.Col) } if p.current.Type == TokenOpenTag || p.current.Type == TokenSelfCloseTag { switch p.current.Tag(p.input) { case "packet": name := p.current.Attributes["name"] packet, err := p.parsePacket() if err != nil { return nil, err } if name != "" { packets[name] = packet } case "substruct": name := p.current.Attributes["name"] substruct, err := p.parseSubstruct() if err != nil { return nil, err } if name != "" { p.substructs[name] = substruct } default: p.advance() } } else { p.advance() } } if err := p.validateAllTagsClosed(); err != nil { return nil, err } return packets, nil } // Parses a packet element with version children func (p *Parser) parsePacket() (*PacketDef, error) { packetDef := NewPacketDef(16) if p.current.Type == TokenSelfCloseTag { p.advance() return packetDef, nil } p.pushTag("packet") p.advance() for p.current.Type != TokenEOF && !(p.current.Type == TokenCloseTag && p.current.Tag(p.input) == "packet") { if p.current.Type == TokenOpenTag && p.current.Tag(p.input) == "version" { err := p.parseVersion(packetDef) if err != nil { return nil, err } } else { p.advance() } } if p.current.Type == TokenCloseTag && p.current.Tag(p.input) == "packet" { if err := p.popTag("packet"); err != nil { return nil, err } p.advance() } else { return nil, fmt.Errorf("expected closing tag for packet at line %d", p.current.Line) } return packetDef, nil } // Parses a version element func (p *Parser) parseVersion(packetDef *PacketDef) error { attrs := p.current.Attributes version := uint32(1) if v := attrs["number"]; v != "" { if parsed, err := strconv.ParseUint(v, 10, 32); err == nil { version = uint32(parsed) } } fieldOrder := fieldOrderPool.Get().(*[]string) *fieldOrder = (*fieldOrder)[:0] defer fieldOrderPool.Put(fieldOrder) if p.current.Type == TokenSelfCloseTag { p.advance() packetDef.Orders[version] = make([]string, len(*fieldOrder)) copy(packetDef.Orders[version], *fieldOrder) return nil } p.pushTag("version") p.advance() err := p.parseElements(packetDef, fieldOrder, "") if err != nil { return err } if p.current.Type == TokenCloseTag && p.current.Tag(p.input) == "version" { if err := p.popTag("version"); err != nil { return err } p.advance() } else { return fmt.Errorf("expected closing tag for version at line %d", p.current.Line) } packetDef.Orders[version] = make([]string, len(*fieldOrder)) copy(packetDef.Orders[version], *fieldOrder) return nil } // Parses a substruct element func (p *Parser) parseSubstruct() (*PacketDef, error) { packetDef := NewPacketDef(16) fieldOrder := fieldOrderPool.Get().(*[]string) *fieldOrder = (*fieldOrder)[:0] defer fieldOrderPool.Put(fieldOrder) if p.current.Type == TokenSelfCloseTag { p.advance() packetDef.Orders[1] = make([]string, len(*fieldOrder)) copy(packetDef.Orders[1], *fieldOrder) return packetDef, nil } p.pushTag("substruct") p.advance() err := p.parseElements(packetDef, fieldOrder, "") if err != nil { return nil, err } if p.current.Type == TokenCloseTag && p.current.Tag(p.input) == "substruct" { if err := p.popTag("substruct"); err != nil { return nil, err } p.advance() } else { return nil, fmt.Errorf("expected closing tag for substruct") } packetDef.Orders[1] = make([]string, len(*fieldOrder)) copy(packetDef.Orders[1], *fieldOrder) return packetDef, nil } // Processes child elements func (p *Parser) parseElements(packetDef *PacketDef, fieldOrder *[]string, prefix string) error { for p.current.Type != TokenEOF && !(p.current.Type == TokenCloseTag) { switch p.current.Type { case TokenOpenTag, TokenSelfCloseTag: switch p.current.Tag(p.input) { case "group": err := p.parseGroup(packetDef, fieldOrder, prefix) if err != nil { return err } case "array": err := p.parseArray(packetDef, fieldOrder, prefix) if err != nil { return err } default: err := p.parseField(packetDef, fieldOrder, prefix) if err != nil { return err } } case TokenText: p.advance() default: p.advance() } } return nil } // Handles group elements func (p *Parser) parseGroup(packetDef *PacketDef, fieldOrder *[]string, prefix string) error { attrs := p.current.Attributes groupPrefix := prefix if name := attrs["name"]; name != "" { if prefix == "" { groupPrefix = name + "_" } else { groupPrefix = prefix + name + "_" } } if p.current.Type == TokenSelfCloseTag { p.advance() return nil } p.pushTag("group") p.advance() err := p.parseElements(packetDef, fieldOrder, groupPrefix) if err != nil { return err } if p.current.Type == TokenCloseTag && p.current.Tag(p.input) == "group" { if err := p.popTag("group"); err != nil { return err } p.advance() } else { return fmt.Errorf("expected closing tag for group at line %d", p.current.Line) } return nil } // Handles array elements - FIXED: Remove redundant substruct wrapper func (p *Parser) parseArray(packetDef *PacketDef, fieldOrder *[]string, prefix string) error { attrs := p.current.Attributes var arrayName string if prefix == "" { arrayName = attrs["name"] } else { arrayName = buildFieldName(prefix, attrs["name"]) } fieldDesc := FieldDesc{ Type: common.TypeArray, Condition: attrs["count"], AddToStruct: true, // Default to true } if ifCond := attrs["if"]; ifCond != "" { fieldDesc.Condition = combineConditions(fieldDesc.Condition, ifCond) } // Parse additional attributes if maxSize := attrs["max_size"]; maxSize != "" { if m, err := strconv.Atoi(maxSize); err == nil { fieldDesc.MaxArraySize = m } else { return fmt.Errorf("invalid max_size value '%s' at line %d: %v", maxSize, p.current.Line, err) } } if optional := attrs["optional"]; optional == "true" { fieldDesc.Optional = true } if addToStruct := attrs["add_to_struct"]; addToStruct == "false" { fieldDesc.AddToStruct = false } // Handle substruct reference if substruct := attrs["substruct"]; substruct != "" { if subDef, exists := p.substructs[substruct]; exists { fieldDesc.SubDef = subDef } } // Arrays with substruct references or explicit self-closing syntax are self-closing if p.current.Type == TokenSelfCloseTag || fieldDesc.SubDef != nil { p.advance() packetDef.Fields[arrayName] = fieldDesc *fieldOrder = append(*fieldOrder, arrayName) return nil } p.pushTag("array") p.advance() // Handle direct child elements as substruct fields (no wrapper needed) if fieldDesc.SubDef == nil { subDef := NewPacketDef(16) subOrder := fieldOrderPool.Get().(*[]string) *subOrder = (*subOrder)[:0] defer fieldOrderPool.Put(subOrder) err := p.parseElements(subDef, subOrder, "") if err != nil { return err } // Only create substruct if we actually have fields if len(*subOrder) > 0 { subDef.Orders[1] = make([]string, len(*subOrder)) copy(subDef.Orders[1], *subOrder) fieldDesc.SubDef = subDef } } if p.current.Type == TokenCloseTag && p.current.Tag(p.input) == "array" { if err := p.popTag("array"); err != nil { return err } p.advance() } else { return fmt.Errorf("expected closing tag for array at line %d", p.current.Line) } packetDef.Fields[arrayName] = fieldDesc *fieldOrder = append(*fieldOrder, arrayName) return nil } // Handles field elements func (p *Parser) parseField(packetDef *PacketDef, fieldOrder *[]string, prefix string) error { tagName := p.current.Tag(p.input) attrs := p.current.Attributes dataType, exists := getDataType(tagName) if !exists { p.advance() return nil } nameAttr := attrs["name"] if nameAttr == "" { p.advance() return nil } names := p.parseFieldNames(nameAttr) for _, name := range names { var fullName string if prefix == "" { fullName = name } else { fullName = buildFieldName(prefix, name) } fieldDesc := FieldDesc{ Type: dataType, Condition: attrs["if"], AddToStruct: true, // Default to true AddType: dataType, } // Parse size attribute if size := attrs["size"]; size != "" { if s, err := strconv.Atoi(size); err == nil { fieldDesc.Length = s } else { return fmt.Errorf("invalid size value '%s' at line %d: %v", size, p.current.Line, err) } } // Parse oversized attribute if oversized := attrs["oversized"]; oversized != "" { if o, err := strconv.Atoi(oversized); err == nil { fieldDesc.Oversized = o } else { return fmt.Errorf("invalid oversized value '%s' at line %d: %v", oversized, p.current.Line, err) } } // Parse type2 attributes if type2 := attrs["type2"]; type2 != "" { if t2, exists := getDataType(type2); exists { fieldDesc.Type2 = t2 fieldDesc.Type2Cond = attrs["type2_if"] } } // Parse default value if defaultVal := attrs["default"]; defaultVal != "" { if d, err := strconv.ParseInt(defaultVal, 10, 8); err == nil { fieldDesc.DefaultValue = int8(d) } else { return fmt.Errorf("invalid default value '%s' at line %d: %v", defaultVal, p.current.Line, err) } } // Parse max_size if maxSize := attrs["max_size"]; maxSize != "" { if m, err := strconv.Atoi(maxSize); err == nil { fieldDesc.MaxArraySize = m } else { return fmt.Errorf("invalid max_size value '%s' at line %d: %v", maxSize, p.current.Line, err) } } // Parse optional if optional := attrs["optional"]; optional == "true" { fieldDesc.Optional = true } // Parse add_to_struct if addToStruct := attrs["add_to_struct"]; addToStruct == "false" { fieldDesc.AddToStruct = false } // Parse add_type if addType := attrs["add_type"]; addType != "" { if at, exists := getDataType(addType); exists { fieldDesc.AddType = at } } packetDef.Fields[fullName] = fieldDesc *fieldOrder = append(*fieldOrder, fullName) } p.advance() return nil } // Parses PML content and returns PacketDef map func Parse(content string) (map[string]*PacketDef, error) { parser := NewParser(content) defer parser.cleanup() return parser.Parse() }