814 lines
19 KiB
Go
814 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{
|
|
"u8": common.TypeUint8,
|
|
"u16": common.TypeUint16,
|
|
"u32": common.TypeUint32,
|
|
"u64": common.TypeUint64,
|
|
"i8": common.TypeInt8,
|
|
"i16": common.TypeInt16,
|
|
"i32": common.TypeInt32,
|
|
"i64": common.TypeInt64,
|
|
"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.TypeUint8, true
|
|
case "u16":
|
|
return common.TypeUint16, true
|
|
case "u32":
|
|
return common.TypeUint32, true
|
|
case "u64":
|
|
return common.TypeUint64, true
|
|
}
|
|
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 "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()
|
|
}
|