1200 lines
27 KiB
Go

package parser
import (
"eq2emu/internal/common"
"fmt"
"strconv"
"strings"
"unicode"
)
// Parser is a single-pass recursive descent parser for packet definitions
type Parser struct {
input string
pos int
line int
col int
substructs map[string]*PacketDef
templates map[string]*PacketDef
tagStack []string
}
// NewParser creates a new string-based parser
func NewParser(input string) *Parser {
return &Parser{
input: input,
line: 1,
col: 1,
substructs: make(map[string]*PacketDef),
templates: make(map[string]*PacketDef),
tagStack: make([]string, 0, 16),
}
}
// peek returns the current character without advancing
func (p *Parser) peek() rune {
if p.pos >= len(p.input) {
return 0
}
return rune(p.input[p.pos])
}
// peekN looks ahead n characters
func (p *Parser) peekN(n int) rune {
pos := p.pos + n
if pos >= len(p.input) {
return 0
}
return rune(p.input[pos])
}
// advance moves to the next character and returns it
func (p *Parser) advance() rune {
if p.pos >= len(p.input) {
return 0
}
ch := rune(p.input[p.pos])
p.pos++
if ch == '\n' {
p.line++
p.col = 1
} else {
p.col++
}
return ch
}
// skipWhitespace skips whitespace characters
func (p *Parser) skipWhitespace() {
for p.pos < len(p.input) && unicode.IsSpace(p.peek()) {
p.advance()
}
}
// consumeChar consumes the expected character, returns true if found
func (p *Parser) consumeChar(expected rune) bool {
if p.peek() == expected {
p.advance()
return true
}
return false
}
// consumeString consumes the expected string, returns true if found
func (p *Parser) consumeString(expected string) bool {
if p.pos+len(expected) > len(p.input) {
return false
}
if p.input[p.pos:p.pos+len(expected)] == expected {
for range expected {
p.advance()
}
return true
}
return false
}
// parseIdentifier parses an identifier (alphanumeric + underscore)
func (p *Parser) parseIdentifier() (string, error) {
start := p.pos
if p.pos >= len(p.input) || (!unicode.IsLetter(p.peek()) && p.peek() != '_') {
return "", fmt.Errorf("expected identifier at line %d, col %d", p.line, p.col)
}
for p.pos < len(p.input) {
ch := p.peek()
if unicode.IsLetter(ch) || unicode.IsDigit(ch) || ch == '_' || ch == '-' {
p.advance()
} else {
break
}
}
return p.input[start:p.pos], nil
}
// parseQuotedString parses a quoted string value
func (p *Parser) parseQuotedString() (string, error) {
quote := p.peek()
if quote != '"' && quote != '\'' {
return "", fmt.Errorf("expected quoted string at line %d, col %d", p.line, p.col)
}
p.advance() // consume opening quote
start := p.pos
for p.pos < len(p.input) && p.peek() != quote {
if p.peek() == '\\' {
p.advance() // consume backslash
if p.pos < len(p.input) {
p.advance() // consume escaped character
}
} else {
p.advance()
}
}
if p.pos >= len(p.input) {
return "", fmt.Errorf("unclosed quoted string at line %d", p.line)
}
value := p.input[start:p.pos]
p.advance() // consume closing quote
return value, nil
}
// parseAttributes parses tag attributes
func (p *Parser) parseAttributes() (map[string]string, error) {
attrs := make(map[string]string)
for {
p.skipWhitespace()
// Check for end of tag
if p.pos >= len(p.input) || p.peek() == '>' || (p.peek() == '/' && p.peekN(1) == '>') {
break
}
// Parse attribute name
name, err := p.parseIdentifier()
if err != nil {
return nil, err
}
p.skipWhitespace()
if !p.consumeChar('=') {
return nil, fmt.Errorf("expected '=' after attribute name '%s' at line %d", name, p.line)
}
p.skipWhitespace()
// Parse attribute value
value, err := p.parseQuotedString()
if err != nil {
return nil, err
}
attrs[name] = strings.TrimSpace(value)
}
return attrs, nil
}
// parseComment skips over comment content
func (p *Parser) parseComment() error {
// We're at the start of <!--
if !p.consumeString("<!--") {
return fmt.Errorf("expected '<!--' at line %d", p.line)
}
// Find closing -->
for p.pos+2 < len(p.input) {
if p.consumeString("-->") {
return nil
}
p.advance()
}
return fmt.Errorf("unclosed comment at line %d", p.line)
}
// Tag represents a parsed XML tag
type Tag struct {
Name string
Attributes map[string]string
SelfClosing bool
IsClosing bool
}
// parseTag parses an opening, closing, or self-closing tag
func (p *Parser) parseTag() (*Tag, error) {
if !p.consumeChar('<') {
return nil, fmt.Errorf("expected '<' at line %d", p.line)
}
// Check for comment
if p.peek() == '!' && p.peekN(1) == '-' && p.peekN(2) == '-' {
// We already consumed the '<', so parse comment from here
if !p.consumeString("!--") {
return nil, fmt.Errorf("expected '<!--' at line %d", p.line)
}
// Find closing -->
for p.pos+2 < len(p.input) {
if p.consumeString("-->") {
return p.parseTag() // recursively get the next tag
}
p.advance()
}
return nil, fmt.Errorf("unclosed comment at line %d", p.line)
}
tag := &Tag{}
// Check for closing tag
if p.consumeChar('/') {
tag.IsClosing = true
name, err := p.parseIdentifier()
if err != nil {
return nil, err
}
tag.Name = name
p.skipWhitespace()
if !p.consumeChar('>') {
return nil, fmt.Errorf("expected '>' after closing tag '%s' at line %d", name, p.line)
}
return tag, nil
}
// Parse tag name
name, err := p.parseIdentifier()
if err != nil {
return nil, err
}
tag.Name = name
// Parse attributes
attrs, err := p.parseAttributes()
if err != nil {
return nil, err
}
tag.Attributes = attrs
p.skipWhitespace()
// Check for self-closing
if p.consumeString("/>") {
tag.SelfClosing = true
} else if p.consumeChar('>') {
// Regular opening tag
} else {
return nil, fmt.Errorf("expected '>' or '/>' after tag '%s' at line %d", name, p.line)
}
return tag, nil
}
// pushTag pushes a tag name onto the stack for validation
func (p *Parser) pushTag(tag string) {
p.tagStack = append(p.tagStack, tag)
}
// popTag pops and validates the 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.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.line)
}
p.tagStack = p.tagStack[:len(p.tagStack)-1]
return nil
}
// validateAllTagsClosed 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
}
// Helper functions for robust attribute parsing
func (p *Parser) parseIntAttribute(value string, attributeName string) (int, error) {
if value == "" {
return 0, fmt.Errorf("empty %s attribute at line %d", attributeName, p.line)
}
cleanValue := strings.TrimSpace(value)
if cleanValue == "" {
return 0, fmt.Errorf("empty %s attribute value '%s' at line %d", attributeName, value, p.line)
}
result, err := strconv.Atoi(cleanValue)
if err != nil {
return 0, fmt.Errorf("invalid %s value '%s' at line %d: %v", attributeName, value, p.line, err)
}
return result, nil
}
func (p *Parser) parseUintAttribute(value string, attributeName string) (uint32, error) {
if value == "" {
return 0, fmt.Errorf("empty %s attribute at line %d", attributeName, p.line)
}
cleanValue := strings.TrimSpace(value)
if cleanValue == "" {
return 0, fmt.Errorf("empty %s attribute value '%s' at line %d", attributeName, value, p.line)
}
result, err := strconv.ParseUint(cleanValue, 10, 32)
if err != nil {
return 0, fmt.Errorf("invalid %s value '%s' at line %d: %v", attributeName, value, p.line, err)
}
return uint32(result), nil
}
func (p *Parser) parseInt8Attribute(value string, attributeName string) (int8, error) {
if value == "" {
return 0, fmt.Errorf("empty %s attribute at line %d", attributeName, p.line)
}
cleanValue := strings.TrimSpace(value)
if cleanValue == "" {
return 0, fmt.Errorf("empty %s attribute value '%s' at line %d", attributeName, value, p.line)
}
result, err := strconv.ParseInt(cleanValue, 10, 8)
if err != nil {
return 0, fmt.Errorf("invalid %s value '%s' at line %d: %v", attributeName, value, p.line, err)
}
return int8(result), nil
}
// parseFieldNames parses comma-separated field names
func (p *Parser) parseFieldNames(nameAttr string) []string {
if nameAttr == "" {
return nil
}
// Fast path for single name
if !strings.Contains(nameAttr, ",") {
name := strings.TrimSpace(nameAttr)
if name != "" {
return []string{name}
}
return nil
}
// Parse multiple names
names := strings.Split(nameAttr, ",")
result := make([]string, 0, len(names))
for _, name := range names {
if trimmed := strings.TrimSpace(name); trimmed != "" {
result = append(result, trimmed)
}
}
return result
}
// Parse parses the entire packet definition document
func (p *Parser) Parse() (map[string]*PacketDef, error) {
packets := make(map[string]*PacketDef)
for p.pos < len(p.input) {
p.skipWhitespace()
if p.pos >= len(p.input) {
break
}
if p.peek() != '<' {
return nil, fmt.Errorf("expected '<' at line %d", p.line)
}
// Handle comments at the top level
if p.peek() == '<' && p.peekN(1) == '!' && p.peekN(2) == '-' && p.peekN(3) == '-' {
err := p.parseComment()
if err != nil {
return nil, err
}
continue
}
tag, err := p.parseTag()
if err != nil {
return nil, err
}
switch tag.Name {
case "packet":
name := tag.Attributes["name"]
packet, err := p.parsePacket(tag)
if err != nil {
return nil, err
}
if name != "" {
packets[name] = packet
}
case "substruct":
name := tag.Attributes["name"]
substruct, err := p.parseSubstruct(tag)
if err != nil {
return nil, err
}
if name != "" {
p.substructs[name] = substruct
}
case "template":
name := tag.Attributes["name"]
if name != "" {
err := p.parseTemplateDefinition(tag, name)
if err != nil {
return nil, err
}
}
default:
return nil, fmt.Errorf("unexpected top-level tag '%s' at line %d", tag.Name, p.line)
}
}
if err := p.validateAllTagsClosed(); err != nil {
return nil, err
}
return packets, nil
}
// parsePacket parses a packet element
func (p *Parser) parsePacket(openTag *Tag) (*PacketDef, error) {
packetDef := NewPacketDef(16)
if openTag.SelfClosing {
return packetDef, nil
}
p.pushTag("packet")
for {
p.skipWhitespace()
if p.pos >= len(p.input) {
return nil, fmt.Errorf("unexpected end of input in packet")
}
if p.peek() != '<' {
return nil, fmt.Errorf("expected '<' at line %d", p.line)
}
// Handle comments
if p.peek() == '<' && p.peekN(1) == '!' && p.peekN(2) == '-' && p.peekN(3) == '-' {
err := p.parseComment()
if err != nil {
return nil, err
}
continue
}
tag, err := p.parseTag()
if err != nil {
return nil, err
}
if tag.IsClosing {
if tag.Name != "packet" {
return nil, fmt.Errorf("expected closing tag 'packet', got '%s' at line %d", tag.Name, p.line)
}
if err := p.popTag("packet"); err != nil {
return nil, err
}
break
}
if tag.Name == "version" {
err := p.parseVersion(tag, packetDef)
if err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf("unexpected tag '%s' in packet at line %d", tag.Name, p.line)
}
}
return packetDef, nil
}
// parseSubstruct parses a substruct element
func (p *Parser) parseSubstruct(openTag *Tag) (*PacketDef, error) {
packetDef := NewPacketDef(16)
if openTag.SelfClosing {
return packetDef, nil
}
p.pushTag("substruct")
// Check if this substruct contains version elements
hasVersions := false
for {
p.skipWhitespace()
if p.pos >= len(p.input) {
return nil, fmt.Errorf("unexpected end of input in substruct")
}
if p.peek() != '<' {
return nil, fmt.Errorf("expected '<' at line %d", p.line)
}
tag, err := p.parseTag()
if err != nil {
return nil, err
}
if tag.IsClosing {
if tag.Name != "substruct" {
return nil, fmt.Errorf("expected closing tag 'substruct', got '%s' at line %d", tag.Name, p.line)
}
if err := p.popTag("substruct"); err != nil {
return nil, err
}
break
}
if tag.Name == "version" {
hasVersions = true
err := p.parseVersion(tag, packetDef)
if err != nil {
return nil, err
}
} else if hasVersions {
return nil, fmt.Errorf("unexpected tag '%s' after version in substruct at line %d", tag.Name, p.line)
} else {
// No versions found, parse as direct elements
fieldOrder := make([]string, 0)
err := p.parseElement(tag, packetDef, &fieldOrder, "")
if err != nil {
return nil, err
}
// Continue parsing remaining elements
for {
p.skipWhitespace()
if p.pos >= len(p.input) || p.peek() != '<' {
break
}
nextTag, err := p.parseTag()
if err != nil {
return nil, err
}
if nextTag.IsClosing {
if nextTag.Name != "substruct" {
return nil, fmt.Errorf("expected closing tag 'substruct', got '%s' at line %d", nextTag.Name, p.line)
}
if err := p.popTag("substruct"); err != nil {
return nil, err
}
break
}
err = p.parseElement(nextTag, packetDef, &fieldOrder, "")
if err != nil {
return nil, err
}
}
// Set field order for version 1
packetDef.Orders[1] = make([]string, len(fieldOrder))
copy(packetDef.Orders[1], fieldOrder)
break
}
}
return packetDef, nil
}
// parseVersion parses a version element
func (p *Parser) parseVersion(openTag *Tag, packetDef *PacketDef) error {
version := uint32(1)
if v := openTag.Attributes["number"]; v != "" {
if parsed, err := p.parseUintAttribute(v, "number"); err == nil {
version = parsed
}
// Don't fail on invalid version numbers, just use default
}
fieldOrder := make([]string, 0)
if openTag.SelfClosing {
packetDef.Orders[version] = fieldOrder
return nil
}
p.pushTag("version")
for {
p.skipWhitespace()
if p.pos >= len(p.input) {
return fmt.Errorf("unexpected end of input in version")
}
if p.peek() != '<' {
return fmt.Errorf("expected '<' at line %d", p.line)
}
// Handle comments
if p.peek() == '<' && p.peekN(1) == '!' && p.peekN(2) == '-' && p.peekN(3) == '-' {
err := p.parseComment()
if err != nil {
return err
}
continue
}
tag, err := p.parseTag()
if err != nil {
return err
}
if tag.IsClosing {
if tag.Name != "version" {
return fmt.Errorf("expected closing tag 'version', got '%s' at line %d", tag.Name, p.line)
}
if err := p.popTag("version"); err != nil {
return err
}
break
}
err = p.parseElement(tag, packetDef, &fieldOrder, "")
if err != nil {
return err
}
}
packetDef.Orders[version] = make([]string, len(fieldOrder))
copy(packetDef.Orders[version], fieldOrder)
return nil
}
// parseElement parses any element (field, array, group, template, substruct reference)
func (p *Parser) parseElement(tag *Tag, packetDef *PacketDef, fieldOrder *[]string, prefix string) error {
switch tag.Name {
case "group":
return p.parseGroup(tag, packetDef, fieldOrder, prefix)
case "array":
return p.parseArray(tag, packetDef, fieldOrder, prefix)
case "template":
return p.parseTemplateUsage(tag, packetDef, fieldOrder, prefix)
case "substruct":
return p.parseSubstructReference(tag, packetDef, fieldOrder, prefix)
case "item":
return p.parseItemField(tag, packetDef, fieldOrder, prefix)
default:
// Try to parse as a field
return p.parseField(tag, packetDef, fieldOrder, prefix)
}
}
// parseGroup parses a group element
func (p *Parser) parseGroup(openTag *Tag, packetDef *PacketDef, fieldOrder *[]string, prefix string) error {
groupPrefix := prefix
if name := openTag.Attributes["name"]; name != "" {
if prefix == "" {
groupPrefix = name + "_"
} else {
groupPrefix = prefix + name + "_"
}
}
if openTag.SelfClosing {
return nil
}
p.pushTag("group")
for {
p.skipWhitespace()
if p.pos >= len(p.input) {
return fmt.Errorf("unexpected end of input in group")
}
if p.peek() != '<' {
return fmt.Errorf("expected '<' at line %d", p.line)
}
tag, err := p.parseTag()
if err != nil {
return err
}
if tag.IsClosing {
if tag.Name != "group" {
return fmt.Errorf("expected closing tag 'group', got '%s' at line %d", tag.Name, p.line)
}
if err := p.popTag("group"); err != nil {
return err
}
break
}
err = p.parseElement(tag, packetDef, fieldOrder, groupPrefix)
if err != nil {
return err
}
}
return nil
}
// parseArray parses an array element
func (p *Parser) parseArray(openTag *Tag, packetDef *PacketDef, fieldOrder *[]string, prefix string) error {
var arrayName string
if prefix == "" {
arrayName = openTag.Attributes["name"]
} else {
arrayName = prefix + openTag.Attributes["name"]
}
fieldDesc := FieldDesc{
Type: common.TypeArray,
Condition: openTag.Attributes["count"],
AddToStruct: true, // Default to true
}
if ifCond := openTag.Attributes["if"]; ifCond != "" {
fieldDesc.Condition = combineConditions(fieldDesc.Condition, ifCond)
}
// Parse additional attributes
if maxSize := openTag.Attributes["max_size"]; maxSize != "" {
if m, err := p.parseIntAttribute(maxSize, "max_size"); err == nil {
fieldDesc.MaxArraySize = m
} else {
return err
}
}
if optional := openTag.Attributes["optional"]; optional == "true" {
fieldDesc.Optional = true
}
if addToStruct := openTag.Attributes["add_to_struct"]; addToStruct == "false" {
fieldDesc.AddToStruct = false
}
// Handle substruct reference
if substruct := openTag.Attributes["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 openTag.SelfClosing || fieldDesc.SubDef != nil {
packetDef.Fields[arrayName] = fieldDesc
*fieldOrder = append(*fieldOrder, arrayName)
return nil
}
p.pushTag("array")
// Handle direct child elements as substruct fields
if fieldDesc.SubDef == nil {
subDef := NewPacketDef(16)
subOrder := make([]string, 0)
for {
p.skipWhitespace()
if p.pos >= len(p.input) {
return fmt.Errorf("unexpected end of input in array")
}
if p.peek() != '<' {
return fmt.Errorf("expected '<' at line %d", p.line)
}
tag, err := p.parseTag()
if err != nil {
return err
}
if tag.IsClosing {
if tag.Name != "array" {
return fmt.Errorf("expected closing tag 'array', got '%s' at line %d", tag.Name, p.line)
}
if err := p.popTag("array"); err != nil {
return err
}
break
}
err = p.parseElement(tag, 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
}
}
packetDef.Fields[arrayName] = fieldDesc
*fieldOrder = append(*fieldOrder, arrayName)
return nil
}
// combineConditions combines two conditions with AND logic - using existing function
// parseField parses a field element
func (p *Parser) parseField(openTag *Tag, packetDef *PacketDef, fieldOrder *[]string, prefix string) error {
dataType, exists := getDataType(openTag.Name)
if !exists {
return fmt.Errorf("unknown field type '%s' at line %d", openTag.Name, p.line)
}
nameAttr := openTag.Attributes["name"]
if nameAttr == "" {
return fmt.Errorf("field missing name attribute at line %d", p.line)
}
names := p.parseFieldNames(nameAttr)
for _, name := range names {
var fullName string
if prefix == "" {
fullName = name
} else {
fullName = prefix + name
}
fieldDesc := FieldDesc{
Type: dataType,
Condition: openTag.Attributes["if"],
AddToStruct: true, // Default to true
AddType: dataType,
}
// Parse size attribute
if size := openTag.Attributes["size"]; size != "" {
if s, err := p.parseIntAttribute(size, "size"); err == nil {
fieldDesc.Length = s
} else {
return err
}
}
// Parse oversized attribute
if oversized := openTag.Attributes["oversized"]; oversized != "" {
if o, err := p.parseIntAttribute(oversized, "oversized"); err == nil {
fieldDesc.Oversized = o
} else {
return err
}
}
// Parse type2 attributes
if type2 := openTag.Attributes["type2"]; type2 != "" {
if t2, exists := getDataType(type2); exists {
fieldDesc.Type2 = t2
fieldDesc.Type2Cond = openTag.Attributes["type2_if"]
}
}
// Parse default value
if defaultVal := openTag.Attributes["default"]; defaultVal != "" {
if d, err := p.parseInt8Attribute(defaultVal, "default"); err == nil {
fieldDesc.DefaultValue = d
} else {
return err
}
}
// Parse max_size
if maxSize := openTag.Attributes["max_size"]; maxSize != "" {
if m, err := p.parseIntAttribute(maxSize, "max_size"); err == nil {
fieldDesc.MaxArraySize = m
} else {
return err
}
}
// Parse optional
if optional := openTag.Attributes["optional"]; optional == "true" {
fieldDesc.Optional = true
}
// Parse add_to_struct
if addToStruct := openTag.Attributes["add_to_struct"]; addToStruct == "false" {
fieldDesc.AddToStruct = false
}
// Parse add_type
if addType := openTag.Attributes["add_type"]; addType != "" {
if at, exists := getDataType(addType); exists {
fieldDesc.AddType = at
}
}
packetDef.Fields[fullName] = fieldDesc
*fieldOrder = append(*fieldOrder, fullName)
}
return nil
}
// parseTemplateDefinition parses a template definition
func (p *Parser) parseTemplateDefinition(openTag *Tag, templateName string) error {
templateDef := NewPacketDef(16)
fieldOrder := make([]string, 0)
if openTag.SelfClosing {
templateDef.Orders[1] = fieldOrder
p.templates[templateName] = templateDef
return nil
}
p.pushTag("template")
for {
p.skipWhitespace()
if p.pos >= len(p.input) {
return fmt.Errorf("unexpected end of input in template")
}
if p.peek() != '<' {
return fmt.Errorf("expected '<' at line %d", p.line)
}
tag, err := p.parseTag()
if err != nil {
return err
}
if tag.IsClosing {
if tag.Name != "template" {
return fmt.Errorf("expected closing tag 'template', got '%s' at line %d", tag.Name, p.line)
}
if err := p.popTag("template"); err != nil {
return err
}
break
}
err = p.parseElement(tag, templateDef, &fieldOrder, "")
if err != nil {
return err
}
}
templateDef.Orders[1] = make([]string, len(fieldOrder))
copy(templateDef.Orders[1], fieldOrder)
p.templates[templateName] = templateDef
return nil
}
// parseTemplateUsage parses template usage
func (p *Parser) parseTemplateUsage(tag *Tag, packetDef *PacketDef, fieldOrder *[]string, prefix string) error {
// Template usage: <template use="foo">
if templateUse := tag.Attributes["use"]; templateUse != "" {
template, exists := p.templates[templateUse]
if !exists {
return fmt.Errorf("undefined template '%s' at line %d", templateUse, p.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 = prefix + fieldName
}
packetDef.Fields[fullName] = fieldDesc
*fieldOrder = append(*fieldOrder, fullName)
}
}
}
return nil
}
return fmt.Errorf("template missing 'use' attribute at line %d", p.line)
}
// parseSubstructReference parses a substruct reference like <substruct name="header" use="ItemDescription">
func (p *Parser) parseSubstructReference(tag *Tag, packetDef *PacketDef, fieldOrder *[]string, prefix string) error {
nameAttr := tag.Attributes["name"]
useAttr := tag.Attributes["use"]
if nameAttr == "" {
return fmt.Errorf("substruct reference missing 'name' attribute at line %d", p.line)
}
var fullName string
if prefix == "" {
fullName = nameAttr
} else {
fullName = prefix + nameAttr
}
fieldDesc := FieldDesc{
Type: common.TypeArray, // Substructs are treated as arrays
AddToStruct: true,
}
// Handle substruct reference with 'use' attribute
if useAttr != "" {
if subDef, exists := p.substructs[useAttr]; exists {
fieldDesc.SubDef = subDef
} else {
// If substruct doesn't exist yet, it might be defined later
// For now, we'll create a placeholder
}
}
packetDef.Fields[fullName] = fieldDesc
*fieldOrder = append(*fieldOrder, fullName)
return nil
}
// parseItemField parses an item field like <item name="item">
func (p *Parser) parseItemField(tag *Tag, packetDef *PacketDef, fieldOrder *[]string, prefix string) error {
nameAttr := tag.Attributes["name"]
if nameAttr == "" {
return fmt.Errorf("item field missing 'name' attribute at line %d", p.line)
}
var fullName string
if prefix == "" {
fullName = nameAttr
} else {
fullName = prefix + nameAttr
}
fieldDesc := FieldDesc{
Type: common.TypeItem,
Condition: tag.Attributes["if"],
AddToStruct: true,
AddType: common.TypeItem,
}
packetDef.Fields[fullName] = fieldDesc
*fieldOrder = append(*fieldOrder, fullName)
return nil
}
// Parse function that uses the new string parser
func Parse(content string) (map[string]*PacketDef, error) {
parser := NewParser(content)
return parser.Parse()
}
// 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,
}
// combineConditions combines two conditions with AND logic
func combineConditions(cond1, cond2 string) string {
if cond1 == "" {
return cond2
}
if cond2 == "" {
return cond1
}
return cond1 + "&" + cond2
}
// 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
}