Big nasty commit to add code generation for encoding
This commit is contained in:
parent
a1f297e5b5
commit
ce503c4689
@ -81,7 +81,7 @@ func (this *Decoder) ReadUint64() (value uint64, n int, err error) {
|
|||||||
n, err = this.ReadFull(buffer[:])
|
n, err = this.ReadFull(buffer[:])
|
||||||
return uint64(buffer[0]) << 56 |
|
return uint64(buffer[0]) << 56 |
|
||||||
uint64(buffer[1]) << 48 |
|
uint64(buffer[1]) << 48 |
|
||||||
uint64(buffer[2]) << 48 |
|
uint64(buffer[2]) << 40 |
|
||||||
uint64(buffer[3]) << 32 |
|
uint64(buffer[3]) << 32 |
|
||||||
uint64(buffer[4]) << 24 |
|
uint64(buffer[4]) << 24 |
|
||||||
uint64(buffer[5]) << 16 |
|
uint64(buffer[5]) << 16 |
|
||||||
@ -89,14 +89,33 @@ func (this *Decoder) ReadUint64() (value uint64, n int, err error) {
|
|||||||
uint64(buffer[7]), n, err
|
uint64(buffer[7]), n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadIntN decodes an N-byte signed integer from the input reader.
|
||||||
|
func (this *Decoder) ReadIntN(bytes int) (value int64, n int, err error) {
|
||||||
|
uncasted, n, err := this.ReadUintN(bytes)
|
||||||
|
return int64(uncasted), n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUintN decodes an N-byte unsigned integer from the input reader.
|
||||||
|
func (this *Decoder) ReadUintN(bytes int) (value uint64, n int, err error) {
|
||||||
|
// TODO: don't make multiple read calls (without allocating)
|
||||||
|
buffer := [1]byte { }
|
||||||
|
for bytesLeft := bytes; bytesLeft > 0; bytesLeft -- {
|
||||||
|
nn, err := this.ReadFull(buffer[:])
|
||||||
|
n += nn; if err != nil { return 0, n, err }
|
||||||
|
value |= uint64(buffer[0]) << ((bytesLeft - 1) * 8)
|
||||||
|
}
|
||||||
|
// *read* integers too big, but don't return them.
|
||||||
|
if bytes > 8 { value = 0 }
|
||||||
|
return value, n, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ReadGBEU decodes a growing unsigned integer of up to 64 bits from the input
|
// ReadGBEU decodes a growing unsigned integer of up to 64 bits from the input
|
||||||
// reader.
|
// reader.
|
||||||
func (this *Decoder) ReadGBEU() (value uint64, n int, err error) {
|
func (this *Decoder) ReadGBEU() (value uint64, n int, err error) {
|
||||||
var fullValue uint64
|
var fullValue uint64
|
||||||
for {
|
for {
|
||||||
chunk, nn, err := this.ReadByte()
|
chunk, nn, err := this.ReadByte()
|
||||||
if err != nil { return 0, n, err }
|
n += nn; if err != nil { return 0, n, err }
|
||||||
n += nn
|
|
||||||
|
|
||||||
fullValue *= 0x80
|
fullValue *= 0x80
|
||||||
fullValue += uint64(chunk & 0x7F)
|
fullValue += uint64(chunk & 0x7F)
|
||||||
|
@ -76,13 +76,31 @@ func (this *Encoder) WriteUint64(value uint64) (n int, err error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteIntN encodes an N-byte signed integer to the output writer.
|
||||||
|
func (this *Encoder) WriteIntN(value int64, bytes int) (n int, err error) {
|
||||||
|
return this.WriteUintN(uint64(value), bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// for below functions, increase buffers if go somehow gets support for over 64
|
||||||
|
// bit integers. we could also make an expanding int type in goutil to use here,
|
||||||
|
// or maybe there is one in the stdlib. keep the int64 versions as well though
|
||||||
|
// because its ergonomic.
|
||||||
|
|
||||||
|
// WriteUintN encodes an N-byte unsigned integer to the output writer.
|
||||||
|
func (this *Encoder) WriteUintN(value uint64, bytes int) (n int, err error) {
|
||||||
|
// TODO: don't make multiple write calls (without allocating)
|
||||||
|
buffer := [1]byte { }
|
||||||
|
for bytesLeft := bytes; bytesLeft > 0; bytesLeft -- {
|
||||||
|
buffer[0] = byte(buffer[0]) >> ((bytesLeft - 1) * 8)
|
||||||
|
nn, err := this.Write(buffer[:])
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
// EncodeGBEU encodes a growing unsigned integer of up to 64 bits to the output
|
// EncodeGBEU encodes a growing unsigned integer of up to 64 bits to the output
|
||||||
// writer.
|
// writer.
|
||||||
func (this *Encoder) EncodeGBEU(value uint64) (n int, err error) {
|
func (this *Encoder) EncodeGBEU(value uint64) (n int, err error) {
|
||||||
// increase if go somehow gets support for over 64 bit integers. we
|
|
||||||
// could also make an expanding int type in goutil to use here, or maybe
|
|
||||||
// there is one in the stdlib. keep this int64 version as well though
|
|
||||||
// because its ergonomic.
|
|
||||||
buffer := [16]byte { }
|
buffer := [16]byte { }
|
||||||
|
|
||||||
window := (GBEUSize(value) - 1) * 7
|
window := (GBEUSize(value) - 1) * 7
|
||||||
|
@ -9,3 +9,14 @@ func GBEUSize(value uint64) int {
|
|||||||
if value == 0 { return length }
|
if value == 0 { return length }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IntBytes returns the number of bytes required to hold a given unsigned
|
||||||
|
// integer.
|
||||||
|
func IntBytes(value uint64) int {
|
||||||
|
bytes := 0
|
||||||
|
for value > 0 || bytes == 0 {
|
||||||
|
value >>= 8;
|
||||||
|
bytes ++
|
||||||
|
}
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
21
codec/measure_test.go
Normal file
21
codec/measure_test.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package codec
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestIntBytes(test *testing.T) {
|
||||||
|
if correct, got := 1, IntBytes(0); correct != got {
|
||||||
|
test.Fatal("wrong:", got)
|
||||||
|
}
|
||||||
|
if correct, got := 1, IntBytes(1); correct != got {
|
||||||
|
test.Fatal("wrong:", got)
|
||||||
|
}
|
||||||
|
if correct, got := 1, IntBytes(16); correct != got {
|
||||||
|
test.Fatal("wrong:", got)
|
||||||
|
}
|
||||||
|
if correct, got := 1, IntBytes(255); correct != got {
|
||||||
|
test.Fatal("wrong:", got)
|
||||||
|
}
|
||||||
|
if correct, got := 2, IntBytes(256); correct != got {
|
||||||
|
test.Fatal("wrong:", got)
|
||||||
|
}
|
||||||
|
}
|
@ -56,20 +56,27 @@ static section.
|
|||||||
|
|
||||||
For each defined type, the compiler shall generate a Go type with the same name
|
For each defined type, the compiler shall generate a Go type with the same name
|
||||||
as written in its definition. The Go type shall be encodable, and shall have
|
as written in its definition. The Go type shall be encodable, and shall have
|
||||||
`Encode` and `Decode` methods as described below.
|
`EncodeValue`, `DecodeValue`, and `Tag` methods as described below.
|
||||||
|
|
||||||
## Encoding and Decoding Methods
|
## Encoding and Decoding Methods
|
||||||
|
|
||||||
Each encodable type shall be given an `Encode` method and a `Decode` method,
|
Each message shall be given an `Encode` method and a `Decode` method,
|
||||||
which will take in a `codec.Encoder` and a `codec.Decoder` respectively. Both
|
which shall take in a `codec.Encoder` and a `codec.Decoder` respectively. Both
|
||||||
return an `(int, error)` pair describing the amount of bytes written and an
|
return an `(int, error)` pair describing the amount of bytes written and an
|
||||||
error if the write stopped early. `Encode` will encode the data within the
|
error if the write stopped early. `Encode` shall encode the data within the
|
||||||
message to the given encoder, and `Decode` will decode data from the given
|
message to the given encoder, and `Decode` shall decode data from the given
|
||||||
decoder and place it in the type's value. The methods shall not retain or close
|
decoder and place it in the type's value. The methods shall not retain or close
|
||||||
any encoders or decoders they are given. Both methods shall have pointer
|
any encoders or decoders they are given. Both methods shall have pointer
|
||||||
receivers. In effect, these methods will satisfy `codec.Encodable` and
|
receivers. In effect, these methods shall satisfy `codec.Encodable` and
|
||||||
`codec.Decodable`.
|
`codec.Decodable`.
|
||||||
|
|
||||||
|
Each defined type shall be given an `EncodeValue` method and a `DecodeValue`
|
||||||
|
method, which shall both take in a `tape.Tag`, then a `codec.Encoder` and a
|
||||||
|
`codec.Decoder` respectively. These methods shall encode and decode the value
|
||||||
|
according to the CN given by the tag. The TN shall be ignored. The message shall
|
||||||
|
also have a method `Tag` that takes no arguments and returns the preferred tag
|
||||||
|
of the type including the TN and CN.
|
||||||
|
|
||||||
## Connection
|
## Connection
|
||||||
|
|
||||||
The compiler shall generate a `Conn` struct which embeds a `hopp.Conn`, which
|
The compiler shall generate a `Conn` struct which embeds a `hopp.Conn`, which
|
||||||
|
603
generate/generate.go
Normal file
603
generate/generate.go
Normal file
@ -0,0 +1,603 @@
|
|||||||
|
package generate
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
import "fmt"
|
||||||
|
import "maps"
|
||||||
|
import "math"
|
||||||
|
import "slices"
|
||||||
|
import "strings"
|
||||||
|
import "git.tebibyte.media/sashakoshka/hopp/codec"
|
||||||
|
|
||||||
|
const imports =
|
||||||
|
`
|
||||||
|
import "git.teibibyte.media/sashakoshka/hopp/tape"
|
||||||
|
import "git.teibibyte.media/sashakoshka/hopp/codec"
|
||||||
|
`
|
||||||
|
|
||||||
|
const preamble = `
|
||||||
|
/* # Do not edit this package by hand!
|
||||||
|
*
|
||||||
|
* This file was automatically generated by the Holanet PDL compiler. The
|
||||||
|
* source file is located at <path>
|
||||||
|
* 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 {
|
||||||
|
codec.Encodable
|
||||||
|
codec.Decodable
|
||||||
|
|
||||||
|
// Method returns the method code of the message.
|
||||||
|
Method() uint16
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
// 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
|
||||||
|
protocol *Protocol
|
||||||
|
}
|
||||||
|
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
|
||||||
|
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 *codec.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 }
|
||||||
|
|
||||||
|
// TODO DecodeValue method
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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 }
|
||||||
|
|
||||||
|
// 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 *codec.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.WriteUint8()\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 }
|
||||||
|
|
||||||
|
// TODO decode method
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource string) (n int, err error) {
|
||||||
|
switch typ := typ.(type) {
|
||||||
|
case TypeInt:
|
||||||
|
// SI: (none)
|
||||||
|
// LI: <value: IntN>
|
||||||
|
if typ.Bits <= 5 {
|
||||||
|
// SI stores the value in the tag, so we write nothing here
|
||||||
|
break
|
||||||
|
}
|
||||||
|
nn, err := this.iprintf("nn, err = encoder.WriteInt%d(%s)\n", bitsToBytes(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: <value: FloatN>
|
||||||
|
nn, err := this.iprintf("nn, err = encoder.WriteFloat%d(%s)\n", bitsToBytes(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: <data: U8>*
|
||||||
|
// LBA: <length: UN> <data: U8>*
|
||||||
|
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(%s.CN(), uint64(len(%s)))\n",
|
||||||
|
tagSource, valueSource)
|
||||||
|
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", tagSource)
|
||||||
|
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: <length: UN> <elementTag: tape.Tag> <values>*
|
||||||
|
nn, err := this.iprintf(
|
||||||
|
"nn, err = encoder.WriteUintN(%s.CN(), uint64(len(%s)))\n",
|
||||||
|
tagSource, valueSource)
|
||||||
|
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 }
|
||||||
|
nn, err = this.iprintf("for _, item := range %s {\n", valueSource)
|
||||||
|
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(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("tag.Is(tape.SBA) { continue }\n")
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
nn, err = this.iprintf("tag.CN() > itemTag.CN() { largest = 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("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: <length: UN> (<key: U16> <tag: Tag> <value>)*
|
||||||
|
nn, err := this.iprintf(
|
||||||
|
"nn, err = encoder.WriteUintN(%s.CN(), uint64(len(%s)))\n",
|
||||||
|
tagSource, valueSource)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
nn, err = this.generateErrorCheck()
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
nn, err = this.iprintf("for key, item := range %s {\n", valueSource)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
this.push()
|
||||||
|
nn, err = this.iprintf("nn, err = encoder.WriteUint16(key)\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("tag := tape.TagAny(tag)\n")
|
||||||
|
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.iprintf("nn, err = tape.EncodeAny(tag)\n")
|
||||||
|
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 }
|
||||||
|
case TypeTableDefined:
|
||||||
|
// KTV: <length: UN> (<key: U16> <tag: Tag> <value>)*
|
||||||
|
nn, err := this.iprintf(
|
||||||
|
"nn, err = encoder.WriteUintN(%s.CN(), %d)\n",
|
||||||
|
tagSource, len(typ.Fields))
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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.TagSI")
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
} else {
|
||||||
|
nn, err := this.printf("tape.TagLI.WithCN(%d)", bitsToCN(typ.Bits))
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
}
|
||||||
|
case TypeFloat:
|
||||||
|
nn, err := this.printf("tape.TagFP.WithCN(%d)", bitsToCN(typ.Bits))
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
case TypeString:
|
||||||
|
nn, err := this.generateTag(TypeBuffer { }, source)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
case TypeBuffer:
|
||||||
|
nn, err := this.printf("bufferTag(%s)", source)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
case TypeArray:
|
||||||
|
nn, err := this.printf("arrayTag(tape.TagOTA.WithCN(tape.IntBytes(uint64(len(%s))))", source)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
case TypeTable:
|
||||||
|
nn, err := this.printf("tape.TagKTV.WithCN(tape.IntBytes(uint64(len(%s))))", source)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
case TypeTableDefined:
|
||||||
|
nn, err := this.printf("tape.TagKTV.WithCN(%d)", codec.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 }
|
||||||
|
}
|
||||||
|
|
||||||
|
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.TagSI")
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
} else {
|
||||||
|
nn, err := this.printf("tape.TagLI")
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
}
|
||||||
|
case TypeFloat:
|
||||||
|
nn, err := this.printf("tape.TagFP",)
|
||||||
|
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.TagLBA")
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
case TypeArray:
|
||||||
|
nn, err := this.printf("tape.TagOTA")
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
case TypeTable:
|
||||||
|
nn, err := this.printf("tape.TagKTV")
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
case TypeTableDefined:
|
||||||
|
nn, err := this.printf("tape.TagKTV")
|
||||||
|
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.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:
|
||||||
|
actual, err := this.resolveTypeName(typ.Name)
|
||||||
|
if err != nil { return n, err }
|
||||||
|
nn, err := this.generateType(actual)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Generator) validateIntBitSize(size int) error {
|
||||||
|
switch size {
|
||||||
|
case 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) {
|
||||||
|
switch name {
|
||||||
|
case "U8": return TypeInt { Bits: 8 }, nil
|
||||||
|
case "U16": return TypeInt { Bits: 16 }, nil
|
||||||
|
case "U32": return TypeInt { Bits: 32 }, nil
|
||||||
|
case "U64": return TypeInt { Bits: 64 }, nil
|
||||||
|
case "U128": return TypeInt { Bits: 128 }, nil
|
||||||
|
case "U256": return TypeInt { Bits: 256 }, nil
|
||||||
|
case "I8": return TypeInt { Bits: 8, Signed: true }, nil
|
||||||
|
case "I16": return TypeInt { Bits: 16, Signed: true }, nil
|
||||||
|
case "I32": return TypeInt { Bits: 32, Signed: true }, nil
|
||||||
|
case "I64": return TypeInt { Bits: 64, Signed: true }, nil
|
||||||
|
case "I128": return TypeInt { Bits: 128, Signed: true }, nil
|
||||||
|
case "I256": return TypeInt { Bits: 256, Signed: true }, nil
|
||||||
|
case "F16": return TypeFloat { Bits: 16 }, nil
|
||||||
|
case "F32": return TypeFloat { Bits: 32 }, nil
|
||||||
|
case "F64": return TypeFloat { Bits: 64 }, nil
|
||||||
|
case "F128": return TypeFloat { Bits: 128 }, nil
|
||||||
|
case "F256": return TypeFloat { Bits: 256 }, nil
|
||||||
|
case "String": return TypeString { }, nil
|
||||||
|
case "Buffer": return TypeBuffer { }, nil
|
||||||
|
case "Table": return TypeTable { }, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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 bitsToBytes(bits int) int {
|
||||||
|
return int(math.Ceil(float64(bits) / 8.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func bitsToCN(bits int) int {
|
||||||
|
return bitsToBytes(bits) - 1
|
||||||
|
}
|
@ -21,28 +21,7 @@ func Parse(lx parse.Lexer) (*Protocol, error) {
|
|||||||
func defaultProtocol() Protocol {
|
func defaultProtocol() Protocol {
|
||||||
return Protocol {
|
return Protocol {
|
||||||
Messages: make(map[uint16] Message),
|
Messages: make(map[uint16] Message),
|
||||||
Types: map[string] Type {
|
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 { },
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
48
tape/dynamic.go
Normal file
48
tape/dynamic.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package tape
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
import "reflect"
|
||||||
|
import "git.tebibyte.media/sashakoshka/hopp/codec"
|
||||||
|
|
||||||
|
// EncodeAny encodes an "any" value. Returns an error if the underlying type is
|
||||||
|
// unsupported. Supported types are:
|
||||||
|
//
|
||||||
|
// - int
|
||||||
|
// - int<N>
|
||||||
|
// - uint
|
||||||
|
// - uint<N>
|
||||||
|
// - string
|
||||||
|
// - []<supported type>
|
||||||
|
// - map[uint16]<supported type>
|
||||||
|
func EncodeAny(encoder *codec.Encoder, value any) (Tag, error) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
// TagAny returns the correct tag for an "any" value. Returns an error if the
|
||||||
|
// underlying type is unsupported. See [EncodeAny] for a list of supported
|
||||||
|
// types.
|
||||||
|
func TagAny(value any) (Tag, error) {
|
||||||
|
// primitives
|
||||||
|
switch value := value.(type) {
|
||||||
|
case int, uint: return LI.WithCN(3), nil
|
||||||
|
case int8, uint8: return LI.WithCN(0), nil
|
||||||
|
case int16, uint16: return LI.WithCN(1), nil
|
||||||
|
case int32, uint32: return LI.WithCN(3), nil
|
||||||
|
case int64, uint64: return LI.WithCN(8), nil
|
||||||
|
case string: return bufferLenTag(len(value)), nil
|
||||||
|
case []byte: return bufferLenTag(len(value)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// aggregates
|
||||||
|
reflectType := reflect.TypeOf(value)
|
||||||
|
switch reflectType.Kind() {
|
||||||
|
case reflect.Slice: return OTA.WithCN(reflect.ValueOf(value).Len()), nil
|
||||||
|
case reflect.Array: return OTA.WithCN(reflectType.Len()), nil
|
||||||
|
case reflect.Map:
|
||||||
|
if reflectType.Key() == reflect.TypeOf(uint16(0)) {
|
||||||
|
return OTA.WithCN(reflect.ValueOf(value).Len()), nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("cannot encode map key %T, key must be uint16", value)
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("cannot encode type %T", value)
|
||||||
|
}
|
45
tape/tag.go
Normal file
45
tape/tag.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package tape
|
||||||
|
|
||||||
|
import "git.tebibyte.media/sashakoshka/hopp/codec"
|
||||||
|
|
||||||
|
type Tag byte; const (
|
||||||
|
SI Tag = 0 << 5 // Small integer
|
||||||
|
LI Tag = 1 << 5 // Large integer
|
||||||
|
FP Tag = 2 << 5 // Floating point
|
||||||
|
SBA Tag = 3 << 5 // Small byte array
|
||||||
|
LBA Tag = 4 << 5 // Large byte array
|
||||||
|
OTA Tag = 5 << 5 // One-tag array
|
||||||
|
KTV Tag = 6 << 5 // Key-tag-value table
|
||||||
|
TNMask Tag = 0xE0 // The entire TN bitfield
|
||||||
|
CNMask Tag = 0x20 // The entire CN bitfield
|
||||||
|
CNLimit Tag = 32 // All valid CNs are < CNLimit
|
||||||
|
)
|
||||||
|
|
||||||
|
func (tag Tag) TN() int {
|
||||||
|
return int(tag >> 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tag Tag) CN() int {
|
||||||
|
return int(tag & CNMask)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tag Tag) WithCN(cn int) Tag {
|
||||||
|
return (tag & TNMask) | Tag(cn % 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tag Tag) Is(other Tag) bool {
|
||||||
|
return tag.TN() == other.TN()
|
||||||
|
}
|
||||||
|
|
||||||
|
// BufferTag returns the appropriate tag for a buffer.
|
||||||
|
func BufferTag(value []byte) Tag {
|
||||||
|
return bufferLenTag(len(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func bufferLenTag(length int) Tag {
|
||||||
|
if length < int(CNLimit) {
|
||||||
|
return SBA.WithCN(length)
|
||||||
|
} else {
|
||||||
|
return LBA.WithCN(codec.IntBytes(uint64(length)))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user