Compare commits
67 Commits
e75d7534c1
...
encode-sig
| Author | SHA1 | Date | |
|---|---|---|---|
| dc72cc2010 | |||
| 0e03f84b8a | |||
| 02196edf61 | |||
| 1058615f6f | |||
| 024edfa922 | |||
| fe973af99c | |||
| 52f0d6932e | |||
| 8e14a2c3f1 | |||
| 4fbb70081a | |||
| a108e53cb6 | |||
| 57c30ac669 | |||
| a270c22cb9 | |||
| a99d4dee66 | |||
| c18e251b4a | |||
| 170f79c914 | |||
| 77c6b67d65 | |||
| 195d0f9725 | |||
| fa4f591126 | |||
| 12142706e1 | |||
| 30e9ead1ab | |||
| 1118b11bcd | |||
| 7343cf5853 | |||
| a9f583d2e7 | |||
| c4dd129fc5 | |||
| 2cbf58d558 | |||
| 7dcfc08678 | |||
| 711ac30486 | |||
| b15c3aa76c | |||
| a1bfae443c | |||
| df3fe1280d | |||
| 41b3376fa3 | |||
| fae702edfd | |||
| c86f9b03f2 | |||
| dcbfbe9141 | |||
| 40444ee2f4 | |||
| 59cc90166f | |||
| f222fb02b7 | |||
| 6ecc33a46b | |||
| 5d84636b55 | |||
| f009a970cd | |||
| 8b63166ba1 | |||
| 3ef7de118b | |||
| 51ed6aed9f | |||
| 6017ac1fa3 | |||
| b8047585fb | |||
| ad3973dd9e | |||
| 0f626b2e93 | |||
| 272e47224d | |||
| 2c57423838 | |||
| e2b9e809a8 | |||
| 7e8b272ef0 | |||
| a257902705 | |||
| 4955f66ad6 | |||
| f646207ab1 | |||
| b50a199842 | |||
| b826cbf83e | |||
| b73f9fa7ce | |||
| d3d7b07a74 | |||
| daa6a44179 | |||
| af7669c783 | |||
| 2305814e10 | |||
| 5a3296a842 | |||
| 3bf365a7a9 | |||
| e48be0bc15 | |||
| a210f6112c | |||
| 9ff317d443 | |||
| cdba8ee601 |
128
design/branched-generated-encoder.md
Normal file
128
design/branched-generated-encoder.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
# Branched Generated Decoder
|
||||||
|
|
||||||
|
Pasted here because Tebitea is down
|
||||||
|
|
||||||
|
## The problem
|
||||||
|
|
||||||
|
TAPE is designed so that the decoder can gloss over data it does not understand.
|
||||||
|
Technically the protocol allows for this, but I completely forgot to implement
|
||||||
|
this in the generated decoder, oops. This would be trivial if TAPE messages were
|
||||||
|
still flat tables, but they aren't, because those aren't useful enough. So,
|
||||||
|
let's analyze the problem.
|
||||||
|
|
||||||
|
## When it happens
|
||||||
|
|
||||||
|
There are two reasons something might not match up with the expected data:
|
||||||
|
|
||||||
|
The first and most obvious is unrecognized keys. If the key is not in the set of
|
||||||
|
recognized keys for a KTV, it should leave the corresponding struct field blank.
|
||||||
|
Once #6 has been implemented, throw an error if the data was not optional.
|
||||||
|
|
||||||
|
The second is wrong types. If we are expecting KTV and get SBA, we should leave
|
||||||
|
the data as empty. The aforementioned concern about #6 also applies here. We
|
||||||
|
don't need to worry about special cases at the structure root, because it would
|
||||||
|
be technically possible to make the structure root an option, so it really is
|
||||||
|
just a normal value. Until #6, we will leave that blank too.
|
||||||
|
|
||||||
|
## Preliminary ideas
|
||||||
|
|
||||||
|
The first is going to be pretty simple. All we need to do is have a skimmer
|
||||||
|
function that skims over TAPE data very, and then call that on the KTV value
|
||||||
|
each time we run into a mystery key. It should only return an error if the
|
||||||
|
structure of the data is malformed in such a way that it cannot continue to the
|
||||||
|
next one. This should be stored in the tape package alongside the dynamic
|
||||||
|
decoding functions, because they will essentially function the same way and
|
||||||
|
could probably share lots of code.
|
||||||
|
|
||||||
|
The second is a bit more complicated because of the existence of KTV and OTA
|
||||||
|
because they are aggregate types. Go types work a bit differently, as if you
|
||||||
|
have an array of an array of an array of ints, that information is represented
|
||||||
|
in one place, whereas TAPE doesn't really do that. All of that information is
|
||||||
|
sort of buried within the data structure, so we don't know what we will be
|
||||||
|
decoding before we actually do it. Whenever we encounter a type we don't expect,
|
||||||
|
we would need to abort decoding of the entire data structure, and then skim over
|
||||||
|
whatever detritus is left, which would literally be in a half-decoded state. The
|
||||||
|
fact that the code is generated flat and thus cannot use return or defer
|
||||||
|
statements contributes to the complexity of this problem. We need to go up, but
|
||||||
|
we can't. There is no up, only forward.
|
||||||
|
|
||||||
|
Of course, the dynamic decoder does not have this problem in the first place
|
||||||
|
because it doesn't expect anything, and constructs the destination to fit
|
||||||
|
whatever it sees in the TAPE structure as it is decoding it. KTVs are completely
|
||||||
|
dynamic because they are implemented as maps, so the only time it needs to
|
||||||
|
completely comprehend a type is with OTAs. There is a function called typeOf
|
||||||
|
that gets the type of the current tag and returns it as a reflect.Type, which
|
||||||
|
necessitates recursion and peeking at OTAs and their elements.
|
||||||
|
|
||||||
|
We could try to do the same thing in the generated decoder, comparing the
|
||||||
|
determined type against the expected type to try to figure out whether we should
|
||||||
|
decode an array or a table, etc. This is immediately problematic as it requires
|
||||||
|
memory to be allocated, both for the peek buffer and the resulting tree of type
|
||||||
|
information. If we end up with some crazy way to keep track of the types, that's
|
||||||
|
only one half of the allocation problem and we would still be spending extra
|
||||||
|
cycles going over all of that twice.
|
||||||
|
|
||||||
|
## Performance constraints
|
||||||
|
|
||||||
|
The generated decoder is supposed to blaze through data, and it can't do that if
|
||||||
|
it does all the singing and dancing that the dynamic decoder does. It's time for
|
||||||
|
some performance constraints:
|
||||||
|
|
||||||
|
- No allocations, except as required to build the destination for the data
|
||||||
|
- No redundant work
|
||||||
|
- So, no freaking peeking
|
||||||
|
- It should take well under 500 lines of generated code to decode one message of
|
||||||
|
reasonable size (i.e. be careful not to bloat the binary)
|
||||||
|
|
||||||
|
I'm not really going to do my usual thing here of making a slow version and
|
||||||
|
speeding it up over time based on evidence and experimentation because these
|
||||||
|
constraints inform the design so much it would be impossible to continue without
|
||||||
|
them. I am 99% confident that these constraints will allow for an acceptable
|
||||||
|
baseline of performance (for generated code) and we can still profile and
|
||||||
|
micro-optimize later. This is good enough for me.
|
||||||
|
Heavy solution
|
||||||
|
|
||||||
|
There is a solution that might work very well which involves completely redoing
|
||||||
|
the generated decoding code. We could create a function for every source type to
|
||||||
|
destination type mapping that exists in protocol, and then compose them all
|
||||||
|
together. The decoding methods for each message or type would be wrappers around
|
||||||
|
the correct function for their root TAPE -> Go type mapping. The main benefit of
|
||||||
|
this is it would make this problem a lot more manageable because the interface
|
||||||
|
points between the data would be represented by function boundaries. This would
|
||||||
|
allow the use of return and defer statements, and would allow more code sharing,
|
||||||
|
producing a smaller binary. Go would probably inline these where needed.
|
||||||
|
|
||||||
|
Would this work? Probably. More investigation is required to make sure. I want
|
||||||
|
to stop re-writing things I don't need to. On the other hand, it is just the
|
||||||
|
decoder.
|
||||||
|
|
||||||
|
## Light solution
|
||||||
|
|
||||||
|
TODO: find a solution that satisfies the performance constraints, keeps the same
|
||||||
|
identical interface, and works off the same code. I am convinced this is doable,
|
||||||
|
and it might even allow us to extract more data from an unexpected structure.
|
||||||
|
However, continuing this way might introduce unmanageable complexity. It is
|
||||||
|
already a little unmanageable and I am just one pony (kind of).
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
Heavy solution is going to work here, applied to only the points of
|
||||||
|
`Generator.generateDecodeValue` where it decodes an aggregate data structure.
|
||||||
|
That way, only minimal amounts of code need to be redone.
|
||||||
|
|
||||||
|
Whenever a branch needs to happen, a call shall be generated, a deferred
|
||||||
|
implementation request shall be added to a special FIFO queue within the
|
||||||
|
generator. After generating data structures and their root decoding functions,
|
||||||
|
the generator shall pick away at this queue until no requests remain. The
|
||||||
|
generator shall accept new items during this process, so that recursion is
|
||||||
|
possible. This is all to ensure it is only ever writing one function at a time
|
||||||
|
|
||||||
|
The functions shall take a pointer to a type that accepts any type like (~) the
|
||||||
|
destination's base type. We should also probably just call
|
||||||
|
`Generator.generateDecodeValue` directly on user defined types this way, keeping
|
||||||
|
their public `Decode` methods just for convenience.
|
||||||
|
|
||||||
|
The tape package shall contain a skimming function that takes a decoder and a
|
||||||
|
tag, and recursively consumes the decoder given the context of the tag. This
|
||||||
|
shall be utilized by the decoder functions to skip over values if their tags
|
||||||
|
or keys do not match up with what is expected.
|
||||||
@@ -7,12 +7,12 @@ PDL allows defining a protocol using HOPP and TAPE.
|
|||||||
| Syntax | TN | CN | Description
|
| Syntax | TN | CN | Description
|
||||||
| ---------- | ------- | -: | -----------
|
| ---------- | ------- | -: | -----------
|
||||||
| I5 | SI | |
|
| I5 | SI | |
|
||||||
| I8 | LI | 0 |
|
| I8 | LSI | 0 |
|
||||||
| I16 | LI | 1 |
|
| I16 | LSI | 1 |
|
||||||
| I32 | LI | 3 |
|
| I32 | LSI | 3 |
|
||||||
| I64 | LI | 7 |
|
| I64 | LSI | 7 |
|
||||||
| I128[^2] | LI | 15 |
|
| I128[^2] | LSI | 15 |
|
||||||
| I256[^2] | LI | 31 |
|
| I256[^2] | LSI | 31 |
|
||||||
| U5 | SI | |
|
| U5 | SI | |
|
||||||
| U8 | LI | 0 |
|
| U8 | LI | 0 |
|
||||||
| U16 | LI | 1 |
|
| U16 | LI | 1 |
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import "maps"
|
|||||||
import "math"
|
import "math"
|
||||||
import "slices"
|
import "slices"
|
||||||
import "strings"
|
import "strings"
|
||||||
|
import "encoding/hex"
|
||||||
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
||||||
|
|
||||||
const imports =
|
const imports =
|
||||||
@@ -14,14 +15,11 @@ import "git.tebibyte.media/sashakoshka/hopp/tape"
|
|||||||
`
|
`
|
||||||
|
|
||||||
const preamble = `
|
const preamble = `
|
||||||
/* # Do not edit this package by hand!
|
// Code generated by the Holanet PDL compiler. DO NOT EDIT.
|
||||||
*
|
// The source file is located at <path>
|
||||||
* This file was automatically generated by the Holanet PDL compiler. The
|
// Please edit that file instead, and re-compile it to this location.
|
||||||
* source file is located at <path>
|
// HOPP, TAPE, METADAPT, PDL/0 (c) 2025 holanet.xyz
|
||||||
* Please edit that file instead, and re-compile it to this location.
|
|
||||||
*
|
|
||||||
* HOPP, TAPE, METADAPT, PDL/0 (c) 2025 holanet.xyz
|
|
||||||
*/
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const static = `
|
const static = `
|
||||||
@@ -36,6 +34,19 @@ type Message interface {
|
|||||||
// Method returns the method code of the message.
|
// Method returns the method code of the message.
|
||||||
Method() uint16
|
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.
|
// Generator converts protocols into Go code.
|
||||||
@@ -47,7 +58,16 @@ type Generator struct {
|
|||||||
PackageName string
|
PackageName string
|
||||||
|
|
||||||
nestingLevel int
|
nestingLevel int
|
||||||
|
temporaryVar int
|
||||||
protocol *Protocol
|
protocol *Protocol
|
||||||
|
|
||||||
|
decodeBranchRequestQueue []decodeBranchRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
type decodeBranchRequest struct {
|
||||||
|
hash [16]byte
|
||||||
|
typ Type
|
||||||
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Generator) Generate(protocol *Protocol) (n int, err error) {
|
func (this *Generator) Generate(protocol *Protocol) (n int, err error) {
|
||||||
@@ -81,6 +101,14 @@ func (this *Generator) Generate(protocol *Protocol) (n int, err error) {
|
|||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// request queue
|
||||||
|
for {
|
||||||
|
hash, typ, name, ok := this.pullDecodeBranchRequest()
|
||||||
|
if !ok { break }
|
||||||
|
nn, err := this.generateDecodeBranch(hash, typ, name)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
}
|
||||||
|
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +125,7 @@ func (this *Generator) generateTypedef(name string, typ Type) (n int, err error)
|
|||||||
nn, err = this.println()
|
nn, err = this.println()
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
|
|
||||||
// Tag method
|
// 'Tag' method
|
||||||
// to be honest we probably don't need this method at all
|
// to be honest we probably don't need this method at all
|
||||||
// nn, err = this.iprintf("\n// Tag returns the preferred TAPE tag.\n")
|
// nn, err = this.iprintf("\n// Tag returns the preferred TAPE tag.\n")
|
||||||
// n += nn; if err != nil { return n, err }
|
// n += nn; if err != nil { return n, err }
|
||||||
@@ -146,7 +174,27 @@ func (this *Generator) generateTypedef(name string, typ Type) (n int, err error)
|
|||||||
name)
|
name)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
this.push()
|
this.push()
|
||||||
nn, err = this.generateDecodeValue(typ, "this", "tag")
|
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, name, "this", "tag")
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
nn, err = this.iprintf("return n, nil\n")
|
nn, err = this.iprintf("return n, nil\n")
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
@@ -170,11 +218,20 @@ func (this *Generator) generateMessage(method uint16, message Message) (n int, e
|
|||||||
nn, err = this.println()
|
nn, err = this.println()
|
||||||
n += nn; if err != nil { return n, err }
|
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
|
// Encode method
|
||||||
nn, err = this.iprintf("\n// Encode encodes this message's tag and value.\n")
|
nn, err = this.iprintf("\n// Encode encodes this message's tag and value.\n")
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
nn, err = this.iprintf(
|
nn, err = this.iprintf(
|
||||||
"func(this %s) Encode(encoder *tape.Encoder) (n int, err error) {\n",
|
"func(this *%s) Encode(encoder *tape.Encoder) (n int, err error) {\n",
|
||||||
this.resolveMessageName(message.Name))
|
this.resolveMessageName(message.Name))
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
this.push()
|
this.push()
|
||||||
@@ -184,7 +241,7 @@ func (this *Generator) generateMessage(method uint16, message Message) (n int, e
|
|||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
nn, err = this.println()
|
nn, err = this.println()
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
nn, err = this.iprintf("nn, err := encoder.WriteUint8()\n")
|
nn, err = this.iprintf("nn, err := encoder.WriteTag(tag)\n")
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
nn, err = this.generateErrorCheck()
|
nn, err = this.generateErrorCheck()
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
@@ -196,7 +253,43 @@ func (this *Generator) generateMessage(method uint16, message Message) (n int, e
|
|||||||
nn, err = this.iprintf("}\n")
|
nn, err = this.iprintf("}\n")
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
|
|
||||||
// TODO decode method
|
// 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.resolveMessageName(message.Name), "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
|
return n, nil
|
||||||
}
|
}
|
||||||
@@ -214,19 +307,23 @@ func (this *Generator) generateMessage(method uint16, message Message) (n int, e
|
|||||||
func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource string) (n int, err error) {
|
func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource string) (n int, err error) {
|
||||||
switch typ := typ.(type) {
|
switch typ := typ.(type) {
|
||||||
case TypeInt:
|
case TypeInt:
|
||||||
// SI: (none)
|
// SI: (none)
|
||||||
// LI: <value: IntN>
|
// LI/LSI: <value: IntN>
|
||||||
if typ.Bits <= 5 {
|
if typ.Bits <= 5 {
|
||||||
// SI stores the value in the tag, so we write nothing here
|
// SI stores the value in the tag, so we write nothing here
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
nn, err := this.iprintf("nn, err = encoder.WriteInt%d(%s)\n", bitsToBytes(typ.Bits), valueSource)
|
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 }
|
n += nn; if err != nil { return n, err }
|
||||||
nn, err = this.generateErrorCheck()
|
nn, err = this.generateErrorCheck()
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
case TypeFloat:
|
case TypeFloat:
|
||||||
// FP: <value: FloatN>
|
// FP: <value: FloatN>
|
||||||
nn, err := this.iprintf("nn, err = encoder.WriteFloat%d(%s)\n", bitsToBytes(typ.Bits), valueSource)
|
nn, err := this.iprintf("nn, err = encoder.WriteFloat%d(%s)\n", typ.Bits, valueSource)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
nn, err = this.generateErrorCheck()
|
nn, err = this.generateErrorCheck()
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
@@ -241,13 +338,13 @@ func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource stri
|
|||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
this.push()
|
this.push()
|
||||||
nn, err = this.iprintf(
|
nn, err = this.iprintf(
|
||||||
"nn, err = encoder.WriteUintN(%s.CN(), uint64(len(%s)))\n",
|
"nn, err = encoder.WriteUintN(uint64(len(%s)), %s.CN())\n",
|
||||||
tagSource, valueSource)
|
valueSource, tagSource)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
nn, err = this.generateErrorCheck()
|
nn, err = this.generateErrorCheck()
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
this.pop()
|
this.pop()
|
||||||
nn, err = this.iprintf("}\n", tagSource)
|
nn, err = this.iprintf("}\n")
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
|
|
||||||
nn, err = this.iprintf("nn, err = encoder.Write([]byte(%s))\n", valueSource)
|
nn, err = this.iprintf("nn, err = encoder.Write([]byte(%s))\n", valueSource)
|
||||||
@@ -257,8 +354,8 @@ func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource stri
|
|||||||
case TypeArray:
|
case TypeArray:
|
||||||
// OTA: <length: UN> <elementTag: tape.Tag> <values>*
|
// OTA: <length: UN> <elementTag: tape.Tag> <values>*
|
||||||
nn, err := this.iprintf(
|
nn, err := this.iprintf(
|
||||||
"nn, err = encoder.WriteUintN(%s.CN(), uint64(len(%s)))\n",
|
"nn, err = encoder.WriteUintN(uint64(len(%s)), %s.CN())\n",
|
||||||
tagSource, valueSource)
|
valueSource, tagSource)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
nn, err = this.generateErrorCheck()
|
nn, err = this.generateErrorCheck()
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
@@ -271,9 +368,14 @@ func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource stri
|
|||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
nn, err = this.println()
|
nn, err = this.println()
|
||||||
n += nn; if err != nil { return n, err }
|
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)
|
nn, err = this.iprintf("for _, item := range %s {\n", valueSource)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
this.push()
|
this.push()
|
||||||
|
nn, err = this.iprintf("_ = item\n")
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
nn, err = this.iprintf("tag := ")
|
nn, err = this.iprintf("tag := ")
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
nn, err = this.generateTag(typ.Element, "item")
|
nn, err = this.generateTag(typ.Element, "item")
|
||||||
@@ -282,13 +384,17 @@ func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource stri
|
|||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
nn, err = this.iprintf("if tag.Is(tape.SBA) { continue }\n")
|
nn, err = this.iprintf("if tag.Is(tape.SBA) { continue }\n")
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
nn, err = this.iprintf("if tag.CN() > itemTag.CN() { largest = tag }\n")
|
nn, err = this.iprintf("if tag.CN() > itemTag.CN() { itemTag = tag }\n")
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
this.pop()
|
this.pop()
|
||||||
nn, err = this.iprintf("}\n")
|
nn, err = this.iprintf("}\n")
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
nn, err = this.iprintf("if itemTag.Is(tape.SBA) { itemTag += 1 << 5 }\n")
|
nn, err = this.iprintf("if itemTag.Is(tape.SBA) { itemTag += 1 << 5 }\n")
|
||||||
n += nn; if err != nil { return n, err }
|
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)
|
nn, err = this.iprintf("for _, item := range %s {\n", valueSource)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
this.push()
|
this.push()
|
||||||
@@ -311,8 +417,8 @@ func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource stri
|
|||||||
case TypeTableDefined:
|
case TypeTableDefined:
|
||||||
// KTV: <length: UN> (<key: U16> <tag: Tag> <value>)*
|
// KTV: <length: UN> (<key: U16> <tag: Tag> <value>)*
|
||||||
nn, err := this.iprintf(
|
nn, err := this.iprintf(
|
||||||
"nn, err = encoder.WriteUintN(%s.CN(), %d)\n",
|
"nn, err = encoder.WriteUintN(%d, %s.CN())\n",
|
||||||
tagSource, len(typ.Fields))
|
len(typ.Fields), tagSource)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
nn, err = this.generateErrorCheck()
|
nn, err = this.generateErrorCheck()
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
@@ -349,8 +455,10 @@ func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource stri
|
|||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
nn, err = this.generateErrorCheck()
|
nn, err = this.generateErrorCheck()
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unknown type: %T", typ))
|
||||||
}
|
}
|
||||||
|
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,9 +472,357 @@ func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource stri
|
|||||||
// - n int
|
// - n int
|
||||||
// - err error
|
// - err error
|
||||||
// - nn int
|
// - nn int
|
||||||
func (this *Generator) generateDecodeValue(typ Type, valueSource, tagSource string) (n int, err error) {
|
//
|
||||||
// TODO
|
// The typeName paramterer is handled in the way described in the documentation
|
||||||
return 0, nil
|
// for [Generator.generateDecodeBranch].
|
||||||
|
func (this *Generator) generateDecodeValue(typ Type, typeName, valueSource, tagSource string) (n int, err error) {
|
||||||
|
switch typ := typ.(type) {
|
||||||
|
case TypeInt:
|
||||||
|
// SI: (none)
|
||||||
|
// LI/LSI: <value: IntN>
|
||||||
|
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: <value: FloatN>
|
||||||
|
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: <data: U8>*
|
||||||
|
// LBA: <length: UN> <data: U8>*
|
||||||
|
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: <length: UN> <elementTag: tape.Tag> <values>*
|
||||||
|
nn, err := this.generateDecodeBranchCall(typ, typeName, valueSource, tagSource)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
case TypeTable:
|
||||||
|
// KTV: <length: UN> (<key: U16> <tag: Tag> <value>)*
|
||||||
|
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: <length: UN> (<key: U16> <tag: Tag> <value>)*
|
||||||
|
nn, err := this.generateDecodeBranchCall(typ, typeName, 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
|
||||||
|
//
|
||||||
|
// The typeName paramterer is handled in the way described in the documentation
|
||||||
|
// for [Generator.generateDecodeBranch].
|
||||||
|
func (this *Generator) generateDecodeBranchCall(typ Type, typeName, valueSource, tagSource string) (n int, err error) {
|
||||||
|
hash := HashType(typ)
|
||||||
|
nn, err := this.iprintf(
|
||||||
|
"nn, err = %s(%s, decoder, %s)\n",
|
||||||
|
this.decodeBranchName(hash, typeName), 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, typeName)
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateDecodeBranch generates an aggregate decoder function definition for a
|
||||||
|
// specified type. It assumes that hash == HashType(typ). If typeName is not
|
||||||
|
// empty, it will be used as the type in the argument list instead of the result
|
||||||
|
// of [Generator.generateType].
|
||||||
|
func (this *Generator) generateDecodeBranch(hash [16]byte, typ Type, typeName string) (n int, err error) {
|
||||||
|
nn, err := this.iprintf("\nfunc %s(this *", this.decodeBranchName(hash, typeName))
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
if typeName == "" {
|
||||||
|
nn, err = this.generateType(typ)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
} else {
|
||||||
|
nn, err = this.print(typeName)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
}
|
||||||
|
nn, err = this.printf(", 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: <length: UN> <elementTag: tape.Tag> <values>*
|
||||||
|
// 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 tape.Tag\n", elementTagVar)
|
||||||
|
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: <length: UN> (<key: U16> <tag: Tag> <value>)*
|
||||||
|
// 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 }
|
||||||
|
|
||||||
|
// 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 _ = range int(%s) {\n", 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
nn, err = this.iprintf("return n, nil\n")
|
||||||
|
|
||||||
|
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, name string) string {
|
||||||
|
if name == "" {
|
||||||
|
return fmt.Sprintf("decodeBranch_%s", hex.EncodeToString(hash[:]))
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("decodeBranch_%s_%s", hex.EncodeToString(hash[:]), name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, name string) {
|
||||||
|
for _, item := range this.decodeBranchRequestQueue {
|
||||||
|
if item.hash == hash && item.name == name { return }
|
||||||
|
}
|
||||||
|
this.decodeBranchRequestQueue = append(this.decodeBranchRequestQueue, decodeBranchRequest {
|
||||||
|
hash: hash,
|
||||||
|
typ: typ,
|
||||||
|
name: name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// pullDecodeBranchRequest pulls a branch decode function request from the front
|
||||||
|
// of the queue.
|
||||||
|
func (this *Generator) pullDecodeBranchRequest() (hash [16]byte, typ Type, name string, 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, request.name, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Generator) generateErrorCheck() (n int, err error) {
|
func (this *Generator) generateErrorCheck() (n int, err error) {
|
||||||
@@ -379,35 +835,40 @@ func (this *Generator) generateTag(typ Type, source string) (n int, err error) {
|
|||||||
switch typ := typ.(type) {
|
switch typ := typ.(type) {
|
||||||
case TypeInt:
|
case TypeInt:
|
||||||
if typ.Bits <= 5 {
|
if typ.Bits <= 5 {
|
||||||
nn, err := this.printf("tape.TagSI")
|
nn, err := this.printf("tape.SI.WithCN(int(%s))", source)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
} else if typ.Signed {
|
||||||
|
nn, err := this.printf("tape.LSI.WithCN(%d)", bitsToCN(typ.Bits))
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
} else {
|
} else {
|
||||||
nn, err := this.printf("tape.TagLI.WithCN(%d)", bitsToCN(typ.Bits))
|
nn, err := this.printf("tape.LI.WithCN(%d)", bitsToCN(typ.Bits))
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
}
|
}
|
||||||
case TypeFloat:
|
case TypeFloat:
|
||||||
nn, err := this.printf("tape.TagFP.WithCN(%d)", bitsToCN(typ.Bits))
|
nn, err := this.printf("tape.FP.WithCN(%d)", bitsToCN(typ.Bits))
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
case TypeString:
|
case TypeString:
|
||||||
nn, err := this.generateTag(TypeBuffer { }, source)
|
nn, err := this.printf("tape.StringTag(%s)", source)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
case TypeBuffer:
|
case TypeBuffer:
|
||||||
nn, err := this.printf("bufferTag(%s)", source)
|
nn, err := this.printf("tape.BufferTag(%s)", source)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
case TypeArray:
|
case TypeArray:
|
||||||
nn, err := this.printf("arrayTag(tape.TagOTA.WithCN(tape.IntBytes(uint64(len(%s)))))", source)
|
nn, err := this.printf("tape.OTA.WithCN(tape.IntBytes(uint64(len(%s))))", source)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
case TypeTable:
|
case TypeTable:
|
||||||
nn, err := this.printf("tape.TagKTV.WithCN(tape.IntBytes(uint64(len(%s))))", source)
|
nn, err := this.printf("tape.KTV.WithCN(tape.IntBytes(uint64(len(%s))))", source)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
case TypeTableDefined:
|
case TypeTableDefined:
|
||||||
nn, err := this.printf("tape.TagKTV.WithCN(%d)", tape.IntBytes(uint64(len(typ.Fields))))
|
nn, err := this.printf("tape.KTV.WithCN(%d)", tape.IntBytes(uint64(len(typ.Fields))))
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
case TypeNamed:
|
case TypeNamed:
|
||||||
resolved, err := this.resolveTypeName(typ.Name)
|
resolved, err := this.resolveTypeName(typ.Name)
|
||||||
if err != nil { return n, err }
|
if err != nil { return n, err }
|
||||||
nn, err := this.generateTag(resolved, source)
|
nn, err := this.generateTag(resolved, source)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unknown type: %T", typ))
|
||||||
}
|
}
|
||||||
|
|
||||||
return n, nil
|
return n, nil
|
||||||
@@ -421,29 +882,32 @@ func (this *Generator) generateTN(typ Type) (n int, err error) {
|
|||||||
switch typ := typ.(type) {
|
switch typ := typ.(type) {
|
||||||
case TypeInt:
|
case TypeInt:
|
||||||
if typ.Bits <= 5 {
|
if typ.Bits <= 5 {
|
||||||
nn, err := this.printf("tape.TagSI")
|
nn, err := this.printf("tape.SI")
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
} else if typ.Signed {
|
||||||
|
nn, err := this.printf("tape.LSI")
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
} else {
|
} else {
|
||||||
nn, err := this.printf("tape.TagLI")
|
nn, err := this.printf("tape.LI")
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
}
|
}
|
||||||
case TypeFloat:
|
case TypeFloat:
|
||||||
nn, err := this.printf("tape.TagFP",)
|
nn, err := this.printf("tape.FP",)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
case TypeString:
|
case TypeString:
|
||||||
nn, err := this.generateTN(TypeBuffer { })
|
nn, err := this.generateTN(TypeBuffer { })
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
case TypeBuffer:
|
case TypeBuffer:
|
||||||
nn, err := this.printf("tape.TagLBA")
|
nn, err := this.printf("tape.LBA")
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
case TypeArray:
|
case TypeArray:
|
||||||
nn, err := this.printf("tape.TagOTA")
|
nn, err := this.printf("tape.OTA")
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
case TypeTable:
|
case TypeTable:
|
||||||
nn, err := this.printf("tape.TagKTV")
|
nn, err := this.printf("tape.KTV")
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
case TypeTableDefined:
|
case TypeTableDefined:
|
||||||
nn, err := this.printf("tape.TagKTV")
|
nn, err := this.printf("tape.KTV")
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
case TypeNamed:
|
case TypeNamed:
|
||||||
resolved, err := this.resolveTypeName(typ.Name)
|
resolved, err := this.resolveTypeName(typ.Name)
|
||||||
@@ -461,6 +925,11 @@ func (this *Generator) generateType(typ Type) (n int, err error) {
|
|||||||
if err := this.validateIntBitSize(typ.Bits); err != nil {
|
if err := this.validateIntBitSize(typ.Bits); err != nil {
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
if typ.Bits <= 5 {
|
||||||
|
nn, err := this.printf("uint8")
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
break
|
||||||
|
}
|
||||||
if typ.Signed {
|
if typ.Signed {
|
||||||
nn, err := this.printf("int%d", typ.Bits)
|
nn, err := this.printf("int%d", typ.Bits)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
@@ -497,9 +966,7 @@ func (this *Generator) generateType(typ Type) (n int, err error) {
|
|||||||
nn, err := this.generateTypeTableDefined(typ)
|
nn, err := this.generateTypeTableDefined(typ)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
case TypeNamed:
|
case TypeNamed:
|
||||||
actual, err := this.resolveTypeName(typ.Name)
|
nn, err := this.print(typ.Name)
|
||||||
if err != nil { return n, err }
|
|
||||||
nn, err := this.generateType(actual)
|
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
}
|
}
|
||||||
return n, nil
|
return n, nil
|
||||||
@@ -526,9 +993,22 @@ func (this *Generator) generateTypeTableDefined(typ TypeTableDefined) (n int, er
|
|||||||
return n, nil
|
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 {
|
func (this *Generator) validateIntBitSize(size int) error {
|
||||||
switch size {
|
switch size {
|
||||||
case 8, 16, 32, 64: return nil
|
case 5, 8, 16, 32, 64: return nil
|
||||||
default: return fmt.Errorf("integers of size %d are unsupported on this platform", size)
|
default: return fmt.Errorf("integers of size %d are unsupported on this platform", size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -583,30 +1063,7 @@ func (this *Generator) resolveMessageName(message string) string {
|
|||||||
return "Message" + message
|
return "Message" + message
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Generator) resolveTypeName(name string) (Type, error) {
|
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 := this.protocol.Types[name]; ok {
|
||||||
if typ, ok := typ.(TypeNamed); ok {
|
if typ, ok := typ.(TypeNamed); ok {
|
||||||
return this.resolveTypeName(typ.Name)
|
return this.resolveTypeName(typ.Name)
|
||||||
@@ -617,6 +1074,11 @@ func (this *Generator) resolveTypeName(name string) (Type, error) {
|
|||||||
return nil, fmt.Errorf("no type exists called %s", name)
|
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 {
|
func bitsToBytes(bits int) int {
|
||||||
return int(math.Ceil(float64(bits) / 8.0))
|
return int(math.Ceil(float64(bits) / 8.0))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,8 +149,8 @@ func TestGenerate(test *testing.T) {
|
|||||||
Name: "Connect",
|
Name: "Connect",
|
||||||
Type: TypeTableDefined {
|
Type: TypeTableDefined {
|
||||||
Fields: map[uint16] Field {
|
Fields: map[uint16] Field {
|
||||||
0x0000: Field { Name: "Name", Type: TypeNamed { Name: "String" } },
|
0x0000: Field { Name: "Name", Type: TypeString { } },
|
||||||
0x0001: Field { Name: "Password", Type: TypeNamed { Name: "String" } },
|
0x0001: Field { Name: "Password", Type: TypeString { } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -164,9 +164,9 @@ func TestGenerate(test *testing.T) {
|
|||||||
}
|
}
|
||||||
protocol.Types["User"] = TypeTableDefined {
|
protocol.Types["User"] = TypeTableDefined {
|
||||||
Fields: map[uint16] Field {
|
Fields: map[uint16] Field {
|
||||||
0x0000: Field { Name: "Name", Type: TypeNamed { Name: "String" } },
|
0x0000: Field { Name: "Name", Type: TypeString { } },
|
||||||
0x0001: Field { Name: "Bio", Type: TypeNamed { Name: "String" } },
|
0x0001: Field { Name: "Bio", Type: TypeString { } },
|
||||||
0x0002: Field { Name: "Followers", Type: TypeNamed { Name: "U32" } },
|
0x0002: Field { Name: "Followers", Type: TypeInt { Bits: 32 } },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,8 +201,8 @@ func TestGenerateRun(test *testing.T) {
|
|||||||
Name: "Connect",
|
Name: "Connect",
|
||||||
Type: TypeTableDefined {
|
Type: TypeTableDefined {
|
||||||
Fields: map[uint16] Field {
|
Fields: map[uint16] Field {
|
||||||
0x0000: Field { Name: "Name", Type: TypeNamed { Name: "String" } },
|
0x0000: Field { Name: "Name", Type: TypeString { } },
|
||||||
0x0001: Field { Name: "Password", Type: TypeNamed { Name: "String" } },
|
0x0001: Field { Name: "Password", Type: TypeString { } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -214,23 +214,173 @@ func TestGenerateRun(test *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
protocol.Messages[0x0002] = Message {
|
||||||
|
Name: "Pulse",
|
||||||
|
Type: TypeTableDefined {
|
||||||
|
Fields: map[uint16] Field {
|
||||||
|
0x0000: Field { Name: "Index", Type: TypeInt { Bits: 5 } },
|
||||||
|
0x0001: Field { Name: "Offset", Type: TypeInt { Bits: 16, Signed: true }},
|
||||||
|
0x0002: Field { Name: "X", Type: TypeFloat { Bits: 16 }},
|
||||||
|
0x0003: Field { Name: "Y", Type: TypeFloat { Bits: 32 }},
|
||||||
|
0x0004: Field { Name: "Z", Type: TypeFloat { Bits: 64 }},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
protocol.Messages[0x0003] = Message {
|
||||||
|
Name: "NestedArray",
|
||||||
|
Type: TypeArray { Element: TypeArray { Element: TypeInt { Bits: 8 } } },
|
||||||
|
}
|
||||||
|
protocol.Messages[0x0004] = Message {
|
||||||
|
Name: "Integers",
|
||||||
|
Type: TypeTableDefined {
|
||||||
|
Fields: map[uint16] Field {
|
||||||
|
0x0000: Field { Name: "U5", Type: TypeInt { Bits: 5 } },
|
||||||
|
0x0001: Field { Name: "U8", Type: TypeInt { Bits: 8 } },
|
||||||
|
0x0002: Field { Name: "U16", Type: TypeInt { Bits: 16 } },
|
||||||
|
0x0003: Field { Name: "U32", Type: TypeInt { Bits: 32 } },
|
||||||
|
0x0004: Field { Name: "U64", Type: TypeInt { Bits: 64 } },
|
||||||
|
0x0006: Field { Name: "I8", Type: TypeInt { Bits: 8, Signed: true } },
|
||||||
|
0x0007: Field { Name: "I16", Type: TypeInt { Bits: 16, Signed: true } },
|
||||||
|
0x0008: Field { Name: "I32", Type: TypeInt { Bits: 32, Signed: true } },
|
||||||
|
0x0009: Field { Name: "I64", Type: TypeInt { Bits: 64, Signed: true } },
|
||||||
|
0x000B: Field { Name: "NI8", Type: TypeInt { Bits: 8, Signed: true } },
|
||||||
|
0x000C: Field { Name: "NI16",Type: TypeInt { Bits: 16, Signed: true } },
|
||||||
|
0x000D: Field { Name: "NI32",Type: TypeInt { Bits: 32, Signed: true } },
|
||||||
|
0x000E: Field { Name: "NI64",Type: TypeInt { Bits: 64, Signed: true } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
protocol.Types["User"] = TypeTableDefined {
|
protocol.Types["User"] = TypeTableDefined {
|
||||||
Fields: map[uint16] Field {
|
Fields: map[uint16] Field {
|
||||||
0x0000: Field { Name: "Name", Type: TypeNamed { Name: "String" } },
|
0x0000: Field { Name: "Name", Type: TypeString { } },
|
||||||
0x0001: Field { Name: "Bio", Type: TypeNamed { Name: "String" } },
|
0x0001: Field { Name: "Bio", Type: TypeString { } },
|
||||||
0x0002: Field { Name: "Followers", Type: TypeNamed { Name: "U32" } },
|
0x0002: Field { Name: "Followers", Type: TypeInt { Bits: 32 } },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
testGenerateRun(test, &protocol, `
|
testGenerateRun(test, &protocol, `
|
||||||
// imports
|
// imports
|
||||||
`, `
|
`, `
|
||||||
// test case
|
// test case
|
||||||
|
log.Println("MessageConnect")
|
||||||
messageConnect := MessageConnect {
|
messageConnect := MessageConnect {
|
||||||
Name: "rarity",
|
Name: "rarity",
|
||||||
Password: "gems",
|
Password: "gems",
|
||||||
}
|
}
|
||||||
testEncode(
|
testEncode(
|
||||||
messageConnect,
|
&messageConnect,
|
||||||
0x0) // TODO
|
tu.S(0xE1, 0x02).AddVar(
|
||||||
|
[]byte { 0x00, 0x00, 0x86, 'r', 'a', 'r', 'i', 't', 'y' },
|
||||||
|
[]byte { 0x00, 0x01, 0x84, 'g', 'e', 'm', 's' },
|
||||||
|
))
|
||||||
|
log.Println("MessageUserList")
|
||||||
|
messageUserList := MessageUserList {
|
||||||
|
Users: []User {
|
||||||
|
User {
|
||||||
|
Name: "rarity",
|
||||||
|
Bio: "asdjads",
|
||||||
|
Followers: 0x324,
|
||||||
|
},
|
||||||
|
User {
|
||||||
|
Name: "deez nuts",
|
||||||
|
Bio: "logy",
|
||||||
|
Followers: 0x8000,
|
||||||
|
},
|
||||||
|
User {
|
||||||
|
Name: "creekflow",
|
||||||
|
Bio: "im creekflow",
|
||||||
|
Followers: 0x3894,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testEncode(
|
||||||
|
&messageUserList,
|
||||||
|
tu.S(0xE1, 0x01, 0x00, 0x00,
|
||||||
|
0xC1, 0x03, 0xE1,
|
||||||
|
).Add(0x03).AddVar(
|
||||||
|
[]byte { 0x00, 0x00, 0x86, 'r', 'a', 'r', 'i', 't', 'y' },
|
||||||
|
[]byte { 0x00, 0x01, 0x87, 'a', 's', 'd', 'j', 'a', 'd', 's' },
|
||||||
|
[]byte { 0x00, 0x02, 0x23, 0x00, 0x00, 0x03, 0x24 },
|
||||||
|
).Add(0x03).AddVar(
|
||||||
|
[]byte { 0x00, 0x00, 0x89, 'd', 'e', 'e', 'z', ' ', 'n', 'u', 't', 's' },
|
||||||
|
[]byte { 0x00, 0x01, 0x84, 'l', 'o', 'g', 'y' },
|
||||||
|
[]byte { 0x00, 0x02, 0x23, 0x00, 0x00, 0x80, 0x00 },
|
||||||
|
).Add(0x03).AddVar(
|
||||||
|
[]byte { 0x00, 0x00, 0x89, 'c', 'r', 'e', 'e', 'k', 'f', 'l', 'o', 'w' },
|
||||||
|
[]byte { 0x00, 0x01, 0x8C, 'i', 'm', ' ', 'c', 'r', 'e', 'e', 'k', 'f',
|
||||||
|
'l', 'o', 'w' },
|
||||||
|
[]byte { 0x00, 0x02, 0x23, 0x00, 0x00, 0x38, 0x94 },
|
||||||
|
))
|
||||||
|
log.Println("MessagePulse")
|
||||||
|
messagePulse := MessagePulse {
|
||||||
|
Index: 9,
|
||||||
|
Offset: -0x3521,
|
||||||
|
X: 45.389,
|
||||||
|
Y: 294.1,
|
||||||
|
Z: 384729384.234892034,
|
||||||
|
}
|
||||||
|
testEncode(
|
||||||
|
&messagePulse,
|
||||||
|
tu.S(0xE1, 0x05).AddVar(
|
||||||
|
[]byte { 0x00, 0x00, 0x09 },
|
||||||
|
[]byte { 0x00, 0x01, 0x41, 0xCA, 0xDF },
|
||||||
|
[]byte { 0x00, 0x02, 0x61, 0x51, 0xAC },
|
||||||
|
[]byte { 0x00, 0x03, 0x63, 0x43, 0x93, 0x0C, 0xCD },
|
||||||
|
[]byte { 0x00, 0x04, 0x67, 0x41, 0xB6, 0xEE, 0x81, 0x28, 0x3C, 0x21, 0xE2 },
|
||||||
|
))
|
||||||
|
log.Println("MessageNestedArray")
|
||||||
|
uint8s := func(n int) []uint8 {
|
||||||
|
array := make([]uint8, n)
|
||||||
|
for index := range array {
|
||||||
|
array[index] = uint8(index + 1) | 0xF0
|
||||||
|
}
|
||||||
|
return array
|
||||||
|
}
|
||||||
|
messageNestedArray := MessageNestedArray {
|
||||||
|
uint8s(6),
|
||||||
|
uint8s(35),
|
||||||
|
}
|
||||||
|
testEncode(
|
||||||
|
&messageNestedArray,
|
||||||
|
tu.S(0xC1, 0x02, 0xC1,
|
||||||
|
0x06, 0x20, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
|
||||||
|
35, 0x20, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
|
||||||
|
0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC,
|
||||||
|
0xFD, 0xFE, 0xFF, 0xF0, 0xF1, 0xF2,
|
||||||
|
0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
|
||||||
|
0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE,
|
||||||
|
0xFF, 0xF0, 0xF1, 0xF2, 0xF3))
|
||||||
|
log.Println("MessageIntegers")
|
||||||
|
messageIntegers := MessageIntegers {
|
||||||
|
U5: 0x13,
|
||||||
|
U8: 0xC9,
|
||||||
|
U16: 0x34C9,
|
||||||
|
U32: 0x10E134C9,
|
||||||
|
U64: 0x639109BC10E134C9,
|
||||||
|
I8: 0x35,
|
||||||
|
I16: 0x34C9,
|
||||||
|
I32: 0x10E134C9,
|
||||||
|
I64: 0x639109BC10E134C9,
|
||||||
|
NI8: -0x35,
|
||||||
|
NI16: -0x34C9,
|
||||||
|
NI32: -0x10E134C9,
|
||||||
|
NI64: -0x639109BC10E134C9,
|
||||||
|
}
|
||||||
|
testEncode(
|
||||||
|
&messageIntegers,
|
||||||
|
tu.S(0xE1, 13).AddVar(
|
||||||
|
[]byte { 0x00, 0x00, 0x13 },
|
||||||
|
[]byte { 0x00, 0x01, 0x20, 0xC9 },
|
||||||
|
[]byte { 0x00, 0x02, 0x21, 0x34, 0xC9 },
|
||||||
|
[]byte { 0x00, 0x03, 0x23, 0x10, 0xE1, 0x34, 0xC9 },
|
||||||
|
[]byte { 0x00, 0x04, 0x27, 0x63, 0x91, 0x09, 0xBC, 0x10, 0xE1, 0x34, 0xC9 },
|
||||||
|
[]byte { 0x00, 0x06, 0x40, 0x35 },
|
||||||
|
[]byte { 0x00, 0x07, 0x41, 0x34, 0xC9 },
|
||||||
|
[]byte { 0x00, 0x08, 0x43, 0x10, 0xE1, 0x34, 0xC9 },
|
||||||
|
[]byte { 0x00, 0x09, 0x47, 0x63, 0x91, 0x09, 0xBC, 0x10, 0xE1, 0x34, 0xC9 },
|
||||||
|
[]byte { 0x00, 0x0B, 0x40, 0xCB },
|
||||||
|
[]byte { 0x00, 0x0C, 0x41, 0xCB, 0x37 },
|
||||||
|
[]byte { 0x00, 0x0D, 0x43, 0xEF, 0x1E, 0xCB, 0x37 },
|
||||||
|
[]byte { 0x00, 0x0E, 0x47, 0x9C, 0x6E, 0xF6, 0x43, 0xEF, 0x1E, 0xCB, 0x37 },
|
||||||
|
))
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,23 +34,26 @@ func testGenerateRun(test *testing.T, protocol *Protocol, imports string, testCa
|
|||||||
imports = `
|
imports = `
|
||||||
import "log"
|
import "log"
|
||||||
import "bytes"
|
import "bytes"
|
||||||
import "slices"
|
|
||||||
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
||||||
|
import tu "git.tebibyte.media/sashakoshka/hopp/internal/testutil"
|
||||||
` + imports
|
` + imports
|
||||||
setup := `log.Println("*** BEGIN TEST CASE OUTPUT ***")`
|
setup := `log.Println("*** BEGIN TEST CASE OUTPUT ***")`
|
||||||
teardown := `log.Println("--- END TEST CASE OUTPUT ---")`
|
teardown := `log.Println("--- END TEST CASE OUTPUT ---")`
|
||||||
static := `
|
static := `
|
||||||
func testEncode(message Message, correct ...byte) {
|
func testEncode(message Message, correct tu.Snake) {
|
||||||
buffer := bytes.Buffer { }
|
buffer := bytes.Buffer { }
|
||||||
encoder := tape.NewEncoder(&buffer)
|
encoder := tape.NewEncoder(&buffer)
|
||||||
n, err := message.Encode(encoder)
|
n, err := message.Encode(encoder)
|
||||||
if err != nil { log.Fatalf("at %d: %v\n", n, err) }
|
if err != nil { log.Fatalf("at %d: %v\n", n, err) }
|
||||||
|
encoder.Flush()
|
||||||
got := buffer.Bytes()
|
got := buffer.Bytes()
|
||||||
|
log.Printf("got: [%s]", tu.HexBytes(got))
|
||||||
|
log.Println("correct:", correct)
|
||||||
if n != len(got) {
|
if n != len(got) {
|
||||||
log.Fatalln("len incorrect: %d != %d", got, correct)
|
log.Fatalf("n incorrect: %d != %d\n", n, len(got))
|
||||||
}
|
}
|
||||||
if !slices.Equal(got, correct) {
|
if ok, n := correct.Check(got); !ok {
|
||||||
log.Fatalln("not equal:")
|
log.Fatalln("not equal at", n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -95,6 +95,28 @@ func (this *parser) parseType() (Type, error) {
|
|||||||
|
|
||||||
switch this.Kind() {
|
switch this.Kind() {
|
||||||
case TokenIdent:
|
case TokenIdent:
|
||||||
|
switch this.Value() {
|
||||||
|
case "U8": return TypeInt { Bits: 8 }, this.Next()
|
||||||
|
case "U16": return TypeInt { Bits: 16 }, this.Next()
|
||||||
|
case "U32": return TypeInt { Bits: 32 }, this.Next()
|
||||||
|
case "U64": return TypeInt { Bits: 64 }, this.Next()
|
||||||
|
case "U128": return TypeInt { Bits: 128 }, this.Next()
|
||||||
|
case "U256": return TypeInt { Bits: 256 }, this.Next()
|
||||||
|
case "I8": return TypeInt { Bits: 8, Signed: true }, this.Next()
|
||||||
|
case "I16": return TypeInt { Bits: 16, Signed: true }, this.Next()
|
||||||
|
case "I32": return TypeInt { Bits: 32, Signed: true }, this.Next()
|
||||||
|
case "I64": return TypeInt { Bits: 64, Signed: true }, this.Next()
|
||||||
|
case "I128": return TypeInt { Bits: 128, Signed: true }, this.Next()
|
||||||
|
case "I256": return TypeInt { Bits: 256, Signed: true }, this.Next()
|
||||||
|
case "F16": return TypeFloat { Bits: 16 }, this.Next()
|
||||||
|
case "F32": return TypeFloat { Bits: 32 }, this.Next()
|
||||||
|
case "F64": return TypeFloat { Bits: 64 }, this.Next()
|
||||||
|
case "F128": return TypeFloat { Bits: 128 }, this.Next()
|
||||||
|
case "F256": return TypeFloat { Bits: 256 }, this.Next()
|
||||||
|
case "String": return TypeString { }, this.Next()
|
||||||
|
case "Buffer": return TypeBuffer { }, this.Next()
|
||||||
|
case "Table": return TypeTable { }, this.Next()
|
||||||
|
}
|
||||||
return this.parseTypeNamed()
|
return this.parseTypeNamed()
|
||||||
case TokenLBracket:
|
case TokenLBracket:
|
||||||
return this.parseTypeArray()
|
return this.parseTypeArray()
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ func TestParse(test *testing.T) {
|
|||||||
Name: "Connect",
|
Name: "Connect",
|
||||||
Type: TypeTableDefined {
|
Type: TypeTableDefined {
|
||||||
Fields: map[uint16] Field {
|
Fields: map[uint16] Field {
|
||||||
0x0000: Field { Name: "Name", Type: TypeNamed { Name: "String" } },
|
0x0000: Field { Name: "Name", Type: TypeString { } },
|
||||||
0x0001: Field { Name: "Password", Type: TypeNamed { Name: "String" } },
|
0x0001: Field { Name: "Password", Type: TypeString { } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -26,9 +26,9 @@ func TestParse(test *testing.T) {
|
|||||||
}
|
}
|
||||||
correct.Types["User"] = TypeTableDefined {
|
correct.Types["User"] = TypeTableDefined {
|
||||||
Fields: map[uint16] Field {
|
Fields: map[uint16] Field {
|
||||||
0x0000: Field { Name: "Name", Type: TypeNamed { Name: "String" } },
|
0x0000: Field { Name: "Name", Type: TypeString { } },
|
||||||
0x0001: Field { Name: "Bio", Type: TypeNamed { Name: "String" } },
|
0x0001: Field { Name: "Bio", Type: TypeString { } },
|
||||||
0x0002: Field { Name: "Followers", Type: TypeNamed { Name: "U32" } },
|
0x0002: Field { Name: "Followers", Type: TypeInt { Bits: 32 } },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
test.Log("CORRECT:", &correct)
|
test.Log("CORRECT:", &correct)
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
package generate
|
package generate
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
import "maps"
|
||||||
|
import "slices"
|
||||||
|
import "crypto/md5"
|
||||||
|
|
||||||
type Protocol struct {
|
type Protocol struct {
|
||||||
Messages map[uint16] Message
|
Messages map[uint16] Message
|
||||||
Types map[string] Type
|
Types map[string] Type
|
||||||
@@ -11,7 +16,7 @@ type Message struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Type interface {
|
type Type interface {
|
||||||
|
fmt.Stringer
|
||||||
}
|
}
|
||||||
|
|
||||||
type TypeInt struct {
|
type TypeInt struct {
|
||||||
@@ -19,29 +24,84 @@ type TypeInt struct {
|
|||||||
Signed bool
|
Signed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (typ TypeInt) String() string {
|
||||||
|
output := ""
|
||||||
|
if typ.Signed {
|
||||||
|
output += "I"
|
||||||
|
} else {
|
||||||
|
output += "U"
|
||||||
|
}
|
||||||
|
output += fmt.Sprint(typ.Bits)
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
type TypeFloat struct {
|
type TypeFloat struct {
|
||||||
Bits int
|
Bits int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (typ TypeFloat) String() string {
|
||||||
|
return fmt.Sprintf("F%d", typ.Bits)
|
||||||
|
}
|
||||||
|
|
||||||
type TypeString struct { }
|
type TypeString struct { }
|
||||||
|
|
||||||
|
func (TypeString) String() string {
|
||||||
|
return "String"
|
||||||
|
}
|
||||||
|
|
||||||
type TypeBuffer struct { }
|
type TypeBuffer struct { }
|
||||||
|
|
||||||
|
func (TypeBuffer) String() string {
|
||||||
|
return "Buffer"
|
||||||
|
}
|
||||||
|
|
||||||
type TypeArray struct {
|
type TypeArray struct {
|
||||||
Element Type
|
Element Type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (typ TypeArray) String() string {
|
||||||
|
return fmt.Sprintf("[]%v", typ.Element)
|
||||||
|
}
|
||||||
|
|
||||||
type TypeTable struct { }
|
type TypeTable struct { }
|
||||||
|
|
||||||
|
func (TypeTable) String() string {
|
||||||
|
return "Table"
|
||||||
|
}
|
||||||
|
|
||||||
type TypeTableDefined struct {
|
type TypeTableDefined struct {
|
||||||
Fields map[uint16] Field
|
Fields map[uint16] Field
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (typ TypeTableDefined) String() string {
|
||||||
|
output := "{"
|
||||||
|
for _, key := range slices.Sorted(maps.Keys(typ.Fields)) {
|
||||||
|
output += fmt.Sprintf("%04X %v", key, typ.Fields[key])
|
||||||
|
}
|
||||||
|
output += "}"
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
type Field struct {
|
type Field struct {
|
||||||
Name string
|
Name string
|
||||||
Type Type
|
Type Type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (field Field) String() string {
|
||||||
|
return fmt.Sprintf("%s %v", field.Name, field.Type)
|
||||||
|
}
|
||||||
|
|
||||||
type TypeNamed struct {
|
type TypeNamed struct {
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (typ TypeNamed) String() string {
|
||||||
|
return typ.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func HashType(typ Type) [16]byte {
|
||||||
|
// TODO: if we ever want to make the compiler more efficient, this would
|
||||||
|
// be a good place to start, complex string concatenation in a hot path
|
||||||
|
// (sorta)
|
||||||
|
return md5.Sum([]byte(typ.String()))
|
||||||
|
}
|
||||||
|
|||||||
@@ -112,6 +112,13 @@ func (this *Decoder) ReadUintN(bytes int) (value uint64, n int, err error) {
|
|||||||
return value, n, nil
|
return value, n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadFloat16 decodes a 16-bit floating point value from the input reader.
|
||||||
|
func (this *Decoder) ReadFloat16() (value float32, n int, err error) {
|
||||||
|
bits, nn, err := this.ReadUint16()
|
||||||
|
n += nn; if err != nil { return 0, n, err }
|
||||||
|
return math.Float32frombits(f16bitsToF32bits(bits)), n, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ReadFloat32 decldes a 32-bit floating point value from the input reader.
|
// ReadFloat32 decldes a 32-bit floating point value from the input reader.
|
||||||
func (this *Decoder) ReadFloat32() (value float32, n int, err error) {
|
func (this *Decoder) ReadFloat32() (value float32, n int, err error) {
|
||||||
bits, nn, err := this.ReadUint32()
|
bits, nn, err := this.ReadUint32()
|
||||||
@@ -132,3 +139,54 @@ func (this *Decoder) ReadTag() (value Tag, n int, err error) {
|
|||||||
n += nn; if err != nil { return 0, n, err }
|
n += nn; if err != nil { return 0, n, err }
|
||||||
return Tag(uncasted), n, nil
|
return Tag(uncasted), n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// f16bitsToF32bits returns uint32 (float32 bits) converted from specified uint16.
|
||||||
|
// Taken from https://github.com/x448/float16/blob/v0.8.4/float16
|
||||||
|
//
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2019 Montgomery Edwards⁴⁴⁸ and Faye Amacker
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
func f16bitsToF32bits(in uint16) uint32 {
|
||||||
|
// All 65536 conversions with this were confirmed to be correct
|
||||||
|
// by Montgomery Edwards⁴⁴⁸ (github.com/x448).
|
||||||
|
|
||||||
|
sign := uint32(in&0x8000) << 16 // sign for 32-bit
|
||||||
|
exp := uint32(in&0x7c00) >> 10 // exponenent for 16-bit
|
||||||
|
coef := uint32(in&0x03ff) << 13 // significand for 32-bit
|
||||||
|
|
||||||
|
if exp == 0x1f {
|
||||||
|
if coef == 0 {
|
||||||
|
// infinity
|
||||||
|
return sign | 0x7f800000 | coef
|
||||||
|
}
|
||||||
|
// NaN
|
||||||
|
return sign | 0x7fc00000 | coef
|
||||||
|
}
|
||||||
|
|
||||||
|
if exp == 0 {
|
||||||
|
if coef == 0 {
|
||||||
|
// zero
|
||||||
|
return sign
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize subnormal numbers
|
||||||
|
exp++
|
||||||
|
for coef&0x7f800000 == 0 {
|
||||||
|
coef <<= 1
|
||||||
|
exp--
|
||||||
|
}
|
||||||
|
coef &= 0x007fffff
|
||||||
|
}
|
||||||
|
|
||||||
|
return sign | ((exp + (0x7f - 0xf)) << 23) | coef
|
||||||
|
}
|
||||||
|
|||||||
@@ -99,6 +99,10 @@ func decodeAny(decoder *Decoder, destination reflect.Value, tag Tag) (n int, err
|
|||||||
if err != nil { return n, err }
|
if err != nil { return n, err }
|
||||||
case LI:
|
case LI:
|
||||||
// LI: <value: IntN>
|
// LI: <value: IntN>
|
||||||
|
nn, err := decodeAndSetUint(decoder, destination, tag.CN() + 1)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
case LSI:
|
||||||
|
// LSI: <value: IntN>
|
||||||
nn, err := decodeAndSetInt(decoder, destination, tag.CN() + 1)
|
nn, err := decodeAndSetInt(decoder, destination, tag.CN() + 1)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
case FP:
|
case FP:
|
||||||
@@ -171,13 +175,18 @@ func TagAny(value any) (Tag, error) {
|
|||||||
// TODO use reflection for all of this to ignore type names
|
// TODO use reflection for all of this to ignore type names
|
||||||
// primitives
|
// primitives
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case int, uint: return LI.WithCN(3), nil
|
case int: return LSI.WithCN(3), nil
|
||||||
case int8, uint8: return LI.WithCN(0), nil
|
case int8: return LSI.WithCN(0), nil
|
||||||
case int16, uint16: return LI.WithCN(1), nil
|
case int16: return LSI.WithCN(1), nil
|
||||||
case int32, uint32: return LI.WithCN(3), nil
|
case int32: return LSI.WithCN(3), nil
|
||||||
case int64, uint64: return LI.WithCN(7), nil
|
case int64: return LSI.WithCN(7), nil
|
||||||
case string: return bufferLenTag(len(value)), nil
|
case uint: return LI.WithCN(3), nil
|
||||||
case []byte: return bufferLenTag(len(value)), nil
|
case uint8: return LI.WithCN(0), nil
|
||||||
|
case uint16: return LI.WithCN(1), nil
|
||||||
|
case uint32: return LI.WithCN(3), nil
|
||||||
|
case uint64: return LI.WithCN(7), nil
|
||||||
|
case string: return bufferLenTag(len(value)), nil
|
||||||
|
case []byte: return bufferLenTag(len(value)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// aggregates
|
// aggregates
|
||||||
@@ -241,7 +250,7 @@ func encodeAnyMap(encoder *Encoder, value any, tag Tag) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setInt expects a settable destination.
|
// setInt expects a settable destination.
|
||||||
func setInt(destination reflect.Value, value uint64) error {
|
func setInt[T int64 | uint64](destination reflect.Value, value T) error {
|
||||||
switch {
|
switch {
|
||||||
case destination.CanInt():
|
case destination.CanInt():
|
||||||
destination.Set(reflect.ValueOf(int64(value)).Convert(destination.Type()))
|
destination.Set(reflect.ValueOf(int64(value)).Convert(destination.Type()))
|
||||||
@@ -277,6 +286,13 @@ func setByteArray(destination reflect.Value, value []byte) error {
|
|||||||
|
|
||||||
// decodeAndSetInt expects a settable destination.
|
// decodeAndSetInt expects a settable destination.
|
||||||
func decodeAndSetInt(decoder *Decoder, destination reflect.Value, bytes int) (n int, err error) {
|
func decodeAndSetInt(decoder *Decoder, destination reflect.Value, bytes int) (n int, err error) {
|
||||||
|
value, nn, err := decoder.ReadIntN(bytes)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
return n, setInt(destination, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeAndSetUint expects a settable destination.
|
||||||
|
func decodeAndSetUint(decoder *Decoder, destination reflect.Value, bytes int) (n int, err error) {
|
||||||
value, nn, err := decoder.ReadUintN(bytes)
|
value, nn, err := decoder.ReadUintN(bytes)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
return n, setInt(destination, value)
|
return n, setInt(destination, value)
|
||||||
@@ -319,6 +335,14 @@ func typeOf(decoder *Decoder, tag Tag) (reflect.Type, error) {
|
|||||||
case 7: return reflect.TypeOf(uint64(0)), nil
|
case 7: return reflect.TypeOf(uint64(0)), nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("unknown CN %d for LI", tag.CN())
|
return nil, fmt.Errorf("unknown CN %d for LI", tag.CN())
|
||||||
|
case LSI:
|
||||||
|
switch tag.CN() {
|
||||||
|
case 0: return reflect.TypeOf(int8(0)), nil
|
||||||
|
case 1: return reflect.TypeOf(int16(0)), nil
|
||||||
|
case 3: return reflect.TypeOf(int32(0)), nil
|
||||||
|
case 7: return reflect.TypeOf(int64(0)), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unknown CN %d for LSI", tag.CN())
|
||||||
case FP:
|
case FP:
|
||||||
switch tag.CN() {
|
switch tag.CN() {
|
||||||
case 3: return reflect.TypeOf(float32(0)), nil
|
case 3: return reflect.TypeOf(float32(0)), nil
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import tu "git.tebibyte.media/sashakoshka/hopp/internal/testutil"
|
|||||||
func TestEncodeAnyInt(test *testing.T) {
|
func TestEncodeAnyInt(test *testing.T) {
|
||||||
err := testEncodeAny(test, uint8(0xCA), LI.WithCN(0), tu.S(0xCA))
|
err := testEncodeAny(test, uint8(0xCA), LI.WithCN(0), tu.S(0xCA))
|
||||||
if err != nil { test.Fatal(err) }
|
if err != nil { test.Fatal(err) }
|
||||||
err = testEncodeAny(test, 400, LI.WithCN(3), tu.S(
|
err = testEncodeAny(test, 400, LSI.WithCN(3), tu.S(
|
||||||
0, 0, 0x1, 0x90,
|
0, 0, 0x1, 0x90,
|
||||||
))
|
))
|
||||||
if err != nil { test.Fatal(err) }
|
if err != nil { test.Fatal(err) }
|
||||||
@@ -22,15 +22,16 @@ func TestEncodeAnyTable(test *testing.T) {
|
|||||||
0x0000: "hi!",
|
0x0000: "hi!",
|
||||||
0xFFFF: []uint16 { 0xBEE5, 0x7777 },
|
0xFFFF: []uint16 { 0xBEE5, 0x7777 },
|
||||||
0x1234: [][]uint16 { []uint16 { 0x5 }, []uint16 { 0x17, 0xAAAA} },
|
0x1234: [][]uint16 { []uint16 { 0x5 }, []uint16 { 0x17, 0xAAAA} },
|
||||||
}, KTV.WithCN(0), tu.S(5).AddVar(
|
0x2345: [][]int16 { []int16 { 0x5 }, []int16 { 0x17, -0xAAA } },
|
||||||
|
}, KTV.WithCN(0), tu.S(6).AddVar(
|
||||||
[]byte {
|
[]byte {
|
||||||
0xF3, 0xB9,
|
0xF3, 0xB9,
|
||||||
byte(LI.WithCN(3)),
|
byte(LSI.WithCN(3)),
|
||||||
0, 0, 0, 1,
|
0, 0, 0, 1,
|
||||||
},
|
},
|
||||||
[]byte {
|
[]byte {
|
||||||
0x01, 0x02,
|
0x01, 0x02,
|
||||||
byte(LI.WithCN(3)),
|
byte(LSI.WithCN(3)),
|
||||||
0, 0, 0, 2,
|
0, 0, 0, 2,
|
||||||
},
|
},
|
||||||
[]byte {
|
[]byte {
|
||||||
@@ -52,6 +53,15 @@ func TestEncodeAnyTable(test *testing.T) {
|
|||||||
0, 0x17,
|
0, 0x17,
|
||||||
0xAA, 0xAA,
|
0xAA, 0xAA,
|
||||||
},
|
},
|
||||||
|
[]byte {
|
||||||
|
0x23, 0x45,
|
||||||
|
byte(OTA.WithCN(0)), 2, byte(OTA.WithCN(0)),
|
||||||
|
1, byte(LSI.WithCN(1)),
|
||||||
|
0, 0x5,
|
||||||
|
2, byte(LSI.WithCN(1)),
|
||||||
|
0, 0x17,
|
||||||
|
0xF5, 0x56,
|
||||||
|
},
|
||||||
))
|
))
|
||||||
if err != nil { test.Fatal(err) }
|
if err != nil { test.Fatal(err) }
|
||||||
}
|
}
|
||||||
@@ -60,6 +70,8 @@ func TestEncodeDecodeAnyTable(test *testing.T) {
|
|||||||
err := testEncodeDecodeAny(test, map[uint16] any {
|
err := testEncodeDecodeAny(test, map[uint16] any {
|
||||||
0xF3B9: uint32(1),
|
0xF3B9: uint32(1),
|
||||||
0x0102: uint32(2),
|
0x0102: uint32(2),
|
||||||
|
0x0103: int64(23432),
|
||||||
|
0x0104: int64(-88777),
|
||||||
0x0000: []byte("hi!"),
|
0x0000: []byte("hi!"),
|
||||||
0xFFFF: []uint16 { 0xBEE5, 0x7777 },
|
0xFFFF: []uint16 { 0xBEE5, 0x7777 },
|
||||||
0x1234: [][]uint16 { []uint16 { 0x5 }, []uint16 { 0x17, 0xAAAA} },
|
0x1234: [][]uint16 { []uint16 { 0x5 }, []uint16 { 0x17, 0xAAAA} },
|
||||||
|
|||||||
@@ -102,6 +102,11 @@ func (this *Encoder) WriteUintN(value uint64, bytes int) (n int, err error) {
|
|||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteFloat16 encodes a 16-bit floating point value to the output writer.
|
||||||
|
func (this *Encoder) WriteFloat16(value float32) (n int, err error) {
|
||||||
|
return this.WriteUint16(f32bitsToF16bits(math.Float32bits(value)))
|
||||||
|
}
|
||||||
|
|
||||||
// WriteFloat32 encodes a 32-bit floating point value to the output writer.
|
// WriteFloat32 encodes a 32-bit floating point value to the output writer.
|
||||||
func (this *Encoder) WriteFloat32(value float32) (n int, err error) {
|
func (this *Encoder) WriteFloat32(value float32) (n int, err error) {
|
||||||
return this.WriteUint32(math.Float32bits(value))
|
return this.WriteUint32(math.Float32bits(value))
|
||||||
@@ -116,3 +121,69 @@ func (this *Encoder) WriteFloat64(value float64) (n int, err error) {
|
|||||||
func (this *Encoder) WriteTag(value Tag) (n int, err error) {
|
func (this *Encoder) WriteTag(value Tag) (n int, err error) {
|
||||||
return this.WriteUint8(uint8(value))
|
return this.WriteUint8(uint8(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// f32bitsToF16bits returns uint16 (Float16 bits) converted from the specified float32.
|
||||||
|
// Conversion rounds to nearest integer with ties to even.
|
||||||
|
// Taken from https://github.com/x448/float16/blob/v0.8.4/float16
|
||||||
|
//
|
||||||
|
// MIT License
|
||||||
|
//
|
||||||
|
// Copyright (c) 2019 Montgomery Edwards⁴⁴⁸ and Faye Amacker
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
func f32bitsToF16bits(u32 uint32) uint16 {
|
||||||
|
// Translated from Rust to Go by Montgomery Edwards⁴⁴⁸ (github.com/x448).
|
||||||
|
// All 4294967296 conversions with this were confirmed to be correct by x448.
|
||||||
|
// Original Rust implementation is by Kathryn Long (github.com/starkat99) with MIT license.
|
||||||
|
|
||||||
|
sign := u32 & 0x80000000
|
||||||
|
exp := u32 & 0x7f800000
|
||||||
|
coef := u32 & 0x007fffff
|
||||||
|
|
||||||
|
if exp == 0x7f800000 {
|
||||||
|
// NaN or Infinity
|
||||||
|
nanBit := uint32(0)
|
||||||
|
if coef != 0 {
|
||||||
|
nanBit = uint32(0x0200)
|
||||||
|
}
|
||||||
|
return uint16((sign >> 16) | uint32(0x7c00) | nanBit | (coef >> 13))
|
||||||
|
}
|
||||||
|
|
||||||
|
halfSign := sign >> 16
|
||||||
|
|
||||||
|
unbiasedExp := int32(exp>>23) - 127
|
||||||
|
halfExp := unbiasedExp + 15
|
||||||
|
|
||||||
|
if halfExp >= 0x1f {
|
||||||
|
return uint16(halfSign | uint32(0x7c00))
|
||||||
|
}
|
||||||
|
|
||||||
|
if halfExp <= 0 {
|
||||||
|
if 14-halfExp > 24 {
|
||||||
|
return uint16(halfSign)
|
||||||
|
}
|
||||||
|
coef := coef | uint32(0x00800000)
|
||||||
|
halfCoef := coef >> uint32(14-halfExp)
|
||||||
|
roundBit := uint32(1) << uint32(13-halfExp)
|
||||||
|
if (coef&roundBit) != 0 && (coef&(3*roundBit-1)) != 0 {
|
||||||
|
halfCoef++
|
||||||
|
}
|
||||||
|
return uint16(halfSign | halfCoef)
|
||||||
|
}
|
||||||
|
|
||||||
|
uHalfExp := uint32(halfExp) << 10
|
||||||
|
halfCoef := coef >> 13
|
||||||
|
roundBit := uint32(0x00001000)
|
||||||
|
if (coef&roundBit) != 0 && (coef&(3*roundBit-1)) != 0 {
|
||||||
|
return uint16((halfSign | uHalfExp | halfCoef) + 1)
|
||||||
|
}
|
||||||
|
return uint16(halfSign | uHalfExp | halfCoef)
|
||||||
|
}
|
||||||
|
|||||||
21
tape/tag.go
21
tape/tag.go
@@ -2,14 +2,17 @@ package tape
|
|||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
|
// TODO: fix #7
|
||||||
|
|
||||||
type Tag byte; const (
|
type Tag byte; const (
|
||||||
SI Tag = 0 << 5 // Small integer
|
SI Tag = 0 << 5 // Small integer
|
||||||
LI Tag = 1 << 5 // Large integer
|
LI Tag = 1 << 5 // Large unsigned integer
|
||||||
FP Tag = 2 << 5 // Floating point
|
LSI Tag = 2 << 5 // Large signed integer
|
||||||
SBA Tag = 3 << 5 // Small byte array
|
FP Tag = 3 << 5 // Floating point
|
||||||
LBA Tag = 4 << 5 // Large byte array
|
SBA Tag = 4 << 5 // Small byte array
|
||||||
OTA Tag = 5 << 5 // One-tag array
|
LBA Tag = 5 << 5 // Large byte array
|
||||||
KTV Tag = 6 << 5 // Key-tag-value table
|
OTA Tag = 6 << 5 // One-tag array
|
||||||
|
KTV Tag = 7 << 5 // Key-tag-value table
|
||||||
TNMask Tag = 0xE0 // The entire TN bitfield
|
TNMask Tag = 0xE0 // The entire TN bitfield
|
||||||
CNMask Tag = 0x1F // The entire CN bitfield
|
CNMask Tag = 0x1F // The entire CN bitfield
|
||||||
CNLimit Tag = 32 // All valid CNs are < CNLimit
|
CNLimit Tag = 32 // All valid CNs are < CNLimit
|
||||||
@@ -40,6 +43,7 @@ func (tag Tag) String() string {
|
|||||||
switch tag.WithoutCN() {
|
switch tag.WithoutCN() {
|
||||||
case SI: tn = "SI"
|
case SI: tn = "SI"
|
||||||
case LI: tn = "LI"
|
case LI: tn = "LI"
|
||||||
|
case LSI: tn = "LSI"
|
||||||
case FP: tn = "FP"
|
case FP: tn = "FP"
|
||||||
case SBA: tn = "SBA"
|
case SBA: tn = "SBA"
|
||||||
case LBA: tn = "LBA"
|
case LBA: tn = "LBA"
|
||||||
@@ -54,6 +58,11 @@ func BufferTag(value []byte) Tag {
|
|||||||
return bufferLenTag(len(value))
|
return bufferLenTag(len(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StringTag returns the appropriate tag for a string.
|
||||||
|
func StringTag(value string) Tag {
|
||||||
|
return bufferLenTag(len(value))
|
||||||
|
}
|
||||||
|
|
||||||
func bufferLenTag(length int) Tag {
|
func bufferLenTag(length int) Tag {
|
||||||
if length < int(CNLimit) {
|
if length < int(CNLimit) {
|
||||||
return SBA.WithCN(length)
|
return SBA.WithCN(length)
|
||||||
|
|||||||
Reference in New Issue
Block a user