From 8ece6436b8e7f908e20f08aa5bfee934f2fe2e84 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sat, 7 Jun 2025 22:38:02 -0400 Subject: [PATCH] generate: Add PDL parser --- generate/parse.go | 206 +++++++++++++++++++++++++++++++++++++++++ generate/parse_test.go | 69 ++++++++++++++ 2 files changed, 275 insertions(+) create mode 100644 generate/parse.go create mode 100644 generate/parse_test.go diff --git a/generate/parse.go b/generate/parse.go new file mode 100644 index 0000000..9d945e5 --- /dev/null +++ b/generate/parse.go @@ -0,0 +1,206 @@ +package generate + +import "io" +import "strconv" +import "git.tebibyte.media/sashakoshka/goparse" + +func Parse(lx parse.Lexer) (*Protocol, error) { + protocol := defaultProtocol() + par := parser { + Parser: parse.Parser { + Lexer: lx, + TokenNames: tokenNames, + }, + protocol: &protocol, + } + err := par.parse() + if err != nil { return nil, err } + return par.protocol, nil +} + +func defaultProtocol() Protocol { + return Protocol { + Messages: make(map[uint16] Message), + Types: map[string] Type { + "U8": TypeInt { Bits: 8 }, + "U16": TypeInt { Bits: 16 }, + "U32": TypeInt { Bits: 32 }, + "U64": TypeInt { Bits: 64 }, + "U128": TypeInt { Bits: 128 }, + "U256": TypeInt { Bits: 256 }, + "I8": TypeInt { Bits: 8, Signed: true }, + "I16": TypeInt { Bits: 16, Signed: true }, + "I32": TypeInt { Bits: 32, Signed: true }, + "I64": TypeInt { Bits: 64, Signed: true }, + "I128": TypeInt { Bits: 128, Signed: true }, + "I256": TypeInt { Bits: 256, Signed: true }, + "F16": TypeFloat { Bits: 16 }, + "F32": TypeFloat { Bits: 32 }, + "F64": TypeFloat { Bits: 64 }, + "F128": TypeFloat { Bits: 128 }, + "F256": TypeFloat { Bits: 256 }, + "String": TypeString { }, + "Buffer": TypeBuffer { }, + "Table": TypeTable { }, + }, + } +} + +func ParseReader(reader io.Reader) (*Protocol, error) { + lx, err := Lex("test.pdl", reader) + if err != nil { return nil, err } + return Parse(lx) +} + +type parser struct { + parse.Parser + protocol *Protocol +} + +func (this *parser) parse() error { + err := this.Next() + if err != nil { return err } + for this.Token.Kind != parse.EOF { + err = this.parseTopLevel() + if err != nil { return err } + } + return nil +} + +func (this *parser) parseTopLevel() error { + err := this.ExpectDesc("message or typedef", TokenMethod, TokenIdent) + if err != nil { return err } + if this.EOF() { return nil } + + switch this.Kind() { + case TokenMethod: return this.parseMessage() + case TokenIdent: return this.parseTypedef() + } + panic("bug") +} + +func (this *parser) parseMessage() error { + err := this.Expect(TokenMethod) + if err != nil { return err } + method, err := this.parseHexNumber(this.Value(), 0xFFFF) + if err != nil { return err } + err = this.ExpectNext(TokenIdent) + if err != nil { return err } + name := this.Value() + err = this.Next() + if err != nil { return err } + typ, err := this.parseType() + if err != nil { return err } + this.protocol.Messages[uint16(method)] = Message { + Name: name, + Type: typ, + } + return nil +} + +func (this *parser) parseTypedef() error { + err := this.Expect(TokenIdent) + if err != nil { return err } + name := this.Value() + err = this.Next() + if err != nil { return err } + typ, err := this.parseType() + if err != nil { return err } + this.protocol.Types[name] = typ + return nil +} + +func (this *parser) parseType() (Type, error) { + err := this.ExpectDesc("type", TokenIdent, TokenLBracket, TokenLBrace) + if err != nil { return nil, err } + + switch this.Kind() { + case TokenIdent: + return this.parseTypeNamed() + case TokenLBracket: + return this.parseTypeArray() + case TokenLBrace: + return this.parseTypeTable() + } + panic("bug") +} + +func (this *parser) parseTypeNamed() (TypeNamed, error) { + err := this.Expect(TokenIdent) + if err != nil { return TypeNamed { }, err } + name := this.Value() + err = this.Next() + if err != nil { return TypeNamed { }, err } + return TypeNamed { Name: name }, nil +} + +func (this *parser) parseTypeArray() (TypeArray, error) { + err := this.Expect(TokenLBracket) + if err != nil { return TypeArray { }, err } + err = this.ExpectNext(TokenRBracket) + if err != nil { return TypeArray { }, err } + err = this.Next() + if err != nil { return TypeArray { }, err } + typ, err := this.parseType() + if err != nil { return TypeArray { }, err } + return TypeArray { Element: typ }, nil +} + +func (this *parser) parseTypeTable() (TypeTableDefined, error) { + err := this.Expect(TokenLBrace) + if err != nil { return TypeTableDefined { }, err } + err = this.Next() + if err != nil { return TypeTableDefined { }, err } + typ := TypeTableDefined { + Fields: make(map[uint16] Field), + } + for { + err := this.ExpectDesc("table field", TokenKey, TokenRBrace) + if err != nil { return TypeTableDefined { }, err } + if this.Is(TokenRBrace) { + break + } + key, field, err := this.parseField() + if err != nil { return TypeTableDefined { }, err } + typ.Fields[key] = field + err = this.Expect(TokenComma, TokenRBrace) + if err != nil { return TypeTableDefined { }, err } + if this.Is(TokenRBrace) { + break + } + err = this.Next() + if err != nil { return TypeTableDefined { }, err } + } + err = this.Next() + if err != nil { return TypeTableDefined { }, err } + return typ, nil +} + +func (this *parser) parseField() (uint16, Field, error) { + err := this.Expect(TokenKey) + if err != nil { return 0, Field { }, err } + key, err := this.parseHexNumber(this.Value(), 0xFFFF) + if err != nil { return 0, Field { }, err } + err = this.ExpectNext(TokenIdent) + if err != nil { return 0, Field { }, err } + name := this.Value() + err = this.Next() + if err != nil { return 0, Field { }, err } + typ, err := this.parseType() + if err != nil { return 0, Field { }, err } + return uint16(key), Field { + Name: name, + Type: typ, + }, nil +} + +func (this *parser) parseHexNumber(input string, maxValue int64) (int64, error) { + number, err := strconv.ParseInt(input, 16, 64) + if err != nil { + return 0, parse.Errorf(this.Pos(), "%v", err) + } + if maxValue > 0 && number > maxValue { + return 0, parse.Errorf(this.Pos(), "value too large (max %X)", maxValue) + } + return number, nil +} diff --git a/generate/parse_test.go b/generate/parse_test.go new file mode 100644 index 0000000..39faabe --- /dev/null +++ b/generate/parse_test.go @@ -0,0 +1,69 @@ +package generate + +import "fmt" +import "strings" +import "testing" +// import "reflect" +import "git.tebibyte.media/sashakoshka/goparse" + +func TestParse(test *testing.T) { + correct := defaultProtocol() + correct.Messages[0x0000] = Message { + Name: "Connect", + Type: TypeTableDefined { + Fields: map[uint16] Field { + 0x0000: Field { Name: "Name", Type: TypeNamed { Name: "String" } }, + 0x0001: Field { Name: "Password", Type: TypeNamed { Name: "String" } }, + }, + }, + } + correct.Messages[0x0001] = Message { + Name: "UserList", + Type: TypeTableDefined { + Fields: map[uint16] Field { + 0x0000: Field { Name: "Users", Type: TypeArray { Element: TypeNamed { Name: "User" } } }, + }, + }, + } + correct.Types["User"] = TypeTableDefined { + Fields: map[uint16] Field { + 0x0000: Field { Name: "Name", Type: TypeNamed { Name: "String" } }, + 0x0001: Field { Name: "Bio", Type: TypeNamed { Name: "String" } }, + 0x0002: Field { Name: "Followers", Type: TypeNamed { Name: "U32" } }, + }, + } + test.Log("CORRECT:", &correct) + + got, err := ParseReader(strings.NewReader(` + M0000 Connect { + 0000 Name String, + 0001 Password String, + } + + M0001 UserList { + 0000 Users []User, + } + + User { + 0000 Name String, + 0001 Bio String, + 0002 Followers U32, + } + `)) + if err != nil { test.Fatal(parse.Format(err)) } + test.Log("GOT: ", got) + + correctStr := fmt.Sprint(&correct) + gotStr := fmt.Sprint(got) + + if correctStr != gotStr { + test.Error("not equal") + for index := range min(len(correctStr), len(gotStr)) { + if correctStr[index] == gotStr[index] { continue } + test.Log("C:", correctStr[max(0, index - 8):min(len(correctStr), index + 8)]) + test.Log("G:", gotStr[max(0, index - 8):min(len(gotStr), index + 8)]) + break + } + test.FailNow() + } +}