812 lines
19 KiB
Go

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 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,
"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
}
// 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)
}
// 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: <template name="foo">
if templateName := attrs["name"]; templateName != "" {
return p.parseTemplateDefinition(templateName)
}
// Template usage: <template use="foo">
if templateUse := attrs["use"]; templateUse != "" {
return p.parseTemplateUsage(packetDef, fieldOrder, prefix, templateUse)
}
// No name or use attribute - skip
p.advance()
return nil
}
// Parses template definition and stores it
func (p *Parser) parseTemplateDefinition(templateName string) error {
templateDef := NewPacketDef(16)
fieldOrder := fieldOrderPool.Get().(*[]string)
*fieldOrder = (*fieldOrder)[:0]
defer fieldOrderPool.Put(fieldOrder)
if p.current.Type == TokenSelfCloseTag {
p.advance()
templateDef.Orders[1] = make([]string, len(*fieldOrder))
copy(templateDef.Orders[1], *fieldOrder)
p.templates[templateName] = templateDef
return nil
}
p.pushTag("template")
p.advance()
err := p.parseElements(templateDef, fieldOrder, "")
if err != nil {
return err
}
if p.current.Type == TokenCloseTag && p.current.Tag(p.input) == "template" {
if err := p.popTag("template"); err != nil {
return err
}
p.advance()
} else {
return fmt.Errorf("expected closing tag for template at line %d", p.current.Line)
}
templateDef.Orders[1] = make([]string, len(*fieldOrder))
copy(templateDef.Orders[1], *fieldOrder)
p.templates[templateName] = templateDef
return nil
}
// Injects template fields into current parsing context
func (p *Parser) parseTemplateUsage(packetDef *PacketDef, fieldOrder *[]string, prefix string, templateName string) error {
template, exists := p.templates[templateName]
if !exists {
return fmt.Errorf("undefined template '%s' at line %d", templateName, p.current.Line)
}
// Inject all template fields into current packet
if templateOrder, exists := template.Orders[1]; exists {
for _, fieldName := range templateOrder {
if fieldDesc, exists := template.Fields[fieldName]; exists {
// Apply prefix if provided
fullName := fieldName
if prefix != "" {
fullName = buildFieldName(prefix, fieldName)
}
packetDef.Fields[fullName] = fieldDesc
*fieldOrder = append(*fieldOrder, fullName)
}
}
}
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
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()
}