Big nasty commit to add code generation for encoding

This commit is contained in:
Sasha Koshka 2025-06-20 15:05:58 -04:00
parent a1f297e5b5
commit ce503c4689
9 changed files with 786 additions and 35 deletions

View File

@ -81,7 +81,7 @@ func (this *Decoder) ReadUint64() (value uint64, n int, err error) {
n, err = this.ReadFull(buffer[:])
return uint64(buffer[0]) << 56 |
uint64(buffer[1]) << 48 |
uint64(buffer[2]) << 48 |
uint64(buffer[2]) << 40 |
uint64(buffer[3]) << 32 |
uint64(buffer[4]) << 24 |
uint64(buffer[5]) << 16 |
@ -89,14 +89,33 @@ func (this *Decoder) ReadUint64() (value uint64, n int, err error) {
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
// reader.
func (this *Decoder) ReadGBEU() (value uint64, n int, err error) {
var fullValue uint64
for {
chunk, nn, err := this.ReadByte()
if err != nil { return 0, n, err }
n += nn
n += nn; if err != nil { return 0, n, err }
fullValue *= 0x80
fullValue += uint64(chunk & 0x7F)

View File

@ -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
// writer.
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 { }
window := (GBEUSize(value) - 1) * 7

View File

@ -9,3 +9,14 @@ func GBEUSize(value uint64) int {
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
View 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)
}
}

View File

@ -56,20 +56,27 @@ static section.
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
`Encode` and `Decode` methods as described below.
`EncodeValue`, `DecodeValue`, and `Tag` methods as described below.
## Encoding and Decoding Methods
Each encodable type shall be given an `Encode` method and a `Decode` method,
which will take in a `codec.Encoder` and a `codec.Decoder` respectively. Both
Each message shall be given an `Encode` method and a `Decode` method,
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
error if the write stopped early. `Encode` will encode the data within the
message to the given encoder, and `Decode` will decode data from the given
error if the write stopped early. `Encode` shall encode the data within the
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
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`.
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
The compiler shall generate a `Conn` struct which embeds a `hopp.Conn`, which

603
generate/generate.go Normal file
View 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
}

View File

@ -21,28 +21,7 @@ func Parse(lx parse.Lexer) (*Protocol, error) {
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 { },
},
Types: map[string] Type { },
}
}

48
tape/dynamic.go Normal file
View 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
View 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)))
}
}