package generate import "io" import "fmt" import "maps" import "math" import "slices" import "strings" import "encoding/hex" import "git.tebibyte.media/sashakoshka/hopp/tape" const imports = ` import "git.tebibyte.media/sashakoshka/hopp/tape" ` const preamble = ` // Code generated by the Holanet PDL compiler. DO NOT EDIT. // The source file is located at // Please edit that file instead, and re-compile it to this location. // HOPP, TAPE, METADAPT, PDL/0 (c) 2025 holanet.xyz ` const static = ` // Table is a KTV table with an undefined schema. type Table map[uint16] any // Message is any message that can be sent along this protocol. type Message interface { tape.Encodable tape.Decodable // Method returns the method code of the message. Method() uint16 } // canAssign determines if data from the given source tag can be assigned to // a Go type represented by destination. It is designed to receive destination // values from [generate.Generator.generateCanAssign]. The eventual Go type and // the destination tag must come from the same (or hash-equivalent) PDL type. func canAssign(destination, source tape.Tag) bool { if destination.Is(source) { return true } if (destination == tape.SBA || destination == tape.LBA) && (source == tape.SBA || source == tape.LBA) { return true } return false } ` // Generator converts protocols into Go code. type Generator struct { // Output is where the generated code will be sent. Output io.Writer // PackageName is the package name that will be used in the file. If // left empty, the default is "protocol". PackageName string nestingLevel int temporaryVar int protocol *Protocol decodeBranchRequestQueue []decodeBranchRequest } type decodeBranchRequest struct { hash [16]byte typ Type } func (this *Generator) Generate(protocol *Protocol) (n int, err error) { this.nestingLevel = 0 this.protocol = protocol defer func() { this.protocol = nil }() // preamble and static section packageName := "protocol" if this.PackageName != "" { packageName = this.PackageName } nn, err := this.iprintf("package %s\n", packageName) n += nn; if err != nil { return n, err } nn, err = this.print(preamble) n += nn; if err != nil { return n, err } nn, err = this.print(imports) n += nn; if err != nil { return n, err } nn, err = this.print(static) n += nn; if err != nil { return n, err } // type definitions for _, name := range slices.Sorted(maps.Keys(protocol.Types)) { nn, err := this.generateTypedef(name, protocol.Types[name]) n += nn; if err != nil { return n, err } } // messages for _, method := range slices.Sorted(maps.Keys(protocol.Messages)) { nn, err := this.generateMessage(method, protocol.Messages[method]) n += nn; if err != nil { return n, err } } // request queue for { hash, typ, ok := this.pullDecodeBranchRequest() if !ok { break } nn, err := this.generateDecodeBranch(hash, typ) n += nn; if err != nil { return n, err } } return n, nil } func (this *Generator) generateTypedef(name string, typ Type) (n int, err error) { // type definition nn, err := this.iprintf( "\n// %s represents the protocol data type %s.\n", name, name) n += nn; if err != nil { return n, err } nn, err = this.iprintf("type %s ", name) n += nn; if err != nil { return n, err } nn, err = this.generateType(typ) n += nn; if err != nil { return n, err } nn, err = this.println() n += nn; if err != nil { return n, err } // 'Tag' method // to be honest we probably don't need this method at all // nn, err = this.iprintf("\n// Tag returns the preferred TAPE tag.\n") // n += nn; if err != nil { return n, err } // nn, err = this.iprintf("func (this *%s) Tag() tape.Tag {\n", name) // n += nn; if err != nil { return n, err } // this.push() // nn, err = this.iprintf("return ") // n += nn; if err != nil { return n, err } // nn, err = this.generateTag(typ, "(*this)") // n += nn; if err != nil { return n, err } // nn, err = this.println() // n += nn; if err != nil { return n, err } // this.pop() // nn, err = this.iprintf("}\n") // n += nn; if err != nil { return n, err } // EncodeValue method nn, err = this.iprintf( "\n// EncodeValue encodes the value of this type without the " + "tag. The value is\n// encoded according to the parameters " + "specified by the tag, if possible.\n") n += nn; if err != nil { return n, err } nn, err = this.iprintf( "func (this *%s) EncodeValue(encoder *tape.Encoder, tag tape.Tag) (n int, err error) {\n", name) n += nn; if err != nil { return n, err } this.push() nn, err = this.iprintf("var nn int\n") n += nn; if err != nil { return n, err } nn, err = this.generateEncodeValue(typ, "(*this)", "tag") n += nn; if err != nil { return n, err } nn, err = this.iprintf("return n, nil\n") n += nn; if err != nil { return n, err } this.pop() nn, err = this.iprintf("}\n") n += nn; if err != nil { return n, err } // DecodeValue method nn, err = this.iprintf( "\n // DecodeValue decodes the value of this type without " + "the tag. The value is\n// decoded according to the " + "parameters specified by the tag, if possible.\n") n += nn; if err != nil { return n, err } nn, err = this.iprintf( "func (this *%s) DecodeValue(decoder *tape.Decoder, tag tape.Tag) (n int, err error) {\n", name) n += nn; if err != nil { return n, err } this.push() nn, err = this.iprintf("var nn int\n") n += nn; if err != nil { return n, err } nn, err = this.iprintf("if !(") n += nn; if err != nil { return n, err } nn, err = this.generateCanAssign(typ, "tag") n += nn; if err != nil { return n, err } nn, err = this.printf(") {\n") n += nn; if err != nil { return n, err } this.push() nn, err = this.iprintf("nn, err = tape.Skim(decoder, tag)\n") n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } nn, err = this.iprintf("return n, nil\n") n += nn; if err != nil { return n, err } this.pop() nn, err = this.iprintf("}\n") n += nn; if err != nil { return n, err } nn, err = this.generateDecodeValue(typ, "this", "tag") n += nn; if err != nil { return n, err } nn, err = this.iprintf("return n, nil\n") n += nn; if err != nil { return n, err } this.pop() nn, err = this.iprintf("}\n") n += nn; if err != nil { return n, err } return n, nil } // generateMessage generates the structure, as well as encoding decoding // functions for the given message. func (this *Generator) generateMessage(method uint16, message Message) (n int, err error) { nn, err := this.iprintf( "\n// %s represents the protocol message M%04X %s.\n", message.Name, method, message.Name) nn, err = this.iprintf("type %s ", this.resolveMessageName(message.Name)) n += nn; if err != nil { return n, err } nn, err = this.generateType(message.Type) n += nn; if err != nil { return n, err } nn, err = this.println() n += nn; if err != nil { return n, err } // Method method nn, err = this.iprintf("\n// Method returns the message's method number.\n") n += nn; if err != nil { return n, err } nn, err = this.iprintf( "func(this *%s) Method() uint16 { return 0x%04X }\n", this.resolveMessageName(message.Name), method) n += nn; if err != nil { return n, err } // Encode method nn, err = this.iprintf("\n// Encode encodes this message's tag and value.\n") n += nn; if err != nil { return n, err } nn, err = this.iprintf( "func(this *%s) Encode(encoder *tape.Encoder) (n int, err error) {\n", this.resolveMessageName(message.Name)) n += nn; if err != nil { return n, err } this.push() nn, err = this.iprintf("tag := ") n += nn; if err != nil { return n, err } nn, err = this.generateTag(message.Type, "(*this)") n += nn; if err != nil { return n, err } nn, err = this.println() n += nn; if err != nil { return n, err } nn, err = this.iprintf("nn, err := encoder.WriteTag(tag)\n") n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } nn, err = this.generateEncodeValue(message.Type, "(*this)", "tag") n += nn; if err != nil { return n, err } nn, err = this.iprintf("return n, nil\n") n += nn; if err != nil { return n, err } this.pop() nn, err = this.iprintf("}\n") n += nn; if err != nil { return n, err } // Decode method nn, err = this.iprintf("\n// Decode decodes this message's tag and value.\n") n += nn; if err != nil { return n, err } nn, err = this.iprintf( "func(this *%s) Decode(decoder *tape.Decoder) (n int, err error) {\n", this.resolveMessageName(message.Name)) n += nn; if err != nil { return n, err } this.push() nn, err = this.iprintf("tag, nn, err := decoder.ReadTag()\n") n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } nn, err = this.iprintf("if !(") n += nn; if err != nil { return n, err } nn, err = this.generateCanAssign(message.Type, "tag") n += nn; if err != nil { return n, err } nn, err = this.printf(") {\n") n += nn; if err != nil { return n, err } this.push() nn, err = this.iprintf("nn, err = tape.Skim(decoder, tag)\n") n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } nn, err = this.iprintf("return n, nil\n") n += nn; if err != nil { return n, err } this.pop() nn, err = this.iprintf("}\n") n += nn; if err != nil { return n, err } nn, err = this.generateDecodeValue(message.Type, "this", "tag") n += nn; if err != nil { return n, err } nn, err = this.iprintf("return n, nil\n") n += nn; if err != nil { return n, err } this.pop() nn, err = this.iprintf("}\n") n += nn; if err != nil { return n, err } return n, nil } // generateEncodeValue generates code to encode a value of a specified type. It // pulls from the variable (or parenthetical statement) specified by // valueSource, and the value will be encoded according to the tag stored in // the variable (or parenthetical statement) specified by tagSource. // the code generated is a BLOCK and expects these variables to be defined: // // - encoder *tape.Encoder // - n int // - err error // - nn int func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource string) (n int, err error) { switch typ := typ.(type) { case TypeInt: // SI: (none) // LI: if typ.Bits <= 5 { // SI stores the value in the tag, so we write nothing here break } prefix := "WriteUint" if typ.Signed { prefix = "WriteInt" } nn, err := this.iprintf("nn, err = encoder.%s%d(%s)\n", prefix, typ.Bits, valueSource) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } case TypeFloat: // FP: nn, err := this.iprintf("nn, err = encoder.WriteFloat%d(%s)\n", typ.Bits, valueSource) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } case TypeString: // see TypeBuffer nn, err := this.generateEncodeValue(TypeBuffer { }, valueSource, tagSource) n += nn; if err != nil { return n, err } case TypeBuffer: // SBA: * // LBA: * nn, err := this.iprintf("if %s.Is(tape.LBA) {\n", tagSource) n += nn; if err != nil { return n, err } this.push() nn, err = this.iprintf( "nn, err = encoder.WriteUintN(uint64(len(%s)), %s.CN())\n", valueSource, tagSource) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } this.pop() nn, err = this.iprintf("}\n") n += nn; if err != nil { return n, err } nn, err = this.iprintf("nn, err = encoder.Write([]byte(%s))\n", valueSource) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } case TypeArray: // OTA: * nn, err := this.iprintf( "nn, err = encoder.WriteUintN(uint64(len(%s)), %s.CN())\n", valueSource, tagSource) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } nn, err = this.iprintf("{\n") n += nn; if err != nil { return n, err } this.push() nn, err = this.iprintf("itemTag := ") n += nn; if err != nil { return n, err } nn, err = this.generateTN(typ.Element) n += nn; if err != nil { return n, err } nn, err = this.println() n += nn; if err != nil { return n, err } // TODO: we don't have to do this for loop for some // types such as integers because the CN will be the // same nn, err = this.iprintf("for _, item := range %s {\n", valueSource) n += nn; if err != nil { return n, err } this.push() nn, err = this.iprintf("_ = item\n") n += nn; if err != nil { return n, err } nn, err = this.iprintf("tag := ") n += nn; if err != nil { return n, err } nn, err = this.generateTag(typ.Element, "item") n += nn; if err != nil { return n, err } nn, err = this.println() n += nn; if err != nil { return n, err } nn, err = this.iprintf("if tag.Is(tape.SBA) { continue }\n") n += nn; if err != nil { return n, err } nn, err = this.iprintf("if tag.CN() > itemTag.CN() { itemTag = tag }\n") n += nn; if err != nil { return n, err } this.pop() nn, err = this.iprintf("}\n") n += nn; if err != nil { return n, err } nn, err = this.iprintf("if itemTag.Is(tape.SBA) { itemTag += 1 << 5 }\n") n += nn; if err != nil { return n, err } nn, err = this.iprintf("nn, err = encoder.WriteTag(itemTag)\n") n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() nn, err = this.iprintf("for _, item := range %s {\n", valueSource) n += nn; if err != nil { return n, err } this.push() nn, err = this.generateEncodeValue(typ.Element, "item", "itemTag") n += nn; if err != nil { return n, err } this.pop() nn, err = this.iprintf("}\n") n += nn; if err != nil { return n, err } this.pop() nn, err = this.iprintf("}\n") n += nn; if err != nil { return n, err } case TypeTable: // KTV: ( )* nn, err := this.iprintf( "nn, err = tape.EncodeAny(encoder, %s, %s)\n", valueSource, tagSource) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } case TypeTableDefined: // KTV: ( )* nn, err := this.iprintf( "nn, err = encoder.WriteUintN(%d, %s.CN())\n", len(typ.Fields), tagSource) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } nn, err = this.iprintf("{\n") n += nn; if err != nil { return n, err } this.push() nn, err = this.iprintf("var tag tape.Tag\n") n += nn; if err != nil { return n, err } for key, field := range typ.Fields { nn, err = this.iprintf("nn, err = encoder.WriteUint16(0x%04X)\n", key) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } nn, err = this.iprintf("tag = ") n += nn; if err != nil { return n, err } fieldSource := fmt.Sprintf("%s.%s", valueSource, field.Name) nn, err = this.generateTag(field.Type, fieldSource) n += nn; if err != nil { return n, err } nn, err = this.println() n += nn; if err != nil { return n, err } nn, err = this.iprintf("nn, err = encoder.WriteUint8(uint8(tag))\n") n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } nn, err = this.generateEncodeValue(field.Type, fieldSource, "tag") n += nn; if err != nil { return n, err } } this.pop() nn, err = this.iprintf("}\n") n += nn; if err != nil { return n, err } case TypeNamed: // WHATEVER: [WHATEVER] nn, err := this.iprintf("nn, err = %s.EncodeValue(encoder, %s)\n", valueSource, tagSource) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } default: panic(fmt.Errorf("unknown type: %T", typ)) } return n, nil } // generateDencodeValue generates code to decode a value of a specified type. It // overwrites memory pointed to by the variable (or parenthetical statement) // specified by valueSource, and the value will be encoded according to the tag // stored in the variable (or parenthetical statement) specified by tagSource. // the code generated is a BLOCK and expects these variables to be defined: // // - decoder *tape.Decoder // - n int // - err error // - nn int func (this *Generator) generateDecodeValue(typ Type, valueSource, tagSource string) (n int, err error) { switch typ := typ.(type) { case TypeInt: // SI: (none) // LI: if typ.Bits <= 5 { // SI stores the value in the tag nn, err := this.iprintf("*%s = uint8(%s.CN())\n", valueSource, tagSource) n += nn; if err != nil { return n, err } break } prefix := "ReadUint" if typ.Signed { prefix = "ReadInt" } nn, err := this.iprintf("*%s, nn, err = decoder.%s%d()\n", valueSource, prefix, typ.Bits) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } case TypeFloat: // FP: nn, err := this.iprintf("*%s, nn, err = decoder.ReadFloat%d()\n", valueSource, typ.Bits) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } case TypeString, TypeBuffer: // SBA: * // LBA: * lengthVar := this.newTemporaryVar("length") nn, err := this.iprintf("var %s uint64\n", lengthVar) n += nn; if err != nil { return n, err } nn, err = this.iprintf("if %s.Is(tape.LBA) {\n", tagSource) n += nn; if err != nil { return n, err } this.push() nn, err = this.iprintf( "%s, nn, err = decoder.ReadUintN(int(%s.CN()))\n", lengthVar, tagSource) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } this.pop() nn, err = this.iprintf("} else {\n") n += nn; if err != nil { return n, err } this.push() nn, err = this.iprintf( "%s = uint64(%s.CN())\n", lengthVar, tagSource) n += nn; if err != nil { return n, err } this.pop() nn, err = this.iprintf("}\n") n += nn; if err != nil { return n, err } nn, err = this.iprintf("buffer := make([]byte, int(%s))\n", lengthVar) n += nn; if err != nil { return n, err } nn, err = this.iprintf("nn, err = decoder.Read(buffer)\n") n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } if _, ok := typ.(TypeString); ok { nn, err = this.iprintf("*%s = string(buffer)\n", valueSource) n += nn; if err != nil { return n, err } } else { nn, err = this.iprintf("*%s = buffer\n", valueSource) n += nn; if err != nil { return n, err } } case TypeArray: // OTA: * nn, err := this.generateDecodeBranchCall(typ, valueSource, tagSource) n += nn; if err != nil { return n, err } case TypeTable: // KTV: ( )* nn, err := this.iprintf( "nn, err = tape.DecodeAny(decoder, %s, %s)\n", valueSource, tagSource) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } case TypeTableDefined: // KTV: ( )* nn, err := this.generateDecodeBranchCall(typ, valueSource, tagSource) n += nn; if err != nil { return n, err } case TypeNamed: // WHATEVER: [WHATEVER] nn, err := this.iprintf("nn, err = %s.DecodeValue(decoder, %s)\n", valueSource, tagSource) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } default: panic(fmt.Errorf("unknown type: %T", typ)) } return n, nil } // generateDecodeBranchCall generates code to call an aggregate decoder function, // for a specified type. The definition of the function is deferred so no // duplicates are created. The function overwrites memory pointed to by the // variable (or parenthetical statement) specified by valueSource, and the value // will be encoded according to the tag stored in the variable (or parenthetical // statement) specified by tagSource. the code generated is a BLOCK and expects // these variables to be defined: // // - decoder *tape.Decoder // - n int // - err error // - nn int func (this *Generator) generateDecodeBranchCall(typ Type, valueSource, tagSource string) (n int, err error) { hash := HashType(typ) nn, err := this.iprintf("nn, err = %s(%s, decoder, %s)\n", this.decodeBranchName(hash), valueSource, tagSource) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } this.pushDecodeBranchRequest(hash, typ) return n, nil } // generateDecodeBranch generates an aggregate decoder function definition for a // specified type. It assumes that hash == HashType(typ). func (this *Generator) generateDecodeBranch(hash [16]byte, typ Type) (n int, err error) { nn, err := this.iprintf("\nfunc %s[T ~", this.decodeBranchName(hash)) n += nn; if err != nil { return n, err } nn, err = this.generateType(typ) n += nn; if err != nil { return n, err } nn, err = this.printf("](this *T, decoder *tape.Decoder, tag tape.Tag) (n int, err error) {\n") n += nn; if err != nil { return n, err } this.push() nn, err = this.iprintf("var nn int\n") n += nn; if err != nil { return n, err } switch typ := typ.(type) { case TypeArray: // OTA: * // read header lengthVar := this.newTemporaryVar("length") nn, err := this.iprintf("var %s uint64\n", lengthVar) n += nn; if err != nil { return n, err } nn, err = this.iprintf("%s, nn, err = decoder.ReadUintN(int(tag.CN()))\n", lengthVar) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } elementTagVar := this.newTemporaryVar("elementTag") nn, err = this.iprintf("var %s uint64\n", lengthVar) n += nn; if err != nil { return n, err } nn, err = this.iprintf("%s, nn, err = decoder.ReadTag()\n", elementTagVar) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } // abort macro abort := func() (n int, err error) { // skim entire array nn, err = this.iprintf("for _ = range %s {\n", lengthVar) n += nn; if err != nil { return n, err } this.push() nn, err = this.iprintf("nn, err = tape.Skim(decoder, %s)\n", elementTagVar) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } this.pop() nn, err = this.iprintf("}\n") n += nn; if err != nil { return n, err } nn, err = this.iprintf("return n, nil\n") n += nn; if err != nil { return n, err } return n, nil } // validate header // TODO: here, validate that length is less than the // max, whatever that is configured to be. the reason we // want to read it here is that we would have to skip // the tag anyway so why not. nn, err = this.iprintf("if !(") n += nn; if err != nil { return n, err } nn, err = this.generateCanAssign(typ.Element, elementTagVar) n += nn; if err != nil { return n, err } nn, err = this.printf(") {\n") n += nn; if err != nil { return n, err } this.push() nn, err = abort() n += nn; if err != nil { return n, err } this.pop() nn, err = this.iprintf("}\n") n += nn; if err != nil { return n, err } // decode payloads nn, err = this.iprintf("*this = make(") n += nn; if err != nil { return n, err } nn, err = this.generateType(typ) n += nn; if err != nil { return n, err } nn, err = this.printf(", %s)\n", lengthVar) n += nn; if err != nil { return n, err } nn, err = this.iprintf("for index := range int(%s) {\n", lengthVar) n += nn; if err != nil { return n, err } this.push() nn, err = this.generateDecodeValue(typ.Element, "(*this)[index]", elementTagVar) n += nn; if err != nil { return n, err } this.pop() nn, err = this.iprintf("}\n") n += nn; if err != nil { return n, err } case TypeTableDefined: // KTV: ( )* // read header lengthVar := this.newTemporaryVar("length") nn, err := this.iprintf("var %s uint64\n", lengthVar) n += nn; if err != nil { return n, err } nn, err = this.iprintf("%s, nn, err = decoder.ReadUintN(int(tag.CN()))\n", lengthVar) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } indexVar := this.newTemporaryVar("index") nn, err = this.iprintf("%s := 0\n", indexVar) n += nn; if err != nil { return n, err } // validate header // TODO: here, validate that length is less than the // max, whatever that is configured to be. if not, stop // ALL decoding. skimming huge big ass data could cause // problems // read fields nn, err = this.iprintf("for %s = range int(%s) {\n", indexVar, lengthVar) n += nn; if err != nil { return n, err } this.push() // read field header fieldKeyVar := this.newTemporaryVar("fieldKey") nn, err = this.iprintf("var %s uint16\n", fieldKeyVar) n += nn; if err != nil { return n, err } nn, err = this.iprintf("%s, nn, err = decoder.ReadUint16()\n", fieldKeyVar) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } fieldTagVar := this.newTemporaryVar("fieldTag") nn, err = this.iprintf("var %s tape.Tag\n", fieldTagVar) n += nn; if err != nil { return n, err } nn, err = this.iprintf("%s, nn, err = decoder.ReadTag()\n", fieldTagVar) n += nn; if err != nil { return n, err } nn, err = this.generateErrorCheck() n += nn; if err != nil { return n, err } // abort field macro abortField := func() (n int, err error) { nn, err = this.iprintf("tape.Skim(decoder, %s)\n", fieldTagVar) n += nn; if err != nil { return n, err } nn, err = this.iprintf("continue\n") n += nn; if err != nil { return n, err } return n, nil } // switch on tag nn, err = this.iprintf("switch %s {\n", fieldKeyVar) n += nn; if err != nil { return n, err } for _, key := range slices.Sorted(maps.Keys(typ.Fields)) { field := typ.Fields[key] nn, err = this.iprintf("case 0x%04X:\n", key) n += nn; if err != nil { return n, err } this.push() // validate field header nn, err = this.iprintf("if !(") n += nn; if err != nil { return n, err } nn, err = this.generateCanAssign(field.Type, fieldTagVar) n += nn; if err != nil { return n, err } nn, err = this.printf(") {\n") n += nn; if err != nil { return n, err } this.push() nn, err = abortField() n += nn; if err != nil { return n, err } this.pop() nn, err = this.iprintf("}\n") n += nn; if err != nil { return n, err } // decode payload nn, err = this.generateDecodeValue(field.Type, fmt.Sprintf("&(this.%s)", field.Name), fieldTagVar) n += nn; if err != nil { return n, err } this.pop() } nn, err = this.iprintf("default:\n") n += nn; if err != nil { return n, err } this.push() abortField() this.pop() nn, err = this.iprintf("}\n") n += nn; if err != nil { return n, err } this.pop() nn, err = this.iprintf("}\n") n += nn; if err != nil { return n, err } // TODO once options are implemented, have a set of // bools for each non-optional field, and check here // that they are all true. a counter will not work // because if someone specifies a non-optional field // twice, they can neglect to specify another // non-optional field and we won't even know because the // count will still be even. we shouldn't use a map // either because its an allocation and its way more // memory than just, like 5 bools (on the stack no less) default: return n, fmt.Errorf("unexpected type: %T", typ) } this.pop() nn, err = this.iprintf("}\n") n += nn; if err != nil { return n, err } return n, nil } func (this *Generator) decodeBranchName(hash [16]byte) string { return fmt.Sprintf("decodeBranch_%s", hex.EncodeToString(hash[:])) } // pushDecodeBranchRequest pushes a new branch decode function request to the // back of the queue, if it is not already in the queue. func (this *Generator) pushDecodeBranchRequest(hash [16]byte, typ Type) { for _, item := range this.decodeBranchRequestQueue { if item.hash == hash { return } } this.decodeBranchRequestQueue = append(this.decodeBranchRequestQueue, decodeBranchRequest { hash: hash, typ: typ, }) } // pullDecodeBranchRequest pulls a branch decode function request from the front // of the queue. func (this *Generator) pullDecodeBranchRequest() (hash [16]byte, typ Type, ok bool) { if len(this.decodeBranchRequestQueue) < 1 { return [16]byte { }, nil, false } request := this.decodeBranchRequestQueue[0] this.decodeBranchRequestQueue = this.decodeBranchRequestQueue[1:] return request.hash, request.typ, true } func (this *Generator) generateErrorCheck() (n int, err error) { return this.iprintf("n += nn; if err != nil { return n, err }\n") } // generateTag generates the preferred TN and CN for the given type and value. // The generated code is INLINE. func (this *Generator) generateTag(typ Type, source string) (n int, err error) { switch typ := typ.(type) { case TypeInt: if typ.Bits <= 5 { nn, err := this.printf("tape.SI.WithCN(int(%s))", source) n += nn; if err != nil { return n, err } } else { nn, err := this.printf("tape.LI.WithCN(%d)", bitsToCN(typ.Bits)) n += nn; if err != nil { return n, err } } case TypeFloat: nn, err := this.printf("tape.FP.WithCN(%d)", bitsToCN(typ.Bits)) n += nn; if err != nil { return n, err } case TypeString: nn, err := this.printf("tape.StringTag(%s)", source) n += nn; if err != nil { return n, err } case TypeBuffer: nn, err := this.printf("tape.BufferTag(%s)", source) n += nn; if err != nil { return n, err } case TypeArray: nn, err := this.printf("tape.OTA.WithCN(tape.IntBytes(uint64(len(%s))))", source) n += nn; if err != nil { return n, err } case TypeTable: nn, err := this.printf("tape.KTV.WithCN(tape.IntBytes(uint64(len(%s))))", source) n += nn; if err != nil { return n, err } case TypeTableDefined: nn, err := this.printf("tape.KTV.WithCN(%d)", tape.IntBytes(uint64(len(typ.Fields)))) n += nn; if err != nil { return n, err } case TypeNamed: resolved, err := this.resolveTypeName(typ.Name) if err != nil { return n, err } nn, err := this.generateTag(resolved, source) n += nn; if err != nil { return n, err } default: panic(fmt.Errorf("unknown type: %T", typ)) } return n, nil } // generateTN generates the appropriate TN for the given type. The generated // code is INLINE. The generated tag will have a CN as zero. For types that // change TN based on their length, the TN capable of supporting more // information is chosen. func (this *Generator) generateTN(typ Type) (n int, err error) { switch typ := typ.(type) { case TypeInt: if typ.Bits <= 5 { nn, err := this.printf("tape.SI") n += nn; if err != nil { return n, err } } else { nn, err := this.printf("tape.LI") n += nn; if err != nil { return n, err } } case TypeFloat: nn, err := this.printf("tape.FP",) n += nn; if err != nil { return n, err } case TypeString: nn, err := this.generateTN(TypeBuffer { }) n += nn; if err != nil { return n, err } case TypeBuffer: nn, err := this.printf("tape.LBA") n += nn; if err != nil { return n, err } case TypeArray: nn, err := this.printf("tape.OTA") n += nn; if err != nil { return n, err } case TypeTable: nn, err := this.printf("tape.KTV") n += nn; if err != nil { return n, err } case TypeTableDefined: nn, err := this.printf("tape.KTV") n += nn; if err != nil { return n, err } case TypeNamed: resolved, err := this.resolveTypeName(typ.Name) if err != nil { return n, err } nn, err := this.generateTN(resolved) n += nn; if err != nil { return n, err } } return n, nil } func (this *Generator) generateType(typ Type) (n int, err error) { switch typ := typ.(type) { case TypeInt: if err := this.validateIntBitSize(typ.Bits); err != nil { return n, err } if typ.Bits <= 5 { nn, err := this.printf("uint8") n += nn; if err != nil { return n, err } break } if typ.Signed { nn, err := this.printf("int%d", typ.Bits) n += nn; if err != nil { return n, err } } else { nn, err := this.printf("uint%d", typ.Bits) n += nn; if err != nil { return n, err } } case TypeFloat: switch typ.Bits { case 16: nn, err := this.print("float32") n += nn; if err != nil { return n, err } case 32, 64: nn, err := this.printf("float%d", typ.Bits) n += nn; if err != nil { return n, err } default: return n, fmt.Errorf("floats of size %d are unsupported on this platform", typ.Bits) } case TypeString: nn, err := this.print("string") n += nn; if err != nil { return n, err } case TypeBuffer: nn, err := this.print("[]byte") n += nn; if err != nil { return n, err } case TypeArray: nn, err := this.print("[]") n += nn; if err != nil { return n, err } nn, err = this.generateType(typ.Element) n += nn; if err != nil { return n, err } case TypeTable: nn, err := this.print("Table") n += nn; if err != nil { return n, err } case TypeTableDefined: nn, err := this.generateTypeTableDefined(typ) n += nn; if err != nil { return n, err } case TypeNamed: nn, err := this.print(typ.Name) n += nn; if err != nil { return n, err } } return n, nil } func (this *Generator) generateTypeTableDefined(typ TypeTableDefined) (n int, err error) { nn, err := this.print("struct {\n") n += nn; if err != nil { return n, err } this.push() for _, key := range slices.Sorted(maps.Keys(typ.Fields)) { field := typ.Fields[key] nn, err := this.iprintf("%s ", field.Name) n += nn; if err != nil { return n, err } nn, err = this.generateType(field.Type) n += nn; if err != nil { return n, err } nn, err = this.print("\n") n += nn; if err != nil { return n, err } } this.pop() nn, err = this.iprint("}") n += nn; if err != nil { return n, err } return n, nil } // generateCanAssign generates an expression which checks if the tag specified // by tagSource can be assigned to a Go destination generated from typ. The // generated code is INLINE. func (this *Generator) generateCanAssign(typ Type, tagSource string) (n int, err error) { nn, err := this.printf("canAssign(") n += nn; if err != nil { return n, err } nn, err = this.generateTN(typ) n += nn; if err != nil { return n, err } nn, err = this.printf(", %s)", tagSource) n += nn; if err != nil { return n, err } return n, nil } func (this *Generator) validateIntBitSize(size int) error { switch size { case 5, 8, 16, 32, 64: return nil default: return fmt.Errorf("integers of size %d are unsupported on this platform", size) } } func (this *Generator) validateFloatBitSize(size int) error { switch size { case 16, 32, 64: return nil default: return fmt.Errorf("floats of size %d are unsupported on this platform", size) } } func (this *Generator) push() { this.nestingLevel ++ } func (this *Generator) pop() { if this.nestingLevel < 1 { panic("cannot pop when nesting level is less than 1") } this.nestingLevel -- } func (this *Generator) indent() string { return strings.Repeat("\t", this.nestingLevel) } func (this *Generator) print(args ...any) (n int, err error) { return fmt.Fprint(this.Output, args...) } func (this *Generator) println(args ...any) (n int, err error) { return fmt.Fprintln(this.Output, args...) } func (this *Generator) printf(format string, args ...any) (n int, err error) { return fmt.Fprintf(this.Output, format, args...) } func (this *Generator) iprint(args ...any) (n int, err error) { return fmt.Fprint(this.Output, this.indent() + fmt.Sprint(args...)) } func (this *Generator) iprintln(args ...any) (n int, err error) { return fmt.Fprintln(this.Output, this.indent() + fmt.Sprint(args...)) } func (this *Generator) iprintf(format string, args ...any) (n int, err error) { return fmt.Fprintf(this.Output, this.indent() + format, args...) } func (this *Generator) resolveMessageName(message string) string { return "Message" + message } func (this *Generator) resolveTypeName(name string) (Type, error) { if typ, ok := this.protocol.Types[name]; ok { if typ, ok := typ.(TypeNamed); ok { return this.resolveTypeName(typ.Name) } return typ, nil } return nil, fmt.Errorf("no type exists called %s", name) } func (this *Generator) newTemporaryVar(base string) string { this.temporaryVar += 1 return fmt.Sprintf("%s_%d", base, this.temporaryVar) } func bitsToBytes(bits int) int { return int(math.Ceil(float64(bits) / 8.0)) } func bitsToCN(bits int) int { return bitsToBytes(bits) - 1 }