Compare commits
No commits in common. "81d95dcd90bb9f247bacf35d5297211d393b51b1" and "fbb68e6ff7a5729c44f35308bc33ac5487fa1ea6" have entirely different histories.
81d95dcd90
...
fbb68e6ff7
@ -33,11 +33,6 @@ PDL allows defining a protocol using HOPP and TAPE.
|
|||||||
| {...} | KTV | * | Table with defined schema
|
| {...} | KTV | * | Table with defined schema
|
||||||
| Any | * | * | Value of an undefined type
|
| Any | * | * | Value of an undefined type
|
||||||
|
|
||||||
Tables with a defined schema can specify some fields as optional using a
|
|
||||||
question mark before the type. This will wrap the field the go-util
|
|
||||||
ucontainer.Option type. When encoding, void fields will not be included in the
|
|
||||||
output, and when decoding, unspecified fields are left void.
|
|
||||||
|
|
||||||
[^1]: Excluding SI and SBA. I5 and U5 cannot be used in an array, but String and
|
[^1]: Excluding SI and SBA. I5 and U5 cannot be used in an array, but String and
|
||||||
Buffer are simply forced to use their "long" variant.
|
Buffer are simply forced to use their "long" variant.
|
||||||
|
|
||||||
@ -53,7 +48,6 @@ structures. They are separated by whitespace.
|
|||||||
| Method | `M[0-9A-Fa-f]{4}` | A 16-bit hexadecimal method code.
|
| Method | `M[0-9A-Fa-f]{4}` | A 16-bit hexadecimal method code.
|
||||||
| Key | `[0-9A-Fa-f]{4}` | A 16-bit hexadecimal table key.
|
| Key | `[0-9A-Fa-f]{4}` | A 16-bit hexadecimal table key.
|
||||||
| Ident | `[A-Z][A-Za-z0-9]` | An identifier.
|
| Ident | `[A-Z][A-Za-z0-9]` | An identifier.
|
||||||
| Option | `?` | A question mark.
|
|
||||||
| Comma | `,` | A comma separator.
|
| Comma | `,` | A comma separator.
|
||||||
| LBrace | `{` | A left curly brace.
|
| LBrace | `{` | A left curly brace.
|
||||||
| RBrace | `}` | A right curly brace.
|
| RBrace | `}` | A right curly brace.
|
||||||
@ -113,7 +107,7 @@ Below is an EBNF description of the language.
|
|||||||
<method> -> /M[0-9A-Fa-f]{4}/
|
<method> -> /M[0-9A-Fa-f]{4}/
|
||||||
<key> -> /[0-9A-Fa-f]{4}/
|
<key> -> /[0-9A-Fa-f]{4}/
|
||||||
<ident> -> /[A-Z][A-Za-z0-9]/
|
<ident> -> /[A-Z][A-Za-z0-9]/
|
||||||
<field> -> <key> <ident> ["?"] <type>
|
<field> -> <key> <ident> <type>
|
||||||
<type> -> <ident>
|
<type> -> <ident>
|
||||||
| "[" "]" <type>
|
| "[" "]" <type>
|
||||||
| "{" (<comment>* <field> ",")* [<comment>* <field>] "}"
|
| "{" (<comment>* <field> ",")* [<comment>* <field>] "}"
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import "git.tebibyte.media/sashakoshka/hopp/tape"
|
|||||||
|
|
||||||
const imports =
|
const imports =
|
||||||
`
|
`
|
||||||
import "git.tebibyte.media/sashakoshka/hopp"
|
|
||||||
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
import "git.tebibyte.media/sashakoshka/hopp/tape"
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -57,8 +56,6 @@ func boolInt(input bool) int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ hopp.Option[int]
|
|
||||||
`
|
`
|
||||||
|
|
||||||
// Generator converts protocols into Go code.
|
// Generator converts protocols into Go code.
|
||||||
@ -482,33 +479,19 @@ 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()
|
||||||
for key, field := range typ.Fields {
|
for key, field := range typ.Fields {
|
||||||
fieldSource := fmt.Sprintf("%s.%s", valueSource, field.Name)
|
|
||||||
if field.Option {
|
|
||||||
nn, err = this.iprintf("if value, ok := %s.Value(); ok {\n", fieldSource)
|
|
||||||
n += nn; if err != nil { return n, err }
|
|
||||||
fieldSource = "value"
|
|
||||||
this.push()
|
|
||||||
}
|
|
||||||
|
|
||||||
nn, err = this.iprintf("nn, err = encoder.WriteUint16(0x%04X)\n", key)
|
nn, err = this.iprintf("nn, err = encoder.WriteUint16(0x%04X)\n", key)
|
||||||
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 }
|
||||||
|
fieldSource := fmt.Sprintf("%s.%s", valueSource, field.Name)
|
||||||
tagVar, nn, err := this.generateTag(field.Type, fieldSource)
|
tagVar, nn, err := this.generateTag(field.Type, fieldSource)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
nn, err = this.iprintf("nn, err = encoder.WriteUint8(uint8(%s))\n", tagVar)
|
nn, err = this.iprintf("nn, err = encoder.WriteUint8(uint8(%s))\n", tagVar)
|
||||||
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 }
|
||||||
|
|
||||||
nn, err = this.generateEncodeValue(field.Type, fieldSource, tagVar)
|
nn, err = this.generateEncodeValue(field.Type, fieldSource, tagVar)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
|
|
||||||
if field.Option {
|
|
||||||
this.pop()
|
|
||||||
nn, err = this.iprintf("}\n")
|
|
||||||
n += nn; if err != nil { return n, err }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.pop()
|
this.pop()
|
||||||
nn, err = this.iprintf("}\n")
|
nn, err = this.iprintf("}\n")
|
||||||
@ -571,7 +554,13 @@ func (this *Generator) generateDecodeValue(typ Type, typeName, valueSource, tagS
|
|||||||
prefix = "ReadInt"
|
prefix = "ReadInt"
|
||||||
}
|
}
|
||||||
destinationVar := this.newTemporaryVar("destination")
|
destinationVar := this.newTemporaryVar("destination")
|
||||||
nn, err := this.iprintf("%s, nn, err := decoder.%s%d()\n", destinationVar, prefix, typ.Bits)
|
nn, err := this.iprintf("var %s ", destinationVar)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
nn, err = this.generateType(typ)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
nn, err = this.print("\n")
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
nn, err = this.iprintf("%s, nn, err = decoder.%s%d()\n", destinationVar, prefix, typ.Bits)
|
||||||
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 }
|
||||||
@ -585,7 +574,13 @@ func (this *Generator) generateDecodeValue(typ Type, typeName, valueSource, tagS
|
|||||||
case TypeFloat:
|
case TypeFloat:
|
||||||
// FP: <value: FloatN>
|
// FP: <value: FloatN>
|
||||||
destinationVar := this.newTemporaryVar("destination")
|
destinationVar := this.newTemporaryVar("destination")
|
||||||
nn, err := this.iprintf("%s, nn, err := decoder.ReadFloat%d()\n", destinationVar, typ.Bits)
|
nn, err := this.iprintf("var %s ", destinationVar)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
nn, err = this.generateType(typ)
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
nn, err = this.print("\n")
|
||||||
|
n += nn; if err != nil { return n, err }
|
||||||
|
nn, err = this.iprintf("%s, nn, err = decoder.ReadFloat%d()\n", destinationVar, typ.Bits)
|
||||||
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 }
|
||||||
@ -808,7 +803,6 @@ func (this *Generator) generateDecodeBranch(hash [16]byte, typ Type, typeName st
|
|||||||
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 }
|
||||||
case TypeTableDefined:
|
case TypeTableDefined:
|
||||||
// TODO: options
|
|
||||||
// KTV: <length: UN> (<key: U16> <tag: Tag> <value>)*
|
// KTV: <length: UN> (<key: U16> <tag: Tag> <value>)*
|
||||||
// read header
|
// read header
|
||||||
lengthVar := this.newTemporaryVar("length")
|
lengthVar := this.newTemporaryVar("length")
|
||||||
@ -885,25 +879,10 @@ func (this *Generator) generateDecodeBranch(hash [16]byte, typ Type, typeName st
|
|||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
|
|
||||||
// decode payload
|
// decode payload
|
||||||
if field.Option {
|
nn, err = this.generateDecodeValue(
|
||||||
destination := this.newTemporaryVar("destination")
|
field.Type, "",
|
||||||
nn, err = this.iprintf("var %s ", destination)
|
fmt.Sprintf("(&(this.%s))", field.Name), fieldTagVar)
|
||||||
n += nn; if err != nil { return n, err }
|
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.printf("\n")
|
|
||||||
n += nn; if err != nil { return n, err }
|
|
||||||
nn, err = this.generateDecodeValue(
|
|
||||||
field.Type, "", fmt.Sprintf("(&%s)", destination), fieldTagVar)
|
|
||||||
n += nn; if err != nil { return n, err }
|
|
||||||
nn, err = this.iprintf("this.%s = hopp.O(%s)\n", field.Name, destination)
|
|
||||||
n += nn; if err != nil { return n, err }
|
|
||||||
} else {
|
|
||||||
nn, err = this.generateDecodeValue(
|
|
||||||
field.Type, "",
|
|
||||||
fmt.Sprintf("(&(this.%s))", field.Name), fieldTagVar)
|
|
||||||
n += nn; if err != nil { return n, err }
|
|
||||||
}
|
|
||||||
this.pop()
|
this.pop()
|
||||||
}
|
}
|
||||||
nn, err = this.iprintf("default:\n")
|
nn, err = this.iprintf("default:\n")
|
||||||
@ -916,6 +895,16 @@ func (this *Generator) generateDecodeBranch(hash [16]byte, typ Type, typeName st
|
|||||||
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 }
|
||||||
|
|
||||||
|
// 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)
|
default: return n, fmt.Errorf("unexpected type: %T", typ)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1144,16 +1133,8 @@ func (this *Generator) generateTypeTableDefined(typ TypeTableDefined) (n int, er
|
|||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
nn, err = this.iprintf("%s ", field.Name)
|
nn, err = this.iprintf("%s ", field.Name)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
if field.Option {
|
|
||||||
nn, err = this.print("hopp.Option[")
|
|
||||||
n += nn; if err != nil { return n, err }
|
|
||||||
}
|
|
||||||
nn, err = this.generateType(field.Type)
|
nn, err = this.generateType(field.Type)
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
if field.Option {
|
|
||||||
nn, err = this.print("]")
|
|
||||||
n += nn; if err != nil { return n, err }
|
|
||||||
}
|
|
||||||
nn, err = this.print("\n")
|
nn, err = this.print("\n")
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,34 +84,6 @@ func init() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
exampleProtocol.Messages[0x0006] = Message {
|
|
||||||
Name: "Option",
|
|
||||||
Type: TypeTableDefined {
|
|
||||||
Fields: map[uint16] Field {
|
|
||||||
0x0000: Field { Name: "OU5", Option: true, Type: TypeInt { Bits: 5 } },
|
|
||||||
0x0001: Field { Name: "OU8", Option: true, Type: TypeInt { Bits: 8 } },
|
|
||||||
0x0002: Field { Name: "OU16", Option: true, Type: TypeInt { Bits: 16 } },
|
|
||||||
0x0003: Field { Name: "OU32", Option: true, Type: TypeInt { Bits: 32 } },
|
|
||||||
0x0004: Field { Name: "OU64", Option: true, Type: TypeInt { Bits: 64 } },
|
|
||||||
0x0005: Field { Name: "OI8", Option: true, Type: TypeInt { Bits: 8, Signed: true } },
|
|
||||||
0x0006: Field { Name: "OI16", Option: true, Type: TypeInt { Bits: 16, Signed: true } },
|
|
||||||
0x0007: Field { Name: "OI32", Option: true, Type: TypeInt { Bits: 32, Signed: true } },
|
|
||||||
0x0008: Field { Name: "OI64", Option: true, Type: TypeInt { Bits: 64, Signed: true } },
|
|
||||||
0x0009: Field { Name: "OF32", Option: true, Type: TypeFloat { Bits: 32 } },
|
|
||||||
0x000A: Field { Name: "OF64", Option: true, Type: TypeFloat { Bits: 64 } },
|
|
||||||
0x000B: Field { Name: "OBool", Option: true, Type: TypeBool { } },
|
|
||||||
0x000C: Field { Name: "OString", Option: true, Type: TypeString { } },
|
|
||||||
0x000D: Field { Name: "OArray", Option: true, Type: TypeArray {
|
|
||||||
Element: TypeInt { Bits: 16, Signed: true } } },
|
|
||||||
0x000E: Field { Name: "OTable", Option: true, Type: TypeTableDefined {
|
|
||||||
Fields: map[uint16] Field {
|
|
||||||
0x0001: Field { Name: "A", Type: TypeInt { Bits: 8, Signed: true }, },
|
|
||||||
0x0002: Field { Name: "B", Type: TypeFloat { Bits: 64 }, },
|
|
||||||
} } },
|
|
||||||
0x000F: Field { Name: "T0", Option: true, Type: TypeTable { } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
exampleProtocol.Types["User"] = Typedef {
|
exampleProtocol.Types["User"] = Typedef {
|
||||||
Type: TypeTableDefined {
|
Type: TypeTableDefined {
|
||||||
Fields: map[uint16] Field {
|
Fields: map[uint16] Field {
|
||||||
|
|||||||
@ -10,7 +10,6 @@ const (
|
|||||||
TokenMethod parse.TokenKind = iota
|
TokenMethod parse.TokenKind = iota
|
||||||
TokenKey
|
TokenKey
|
||||||
TokenIdent
|
TokenIdent
|
||||||
TokenOption
|
|
||||||
TokenComma
|
TokenComma
|
||||||
TokenLBrace
|
TokenLBrace
|
||||||
TokenRBrace
|
TokenRBrace
|
||||||
@ -23,7 +22,6 @@ var tokenNames = map[parse.TokenKind] string {
|
|||||||
TokenMethod: "Method",
|
TokenMethod: "Method",
|
||||||
TokenKey: "Key",
|
TokenKey: "Key",
|
||||||
TokenIdent: "Ident",
|
TokenIdent: "Ident",
|
||||||
TokenOption: "Option",
|
|
||||||
TokenComma: "Comma",
|
TokenComma: "Comma",
|
||||||
TokenLBrace: "LBrace",
|
TokenLBrace: "LBrace",
|
||||||
TokenRBrace: "RBrace",
|
TokenRBrace: "RBrace",
|
||||||
@ -124,11 +122,6 @@ func (this *lexer) nextInternal() (token parse.Token, err error) {
|
|||||||
if this.eof { err = nil; return }
|
if this.eof { err = nil; return }
|
||||||
if err != nil { return }
|
if err != nil { return }
|
||||||
}
|
}
|
||||||
// Option
|
|
||||||
case this.rune == '?':
|
|
||||||
token.Kind = TokenOption
|
|
||||||
appendRune()
|
|
||||||
if this.eof { err = nil; return }
|
|
||||||
// Comma
|
// Comma
|
||||||
case this.rune == ',':
|
case this.rune == ',':
|
||||||
token.Kind = TokenComma
|
token.Kind = TokenComma
|
||||||
|
|||||||
@ -16,7 +16,6 @@ func TestLex(test *testing.T) {
|
|||||||
// wow
|
// wow
|
||||||
0001 Users []User,
|
0001 Users []User,
|
||||||
0002 Followers U32,
|
0002 Followers U32,
|
||||||
0003 Wings ?Int,
|
|
||||||
}`))
|
}`))
|
||||||
if err != nil { test.Fatal(parse.Format(err)) }
|
if err != nil { test.Fatal(parse.Format(err)) }
|
||||||
|
|
||||||
@ -43,11 +42,6 @@ func TestLex(test *testing.T) {
|
|||||||
tok(TokenIdent, "Followers"),
|
tok(TokenIdent, "Followers"),
|
||||||
tok(TokenIdent, "U32"),
|
tok(TokenIdent, "U32"),
|
||||||
tok(TokenComma, ","),
|
tok(TokenComma, ","),
|
||||||
tok(TokenKey, "0003"),
|
|
||||||
tok(TokenIdent, "Wings"),
|
|
||||||
tok(TokenOption, "?"),
|
|
||||||
tok(TokenIdent, "Int"),
|
|
||||||
tok(TokenComma, ","),
|
|
||||||
tok(TokenRBrace, "}"),
|
tok(TokenRBrace, "}"),
|
||||||
tok(parse.EOF, ""),
|
tok(parse.EOF, ""),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -214,19 +214,12 @@ func (this *parser) parseField(doc string) (uint16, Field, error) {
|
|||||||
name := this.Value()
|
name := this.Value()
|
||||||
err = this.Next()
|
err = this.Next()
|
||||||
if err != nil { return 0, Field { }, err }
|
if err != nil { return 0, Field { }, err }
|
||||||
option := false
|
|
||||||
if this.Kind() == TokenOption {
|
|
||||||
option = true
|
|
||||||
err = this.Next()
|
|
||||||
if err != nil { return 0, Field { }, err }
|
|
||||||
}
|
|
||||||
typ, err := this.parseType()
|
typ, err := this.parseType()
|
||||||
if err != nil { return 0, Field { }, err }
|
if err != nil { return 0, Field { }, err }
|
||||||
return uint16(key), Field {
|
return uint16(key), Field {
|
||||||
Name: name,
|
Name: name,
|
||||||
Doc: doc,
|
Doc: doc,
|
||||||
Type: typ,
|
Type: typ,
|
||||||
Option: option,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,6 @@ func TestParse(test *testing.T) {
|
|||||||
0x0001: Field { Name: "Bio", Type: TypeString { } },
|
0x0001: Field { Name: "Bio", Type: TypeString { } },
|
||||||
0x0002: Field { Name: "Followers", Type: TypeInt { Bits: 32 } },
|
0x0002: Field { Name: "Followers", Type: TypeInt { Bits: 32 } },
|
||||||
0x0003: Field { Name: "Bouncy", Type: TypeBool { } },
|
0x0003: Field { Name: "Bouncy", Type: TypeBool { } },
|
||||||
0x0004: Field { Name: "Wings", Type: TypeInt { Bits: 32 } },
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -64,7 +63,6 @@ func TestParse(test *testing.T) {
|
|||||||
0001 Bio String,
|
0001 Bio String,
|
||||||
0002 Followers U32,
|
0002 Followers U32,
|
||||||
0003 Bouncy Bool,
|
0003 Bouncy Bool,
|
||||||
0004 Wings ?U32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Anything Any
|
Anything Any
|
||||||
|
|||||||
@ -95,10 +95,9 @@ func (typ TypeTableDefined) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Field struct {
|
type Field struct {
|
||||||
Name string
|
Name string
|
||||||
Doc string
|
Doc string
|
||||||
Type Type
|
Type Type
|
||||||
Option bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (field Field) String() string {
|
func (field Field) String() string {
|
||||||
|
|||||||
2
go.mod
2
go.mod
@ -1,6 +1,6 @@
|
|||||||
module git.tebibyte.media/sashakoshka/hopp
|
module git.tebibyte.media/sashakoshka/hopp
|
||||||
|
|
||||||
go 1.24.0
|
go 1.23.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.tebibyte.media/sashakoshka/go-cli v0.1.3
|
git.tebibyte.media/sashakoshka/go-cli v0.1.3
|
||||||
|
|||||||
@ -97,32 +97,6 @@ func (sn Snake) String() string {
|
|||||||
return out.String()
|
return out.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sn Snake) CharsString() string {
|
|
||||||
if len(sn) == 0 || len(sn[0]) == 0 || len(sn[0][0]) == 0 {
|
|
||||||
return "EMPTY"
|
|
||||||
}
|
|
||||||
|
|
||||||
out := strings.Builder { }
|
|
||||||
for index, sector := range sn {
|
|
||||||
if index > 0 { out.WriteString(" : ") }
|
|
||||||
out.WriteRune('[')
|
|
||||||
for index, variation := range sector {
|
|
||||||
if index > 0 { out.WriteString(" / ") }
|
|
||||||
for _, byt := range variation {
|
|
||||||
run := rune(byt)
|
|
||||||
if unicode.IsPrint(run) && run < 0x7F {
|
|
||||||
out.WriteRune(run)
|
|
||||||
} else {
|
|
||||||
out.WriteRune('.')
|
|
||||||
}
|
|
||||||
out.WriteRune(' ')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.WriteRune(']')
|
|
||||||
}
|
|
||||||
return out.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// HexBytes formats bytes into a hexadecimal string.
|
// HexBytes formats bytes into a hexadecimal string.
|
||||||
func HexBytes(data []byte) string {
|
func HexBytes(data []byte) string {
|
||||||
if len(data) == 0 { return "EMPTY" }
|
if len(data) == 0 { return "EMPTY" }
|
||||||
@ -133,24 +107,6 @@ func HexBytes(data []byte) string {
|
|||||||
return out.String()
|
return out.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// HexChars returns all printable bytes in the string, with non-printable ones
|
|
||||||
// replaced with a dot. Each character has an extra space after it for placing
|
|
||||||
// underneath the result of HexBytes.
|
|
||||||
func HexChars(data []byte) string {
|
|
||||||
if len(data) == 0 { return "EMPTY" }
|
|
||||||
out := strings.Builder { }
|
|
||||||
for _, byt := range data {
|
|
||||||
run := rune(byt)
|
|
||||||
if unicode.IsPrint(run) && run < 0x7F {
|
|
||||||
out.WriteRune(run)
|
|
||||||
} else {
|
|
||||||
out.WriteRune('.')
|
|
||||||
}
|
|
||||||
out.WriteRune(' ')
|
|
||||||
}
|
|
||||||
return out.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Describe returns a string representing the type and data of the given value.
|
// Describe returns a string representing the type and data of the given value.
|
||||||
func Describe(value any) string {
|
func Describe(value any) string {
|
||||||
desc := describer { }
|
desc := describer { }
|
||||||
|
|||||||
27
option.go
27
option.go
@ -2,10 +2,29 @@ package hopp
|
|||||||
|
|
||||||
import "git.tebibyte.media/sashakoshka/go-util/container"
|
import "git.tebibyte.media/sashakoshka/go-util/container"
|
||||||
|
|
||||||
// Option is an alias for ucontainer.Option, defined here for convenience
|
// Option allows an optional value to be defined without using a pointer.
|
||||||
type Option[T any] = ucontainer.Option[T]
|
// TODO make generic alias once go 1.24 releases
|
||||||
|
type Option[T any] ucontainer.Optional[T]
|
||||||
|
|
||||||
// O is an alias for ucontainer.O, defined here for convenience
|
|
||||||
func O[T any](value T) Option[T] {
|
func O[T any](value T) Option[T] {
|
||||||
return ucontainer.O(value)
|
return Option[T](ucontainer.O(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Void[T any]() Option[T] {
|
||||||
|
return Option[T](ucontainer.Void[T]())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (option Option[T]) Ok() bool {
|
||||||
|
return (ucontainer.Optional[T])(option).Exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (option Option[T]) Get() (T, bool) {
|
||||||
|
return (ucontainer.Optional[T])(option).Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (option Option[T]) Default(defaul T) T {
|
||||||
|
if value, ok := option.Get(); ok {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return defaul
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,10 +6,6 @@ package tape
|
|||||||
// TODO: add support for struct tags: `tape:"0000"`, tape:"0001"` so they can get
|
// TODO: add support for struct tags: `tape:"0000"`, tape:"0001"` so they can get
|
||||||
// transformed into tables with a defined schema
|
// transformed into tables with a defined schema
|
||||||
|
|
||||||
// TODO: support special behavior for options in structs: don't just write a
|
|
||||||
// zero value if the option is void, write no field at all. also consider doing
|
|
||||||
// this for maps, and maybe slices.
|
|
||||||
|
|
||||||
// TODO: test all of these smaller functions individually
|
// TODO: test all of these smaller functions individually
|
||||||
|
|
||||||
// For an explanation as to why this package always treats LBA/SBA as strings,
|
// For an explanation as to why this package always treats LBA/SBA as strings,
|
||||||
@ -83,12 +79,6 @@ func EncodeAny(encoder *Encoder, value any, tag Tag) (n int, err error) {
|
|||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// option
|
|
||||||
if isTypeOption(reflectValue.Type()) {
|
|
||||||
elemValue, _ := optionValue(reflectValue) // zero value for free
|
|
||||||
return EncodeAny(encoder, elemValue, tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
// aggregates
|
// aggregates
|
||||||
reflectType := reflect.TypeOf(value)
|
reflectType := reflect.TypeOf(value)
|
||||||
switch reflectType.Kind() {
|
switch reflectType.Kind() {
|
||||||
@ -321,12 +311,6 @@ func tagAny(reflectValue reflect.Value) (Tag, error) {
|
|||||||
return bufferLenTag(reflectValue.Len()), nil
|
return bufferLenTag(reflectValue.Len()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// option
|
|
||||||
if isTypeOption(reflectValue.Type()) {
|
|
||||||
elem, _ := optionValue(reflectValue) // zero value for free
|
|
||||||
return tagAny(elem)
|
|
||||||
}
|
|
||||||
|
|
||||||
// aggregates
|
// aggregates
|
||||||
reflectType := reflectValue.Type()
|
reflectType := reflectValue.Type()
|
||||||
switch reflectType.Kind() {
|
switch reflectType.Kind() {
|
||||||
@ -352,14 +336,9 @@ func encodeAnySlice(encoder *Encoder, value any, tag Tag) (n int, err error) {
|
|||||||
for index := 0; index < reflectValue.Len(); index += 1 {
|
for index := 0; index < reflectValue.Len(); index += 1 {
|
||||||
itemTag, err := tagAny(reflectValue.Index(index))
|
itemTag, err := tagAny(reflectValue.Index(index))
|
||||||
if err != nil { return n, err }
|
if err != nil { return n, err }
|
||||||
if itemTag.Is(SBA) {
|
|
||||||
// SBA data in an LBA will always have the tag LBA:0,
|
|
||||||
// because 32 <= 256
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if itemTag.CN() > oneTag.CN() { oneTag = itemTag }
|
if itemTag.CN() > oneTag.CN() { oneTag = itemTag }
|
||||||
}
|
}
|
||||||
if oneTag.Is(SBA) { oneTag = LBA.WithCN(oneTag.CN()) }
|
if oneTag.Is(SBA) { oneTag += 1 << 5 }
|
||||||
nn, err = encoder.WriteUint8(uint8(oneTag))
|
nn, err = encoder.WriteUint8(uint8(oneTag))
|
||||||
n += nn; if err != nil { return n, err }
|
n += nn; if err != nil { return n, err }
|
||||||
for index := 0; index < reflectValue.Len(); index += 1 {
|
for index := 0; index < reflectValue.Len(); index += 1 {
|
||||||
@ -597,21 +576,6 @@ func isTypeAny(typ reflect.Type) bool {
|
|||||||
return typ.Kind() == reflect.Interface && typ.NumMethod() == 0
|
return typ.Kind() == reflect.Interface && typ.NumMethod() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// isTypeOption returns whether the given reflect.Type is a ucontainer.Option,
|
|
||||||
// and returns the element type if true.
|
|
||||||
func isTypeOption(typ reflect.Type) bool {
|
|
||||||
// TODO: change when needed
|
|
||||||
goutilPath := "git.tebibyte.media/sashakoshka/go-util"
|
|
||||||
return typ.Name() == "Option" && typ.PkgPath() == goutilPath + "/container"
|
|
||||||
}
|
|
||||||
|
|
||||||
// optionValue returns the value of an option. The value MUST be an option, or
|
|
||||||
// this function will panic.
|
|
||||||
func optionValue(value reflect.Value) (elem reflect.Value, ok bool) {
|
|
||||||
result := value.MethodByName("Value").Call([]reflect.Value { })
|
|
||||||
return result[0], result[1].Bool()
|
|
||||||
}
|
|
||||||
|
|
||||||
// peekSlice returns the element tag and dimension count of the OTA currently
|
// peekSlice returns the element tag and dimension count of the OTA currently
|
||||||
// being decoded. It does not use up the decoder, it only peeks.
|
// being decoded. It does not use up the decoder, it only peeks.
|
||||||
func peekSlice(decoder *Decoder, tag Tag) (Tag, int, error) {
|
func peekSlice(decoder *Decoder, tag Tag) (Tag, int, error) {
|
||||||
|
|||||||
@ -3,10 +3,39 @@ package tape
|
|||||||
import "bytes"
|
import "bytes"
|
||||||
import "testing"
|
import "testing"
|
||||||
import "reflect"
|
import "reflect"
|
||||||
import "git.tebibyte.media/sashakoshka/go-util/container"
|
|
||||||
import tu "git.tebibyte.media/sashakoshka/hopp/internal/testutil"
|
import tu "git.tebibyte.media/sashakoshka/hopp/internal/testutil"
|
||||||
|
|
||||||
var samplePayloads [][]byte
|
var samplePayloads = [][]byte {
|
||||||
|
/* int8 */ []byte { byte(LSI.WithCN(0)), 0x45 },
|
||||||
|
/* int16 */ []byte { byte(LSI.WithCN(1)), 0x45, 0x67 },
|
||||||
|
/* int32 */ []byte { byte(LSI.WithCN(3)), 0x45, 0x67, 0x89, 0xAB },
|
||||||
|
/* int64 */ []byte { byte(LSI.WithCN(7)), 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23 },
|
||||||
|
/* uint5 */ []byte { byte(SI.WithCN(12)) },
|
||||||
|
/* uint8 */ []byte { byte(LI.WithCN(0)), 0x45 },
|
||||||
|
/* uint16 */ []byte { byte(LI.WithCN(1)), 0x45, 0x67 },
|
||||||
|
/* uint32 */ []byte { byte(LI.WithCN(3)), 0x45, 0x67, 0x89, 0xAB },
|
||||||
|
/* uint64 */ []byte { byte(LI.WithCN(7)), 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23 },
|
||||||
|
/* bool */ []byte { byte(SI.WithCN(0)) },
|
||||||
|
/* bool */ []byte { byte(SI.WithCN(1)) },
|
||||||
|
/* string */ []byte { byte(SBA.WithCN(7)), 'p', 'u', 'p', 'e', 'v', 'e', 'r' },
|
||||||
|
/* []byte */ []byte { byte(SBA.WithCN(5)), 'b', 'l', 'a', 'r', 'g' },
|
||||||
|
/* []string */ []byte {
|
||||||
|
byte(OTA.WithCN(0)), 2, byte(LBA.WithCN(0)),
|
||||||
|
0x08, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23,
|
||||||
|
0x05, 0x11, 0x11, 0x11, 0x11, 0x11,
|
||||||
|
},
|
||||||
|
/* map[uint16] any */ []byte {
|
||||||
|
byte(KTV.WithCN(0)), 2,
|
||||||
|
0x02, 0x23, byte(LSI.WithCN(1)), 0x45, 0x67,
|
||||||
|
0x02, 0x24, byte(LI.WithCN(3)), 0x45, 0x67, 0x89, 0xAB,
|
||||||
|
},
|
||||||
|
/* map[uint16] any */ []byte {
|
||||||
|
byte(KTV.WithCN(0)), 3,
|
||||||
|
0x00, 0x01, 0x63, 0x43, 0xF4, 0xC0, 0x00,
|
||||||
|
0x00, 0x02, 0x82, 'h', 'i',
|
||||||
|
0x00, 0x03, 0x21, 0x39, 0x92,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
var sampleValues = []any {
|
var sampleValues = []any {
|
||||||
/* int8 */ int8(0x45),
|
/* int8 */ int8(0x45),
|
||||||
@ -35,64 +64,6 @@ var sampleValues = []any {
|
|||||||
0x0002: "hi",
|
0x0002: "hi",
|
||||||
0x0003: uint16(0x3992),
|
0x0003: uint16(0x3992),
|
||||||
},
|
},
|
||||||
// IMPORTANT: ONLY ADD AT THE END!!!! DO NOT MOVE WHAT IS ALREADY HERE!
|
|
||||||
// IMPORTANT: ONLY ADD AT THE END!!!! DO NOT MOVE WHAT IS ALREADY HERE!
|
|
||||||
}
|
|
||||||
|
|
||||||
type sample struct {
|
|
||||||
t Tag
|
|
||||||
v any
|
|
||||||
s tu.Snake
|
|
||||||
}
|
|
||||||
|
|
||||||
var samples = []sample {
|
|
||||||
/* int8 */ sample { t: LSI.WithCN(0), s: tu.S(0x45) },
|
|
||||||
/* int16 */ sample { t: LSI.WithCN(1), s: tu.S(0x45, 0x67) },
|
|
||||||
/* int32 */ sample { t: LSI.WithCN(3), s: tu.S(0x45, 0x67, 0x89, 0xAB) },
|
|
||||||
/* int64 */ sample { t: LSI.WithCN(7), s: tu.S(0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23) },
|
|
||||||
/* uint5 */ sample { t: SI.WithCN(12), s: tu.S() },
|
|
||||||
/* uint8 */ sample { t: LI.WithCN(0), s: tu.S(0x45) },
|
|
||||||
/* uint16 */ sample { t: LI.WithCN(1), s: tu.S(0x45, 0x67) },
|
|
||||||
/* uint32 */ sample { t: LI.WithCN(3), s: tu.S(0x45, 0x67, 0x89, 0xAB) },
|
|
||||||
/* uint64 */ sample { t: LI.WithCN(7), s: tu.S(0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23) },
|
|
||||||
/* bool */ sample { t: SI.WithCN(0), s: tu.S() },
|
|
||||||
/* bool */ sample { t: SI.WithCN(1), s: tu.S() },
|
|
||||||
/* string */ sample { t: SBA.WithCN(7), s: tu.S('p', 'u', 'p', 'e', 'v', 'e', 'r') },
|
|
||||||
/* []byte */ sample { t: SBA.WithCN(5), s: tu.S('b', 'l', 'a', 'r', 'g') },
|
|
||||||
/* []string */ sample {
|
|
||||||
t: OTA.WithCN(0),
|
|
||||||
s: tu.S(2, byte(LBA.WithCN(0)),
|
|
||||||
0x08, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23,
|
|
||||||
0x05, 0x11, 0x11, 0x11, 0x11, 0x11),
|
|
||||||
},
|
|
||||||
/* map[uint16] any */ sample {
|
|
||||||
t: KTV.WithCN(0),
|
|
||||||
s: tu.S(2).AddVar(
|
|
||||||
[]byte { 0x02, 0x23, byte(LSI.WithCN(1)), 0x45, 0x67 },
|
|
||||||
[]byte { 0x02, 0x24, byte(LI.WithCN(3)), 0x45, 0x67, 0x89, 0xAB }),
|
|
||||||
},
|
|
||||||
/* map[uint16] any */ sample {
|
|
||||||
t: KTV.WithCN(0),
|
|
||||||
s: tu.S(3).AddVar(
|
|
||||||
[]byte { 0x00, 0x01, 0x63, 0x43, 0xF4, 0xC0, 0x00 },
|
|
||||||
[]byte { 0x00, 0x02, 0x82, 'h', 'i' },
|
|
||||||
[]byte { 0x00, 0x03, 0x21, 0x39, 0x92 }),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var sampleOptionValues []any
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
sampleOptionValues = make([]any, len(sampleValues))
|
|
||||||
for index, value := range sampleValues {
|
|
||||||
sampleOptionValues[index] = ucontainer.O(value)
|
|
||||||
samples[index].v = value
|
|
||||||
}
|
|
||||||
samplePayloads = make([][]byte, len(samples))
|
|
||||||
for index, sample := range samples {
|
|
||||||
item := append([]byte { byte(sample.t) }, sample.s.Flatten()...)
|
|
||||||
samplePayloads[index] = item
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type userDefinedInteger int16
|
type userDefinedInteger int16
|
||||||
@ -300,20 +271,6 @@ func TestEncodeDecodeAnyDestination(test *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEncodeOption(test *testing.T) {
|
|
||||||
for _, sample := range samples {
|
|
||||||
snake := sample.s
|
|
||||||
tag := sample.t
|
|
||||||
value := sample.v
|
|
||||||
if _, ok := value.(bool); tag.Is(SI) && !ok {
|
|
||||||
// we will never encode an SI unless its a bool
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err := testEncodeAny(test, value, tag, snake)
|
|
||||||
if err != nil { test.Fatal(err) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPeekSlice(test *testing.T) {
|
func TestPeekSlice(test *testing.T) {
|
||||||
buffer := bytes.NewBuffer([]byte {
|
buffer := bytes.NewBuffer([]byte {
|
||||||
2, byte(OTA.WithCN(3)),
|
2, byte(OTA.WithCN(3)),
|
||||||
|
|||||||
@ -32,9 +32,7 @@ func testEncodeAny(test *testing.T, value any, correctTag Tag, correctBytes tu.S
|
|||||||
test.Log("n: ", n)
|
test.Log("n: ", n)
|
||||||
test.Log("tag: ", tag)
|
test.Log("tag: ", tag)
|
||||||
test.Log("got: ", tu.HexBytes(bytes))
|
test.Log("got: ", tu.HexBytes(bytes))
|
||||||
test.Log(" : ", tu.HexChars(bytes))
|
|
||||||
test.Log("correct:", correctBytes)
|
test.Log("correct:", correctBytes)
|
||||||
test.Log(" :", correctBytes.CharsString())
|
|
||||||
if tag != correctTag {
|
if tag != correctTag {
|
||||||
return fmt.Errorf("tag not equal: %v != %v", tag, correctTag)
|
return fmt.Errorf("tag not equal: %v != %v", tag, correctTag)
|
||||||
}
|
}
|
||||||
@ -58,7 +56,6 @@ func testEncodeDecodeAny(test *testing.T, value, correctValue any) error {
|
|||||||
test.Log("n: ", n)
|
test.Log("n: ", n)
|
||||||
test.Log("tag:", tag)
|
test.Log("tag:", tag)
|
||||||
test.Log("got:", tu.HexBytes(bytes))
|
test.Log("got:", tu.HexBytes(bytes))
|
||||||
test.Log(" :", tu.HexChars(bytes))
|
|
||||||
test.Log("decoding...", tag)
|
test.Log("decoding...", tag)
|
||||||
if n != len(bytes) {
|
if n != len(bytes) {
|
||||||
return fmt.Errorf("n not equal: %d != %d", n, len(bytes))
|
return fmt.Errorf("n not equal: %d != %d", n, len(bytes))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user