package parser import ( "eq2emu/internal/common" "fmt" "strconv" "strings" "sync" ) 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)} }, } ) 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 templates map[string]*PacketDef tagStack []string fieldNames []string } // Type mapping using corrected type constants var typeMap = map[string]common.EQ2DataType{ "u8": common.TypeInt8, "u16": common.TypeInt16, "u32": common.TypeInt32, "u64": common.TypeInt64, "i8": common.TypeSInt8, "i16": common.TypeSInt16, "i32": common.TypeSInt32, "i64": common.TypeSInt64, "f32": common.TypeFloat, "f64": common.TypeDouble, "double": common.TypeDouble, "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), templates: 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 } switch tag[0] { case 'u': switch tag { case "u8": return common.TypeInt8, true case "u16": return common.TypeInt16, true case "u32": return common.TypeInt32, true case "u64": return common.TypeInt64, true } case 'i': switch tag { case "i8": return common.TypeSInt8, true case "i16": return common.TypeSInt16, true case "i32": return common.TypeSInt32, true case "i64": return common.TypeSInt64, true } case 's': switch tag { 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) } // 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 } case "template": name := p.current.Attributes["name"] if name != "" { err := p.parseTemplateDefinition(name) if err != nil { return nil, err } } else { p.advance() } 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 } case "template": err := p.parseTemplate(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 template definition and usage func (p *Parser) parseTemplate(packetDef *PacketDef, fieldOrder *[]string, prefix string) error { attrs := p.current.Attributes // Template definition: