This commit is contained in:
2026-03-19 14:52:12 +03:00
commit c5aca289a5
15 changed files with 3116 additions and 0 deletions
+478
View File
@@ -0,0 +1,478 @@
package generator
import (
"edmand46/arpack/parser"
"fmt"
"strings"
)
// GenerateCSharp генерирует C# unsafe код для списка сообщений.
// namespace — пространство имён (например, "Ragono.Messages").
func GenerateCSharp(messages []parser.Message, namespace string) ([]byte, error) {
return GenerateCSharpSchema(parser.Schema{Messages: messages}, namespace)
}
// GenerateCSharpSchema генерирует C# код для полной схемы, включая enum-ы.
func GenerateCSharpSchema(schema parser.Schema, namespace string) ([]byte, error) {
messages := schema.Messages
var b strings.Builder
b.WriteString("// <auto-generated> arpack </auto-generated>\n")
b.WriteString("// Code generated by arpack. DO NOT EDIT.\n")
b.WriteString("#pragma warning disable CS8500\n\n")
b.WriteString("using System;\n")
if needsTextEncoding(messages) {
b.WriteString("using System.Text;\n")
}
b.WriteString("\n")
fmt.Fprintf(&b, "namespace %s\n{\n", namespace)
enumNames := make(map[string]struct{}, len(schema.Enums))
for _, enum := range schema.Enums {
enumNames[enum.Name] = struct{}{}
}
wroteSection := false
for _, enum := range schema.Enums {
writeCSharpEnum(&b, enum)
b.WriteString("\n")
wroteSection = true
}
for i, msg := range messages {
if err := writeCSharpMessage(&b, msg, enumNames); err != nil {
return nil, fmt.Errorf("message %s: %w", msg.Name, err)
}
if i < len(messages)-1 {
b.WriteString("\n")
} else if wroteSection {
// leave a single blank line between the last enum and the first struct only
}
}
b.WriteString("}\n")
return []byte(b.String()), nil
}
func writeCSharpEnum(b *strings.Builder, enum parser.Enum) {
fmt.Fprintf(b, " public enum %s : %s\n {\n", enum.Name, csharpEnumBaseType(enum))
for i, value := range enum.Values {
fmt.Fprintf(b, " %s = %s", csharpEnumValueName(enum.Name, value.Name), value.Value)
if i < len(enum.Values)-1 {
b.WriteString(",")
}
b.WriteString("\n")
}
b.WriteString(" }\n")
}
func writeCSharpMessage(b *strings.Builder, msg parser.Message, enumNames map[string]struct{}) error {
segs := segmentFields(msg.Fields)
b.WriteString(" public unsafe struct ")
b.WriteString(msg.Name)
b.WriteString("\n {\n")
// Поля
for _, f := range msg.Fields {
fmt.Fprintf(b, " public %s %s;\n", csharpTypeName(f, enumNames), f.Name)
}
b.WriteString("\n")
// Serialize
b.WriteString(" public int Serialize(byte* buffer)\n")
b.WriteString(" {\n")
b.WriteString(" byte* ptr = buffer;\n")
for i, seg := range segs {
if seg.single != nil {
if err := writeCSharpSerializeField(b, *seg.single, " ", enumNames); err != nil {
return err
}
} else {
writeCSharpBoolGroupSerialize(b, seg.bools, i, " ")
}
}
b.WriteString(" return (int)(ptr - buffer);\n")
b.WriteString(" }\n\n")
// Deserialize
fmt.Fprintf(b, " public static int Deserialize(byte* buffer, out %s msg)\n", msg.Name)
b.WriteString(" {\n")
b.WriteString(" byte* ptr = buffer;\n")
b.WriteString(" msg = default;\n")
for i, seg := range segs {
if seg.single != nil {
if err := writeCSharpDeserializeField(b, "msg", *seg.single, " ", enumNames); err != nil {
return err
}
} else {
writeCSharpBoolGroupDeserialize(b, "msg", seg.bools, i, " ")
}
}
b.WriteString(" return (int)(ptr - buffer);\n")
b.WriteString(" }\n")
b.WriteString(" }\n")
return nil
}
func writeCSharpBoolGroupSerialize(b *strings.Builder, bools []parser.Field, groupIdx int, indent string) {
varName := fmt.Sprintf("_boolByte%d", groupIdx)
fmt.Fprintf(b, "%sbyte %s = 0;\n", indent, varName)
for bit, f := range bools {
fmt.Fprintf(b, "%sif (%s) %s |= (byte)(1 << %d);\n", indent, f.Name, varName, bit)
}
fmt.Fprintf(b, "%s*ptr = %s; ptr++;\n", indent, varName)
}
func writeCSharpBoolGroupDeserialize(b *strings.Builder, recv string, bools []parser.Field, groupIdx int, indent string) {
varName := fmt.Sprintf("_boolByte%d", groupIdx)
fmt.Fprintf(b, "%sbyte %s = *ptr; ptr++;\n", indent, varName)
for bit, f := range bools {
fmt.Fprintf(b, "%s%s.%s = (%s & (1 << %d)) != 0;\n", indent, recv, f.Name, varName, bit)
}
}
// --- Serialize ---
func writeCSharpSerializeField(b *strings.Builder, f parser.Field, indent string, enumNames map[string]struct{}) error {
switch f.Kind {
case parser.KindPrimitive:
return writeCSharpSerializePrimitive(b, f.Name, f, indent, enumNames)
case parser.KindNested:
fmt.Fprintf(b, "%sptr += %s.Serialize(ptr);\n", indent, f.Name)
case parser.KindFixedArray:
iVar := "_i" + f.Name
fmt.Fprintf(b, "%sfor (int %s = 0; %s < %d; %s++)\n%s{\n",
indent, iVar, iVar, f.FixedLen, iVar, indent)
elemField := parser.Field{
Name: f.Name + "[" + iVar + "]",
Kind: f.Elem.Kind,
Primitive: f.Elem.Primitive,
NamedType: f.Elem.NamedType,
Quant: f.Elem.Quant,
TypeName: f.Elem.TypeName,
Elem: f.Elem.Elem,
FixedLen: f.Elem.FixedLen,
}
if err := writeCSharpSerializeField(b, elemField, indent+" ", enumNames); err != nil {
return err
}
fmt.Fprintf(b, "%s}\n", indent)
case parser.KindSlice:
fmt.Fprintf(b, "%s*(ushort*)ptr = (ushort)(%s?.Length ?? 0); ptr += 2;\n", indent, f.Name)
fmt.Fprintf(b, "%sif (%s != null)\n%s{\n", indent, f.Name, indent)
iVar := "_i" + f.Name
fmt.Fprintf(b, "%s for (int %s = 0; %s < %s.Length; %s++)\n%s {\n",
indent, iVar, iVar, f.Name, iVar, indent)
elemField := parser.Field{
Name: f.Name + "[" + iVar + "]",
Kind: f.Elem.Kind,
Primitive: f.Elem.Primitive,
NamedType: f.Elem.NamedType,
Quant: f.Elem.Quant,
TypeName: f.Elem.TypeName,
Elem: f.Elem.Elem,
FixedLen: f.Elem.FixedLen,
}
if err := writeCSharpSerializeField(b, elemField, indent+" ", enumNames); err != nil {
return err
}
fmt.Fprintf(b, "%s }\n%s}\n", indent, indent)
}
return nil
}
func writeCSharpSerializePrimitive(
b *strings.Builder,
access string,
f parser.Field,
indent string,
enumNames map[string]struct{},
) error {
if f.Quant != nil {
return writeCSharpSerializeQuant(b, access, f, indent)
}
valueExpr := csharpSerializeValueExpr(access, f, enumNames)
switch f.Primitive {
case parser.KindFloat32:
fmt.Fprintf(b, "%s*(float*)ptr = %s; ptr += 4;\n", indent, valueExpr)
case parser.KindFloat64:
fmt.Fprintf(b, "%s*(double*)ptr = %s; ptr += 8;\n", indent, valueExpr)
case parser.KindInt8:
fmt.Fprintf(b, "%s*(sbyte*)ptr = %s; ptr += 1;\n", indent, valueExpr)
case parser.KindUint8:
fmt.Fprintf(b, "%s*ptr = %s; ptr += 1;\n", indent, valueExpr)
case parser.KindBool:
fmt.Fprintf(b, "%s*ptr = (byte)(%s ? 1 : 0); ptr += 1;\n", indent, valueExpr)
case parser.KindInt16:
fmt.Fprintf(b, "%s*(short*)ptr = %s; ptr += 2;\n", indent, valueExpr)
case parser.KindUint16:
fmt.Fprintf(b, "%s*(ushort*)ptr = %s; ptr += 2;\n", indent, valueExpr)
case parser.KindInt32:
fmt.Fprintf(b, "%s*(int*)ptr = %s; ptr += 4;\n", indent, valueExpr)
case parser.KindUint32:
fmt.Fprintf(b, "%s*(uint*)ptr = %s; ptr += 4;\n", indent, valueExpr)
case parser.KindInt64:
fmt.Fprintf(b, "%s*(long*)ptr = %s; ptr += 8;\n", indent, valueExpr)
case parser.KindUint64:
fmt.Fprintf(b, "%s*(ulong*)ptr = %s; ptr += 8;\n", indent, valueExpr)
case parser.KindString:
// UTF-8: uint16 byteCount + bytes
lenVar := "_slen" + sanitizeVarName(access)
fmt.Fprintf(b, "%sint %s = %s != null ? Encoding.UTF8.GetByteCount(%s) : 0;\n",
indent, lenVar, valueExpr, valueExpr)
fmt.Fprintf(b, "%s*(ushort*)ptr = (ushort)%s; ptr += 2;\n", indent, lenVar)
fmt.Fprintf(b, "%sif (%s != null && %s > 0)\n%s{\n", indent, valueExpr, lenVar, indent)
fmt.Fprintf(b, "%s fixed (char* _chars%s = %s)\n%s {\n",
indent, sanitizeVarName(access), valueExpr, indent)
fmt.Fprintf(b, "%s Encoding.UTF8.GetBytes(_chars%s, %s.Length, ptr, %s);\n",
indent, sanitizeVarName(access), valueExpr, lenVar)
fmt.Fprintf(b, "%s }\n", indent)
fmt.Fprintf(b, "%s}\n", indent)
fmt.Fprintf(b, "%sptr += %s;\n", indent, lenVar)
}
return nil
}
func writeCSharpSerializeQuant(b *strings.Builder, access string, f parser.Field, indent string) error {
q := f.Quant
maxUint := q.MaxUint()
if q.Bits == 8 {
fmt.Fprintf(b, "%s*ptr = (byte)((%s - (%gf)) / (%gf - (%gf)) * %gf); ptr += 1;\n",
indent, access, q.Min, q.Max, q.Min, maxUint)
} else {
fmt.Fprintf(b, "%s*(ushort*)ptr = (ushort)((%s - (%gf)) / (%gf - (%gf)) * %gf); ptr += 2;\n",
indent, access, q.Min, q.Max, q.Min, maxUint)
}
return nil
}
// --- Deserialize ---
func writeCSharpDeserializeField(
b *strings.Builder,
recv string,
f parser.Field,
indent string,
enumNames map[string]struct{},
) error {
access := recv + "." + f.Name
switch f.Kind {
case parser.KindPrimitive:
return writeCSharpDeserializePrimitive(b, access, f, indent, enumNames)
case parser.KindNested:
fmt.Fprintf(b, "%sptr += %s.Deserialize(ptr, out %s);\n", indent, f.TypeName, access)
case parser.KindFixedArray:
iVar := "_i" + f.Name
fmt.Fprintf(b, "%s%s = new %s[%d];\n", indent, access, csharpTypeName(*f.Elem, enumNames), f.FixedLen)
fmt.Fprintf(b, "%sfor (int %s = 0; %s < %d; %s++)\n%s{\n",
indent, iVar, iVar, f.FixedLen, iVar, indent)
elemField := parser.Field{
Name: f.Name + "[" + iVar + "]",
Kind: f.Elem.Kind,
Primitive: f.Elem.Primitive,
NamedType: f.Elem.NamedType,
Quant: f.Elem.Quant,
TypeName: f.Elem.TypeName,
Elem: f.Elem.Elem,
FixedLen: f.Elem.FixedLen,
}
if err := writeCSharpDeserializeField(b, recv, elemField, indent+" ", enumNames); err != nil {
return err
}
fmt.Fprintf(b, "%s}\n", indent)
case parser.KindSlice:
lenVar := "_len" + sanitizeVarName(f.Name)
fmt.Fprintf(b, "%sint %s = *(ushort*)ptr; ptr += 2;\n", indent, lenVar)
fmt.Fprintf(b, "%s%s = new %s[%s];\n", indent, access, csharpTypeName(*f.Elem, enumNames), lenVar)
iVar := "_i" + sanitizeVarName(f.Name)
fmt.Fprintf(b, "%sfor (int %s = 0; %s < %s; %s++)\n%s{\n",
indent, iVar, iVar, lenVar, iVar, indent)
elemField := parser.Field{
Name: f.Name + "[" + iVar + "]",
Kind: f.Elem.Kind,
Primitive: f.Elem.Primitive,
NamedType: f.Elem.NamedType,
Quant: f.Elem.Quant,
TypeName: f.Elem.TypeName,
Elem: f.Elem.Elem,
FixedLen: f.Elem.FixedLen,
}
if err := writeCSharpDeserializeField(b, recv, elemField, indent+" ", enumNames); err != nil {
return err
}
fmt.Fprintf(b, "%s}\n", indent)
}
return nil
}
func writeCSharpDeserializePrimitive(
b *strings.Builder,
access string,
f parser.Field,
indent string,
enumNames map[string]struct{},
) error {
if f.Quant != nil {
return writeCSharpDeserializeQuant(b, access, f, indent)
}
switch f.Primitive {
case parser.KindFloat32:
fmt.Fprintf(b, "%s%s = %s; ptr += 4;\n", indent, access,
csharpDeserializeValueExpr("*(float*)ptr", f, enumNames))
case parser.KindFloat64:
fmt.Fprintf(b, "%s%s = %s; ptr += 8;\n", indent, access,
csharpDeserializeValueExpr("*(double*)ptr", f, enumNames))
case parser.KindInt8:
fmt.Fprintf(b, "%s%s = %s; ptr += 1;\n", indent, access,
csharpDeserializeValueExpr("*(sbyte*)ptr", f, enumNames))
case parser.KindUint8:
fmt.Fprintf(b, "%s%s = %s; ptr += 1;\n", indent, access,
csharpDeserializeValueExpr("*ptr", f, enumNames))
case parser.KindBool:
fmt.Fprintf(b, "%s%s = %s; ptr += 1;\n", indent, access,
csharpDeserializeValueExpr("*ptr != 0", f, enumNames))
case parser.KindInt16:
fmt.Fprintf(b, "%s%s = %s; ptr += 2;\n", indent, access,
csharpDeserializeValueExpr("*(short*)ptr", f, enumNames))
case parser.KindUint16:
fmt.Fprintf(b, "%s%s = %s; ptr += 2;\n", indent, access,
csharpDeserializeValueExpr("*(ushort*)ptr", f, enumNames))
case parser.KindInt32:
fmt.Fprintf(b, "%s%s = %s; ptr += 4;\n", indent, access,
csharpDeserializeValueExpr("*(int*)ptr", f, enumNames))
case parser.KindUint32:
fmt.Fprintf(b, "%s%s = %s; ptr += 4;\n", indent, access,
csharpDeserializeValueExpr("*(uint*)ptr", f, enumNames))
case parser.KindInt64:
fmt.Fprintf(b, "%s%s = %s; ptr += 8;\n", indent, access,
csharpDeserializeValueExpr("*(long*)ptr", f, enumNames))
case parser.KindUint64:
fmt.Fprintf(b, "%s%s = %s; ptr += 8;\n", indent, access,
csharpDeserializeValueExpr("*(ulong*)ptr", f, enumNames))
case parser.KindString:
lenVar := "_slen" + sanitizeVarName(access)
fmt.Fprintf(b, "%sint %s = *(ushort*)ptr; ptr += 2;\n", indent, lenVar)
expr := fmt.Sprintf("%s > 0 ? Encoding.UTF8.GetString(ptr, %s) : string.Empty", lenVar, lenVar)
fmt.Fprintf(b, "%s%s = %s;\n", indent, access, csharpDeserializeValueExpr(expr, f, enumNames))
fmt.Fprintf(b, "%sptr += %s;\n", indent, lenVar)
}
return nil
}
func writeCSharpDeserializeQuant(b *strings.Builder, access string, f parser.Field, indent string) error {
q := f.Quant
maxUint := q.MaxUint()
if q.Bits == 8 {
if f.Primitive == parser.KindFloat32 {
fmt.Fprintf(b, "%s%s = (float)(*ptr) / %gf * (%gf - (%gf)) + (%gf); ptr += 1;\n",
indent, access, maxUint, q.Max, q.Min, q.Min)
} else {
fmt.Fprintf(b, "%s%s = (double)(*ptr) / %g * (%g - (%g)) + (%g); ptr += 1;\n",
indent, access, maxUint, q.Max, q.Min, q.Min)
}
} else {
if f.Primitive == parser.KindFloat32 {
fmt.Fprintf(b, "%s%s = (float)(*(ushort*)ptr) / %gf * (%gf - (%gf)) + (%gf); ptr += 2;\n",
indent, access, maxUint, q.Max, q.Min, q.Min)
} else {
fmt.Fprintf(b, "%s%s = (double)(*(ushort*)ptr) / %g * (%g - (%g)) + (%g); ptr += 2;\n",
indent, access, maxUint, q.Max, q.Min, q.Min)
}
}
return nil
}
func needsTextEncoding(messages []parser.Message) bool {
for _, msg := range messages {
for _, f := range msg.Fields {
if fieldNeedsEncoding(f) {
return true
}
}
}
return false
}
func fieldNeedsEncoding(f parser.Field) bool {
switch f.Kind {
case parser.KindPrimitive:
return f.Primitive == parser.KindString
case parser.KindFixedArray, parser.KindSlice:
if f.Elem != nil {
return fieldNeedsEncoding(*f.Elem)
}
}
return false
}
func csharpTypeName(f parser.Field, enumNames map[string]struct{}) string {
switch f.Kind {
case parser.KindPrimitive:
if csharpIsEnumType(f, enumNames) {
return f.NamedType
}
return f.CSharpPrimitiveTypeName()
case parser.KindNested:
return f.TypeName
case parser.KindFixedArray:
return csharpTypeName(*f.Elem, enumNames) + "[]"
case parser.KindSlice:
return csharpTypeName(*f.Elem, enumNames) + "[]"
}
return "unknown"
}
func csharpSerializeValueExpr(access string, f parser.Field, enumNames map[string]struct{}) string {
if !csharpIsEnumType(f, enumNames) {
return access
}
return "(" + f.CSharpPrimitiveTypeName() + ")" + access
}
func csharpDeserializeValueExpr(expr string, f parser.Field, enumNames map[string]struct{}) string {
if !csharpIsEnumType(f, enumNames) {
return expr
}
return "(" + f.NamedType + ")(" + expr + ")"
}
func csharpIsEnumType(f parser.Field, enumNames map[string]struct{}) bool {
if f.NamedType == "" {
return false
}
_, ok := enumNames[f.NamedType]
return ok
}
func csharpEnumBaseType(enum parser.Enum) string {
field := parser.Field{Primitive: enum.Primitive}
return field.CSharpPrimitiveTypeName()
}
func csharpEnumValueName(enumName, valueName string) string {
if !strings.HasPrefix(valueName, enumName) || len(valueName) == len(enumName) {
return valueName
}
suffix := valueName[len(enumName):]
if suffix == "" {
return valueName
}
if suffix[0] == '_' {
suffix = suffix[1:]
}
if suffix == "" {
return valueName
}
first := suffix[0]
if !((first >= 'A' && first <= 'Z') || (first >= '0' && first <= '9') || first == '_') {
return valueName
}
return suffix
}
+382
View File
@@ -0,0 +1,382 @@
package generator
import (
"edmand46/arpack/parser"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
const samplePath = "../testdata/sample.go"
func TestGenerateGo_Compiles(t *testing.T) {
msgs, err := parser.ParseFile(samplePath)
if err != nil {
t.Fatalf("ParseFile: %v", err)
}
src, err := GenerateGo(msgs, "messages")
if err != nil {
t.Fatalf("GenerateGo: %v", err)
}
if len(src) == 0 {
t.Fatal("GenerateGo returned empty output")
}
// Проверяем что содержит нужные функции
code := string(src)
for _, name := range []string{"Vector3", "MoveMessage", "SpawnMessage", "EnvelopeMessage"} {
if !strings.Contains(code, "func (m *"+name+") Marshal") {
t.Errorf("missing Marshal for %s", name)
}
if !strings.Contains(code, "func (m *"+name+") Unmarshal") {
t.Errorf("missing Unmarshal for %s", name)
}
}
t.Logf("Generated Go (%d bytes):\n%s", len(src), code)
}
func TestGenerateGo_RoundTrip(t *testing.T) {
msgs, err := parser.ParseFile(samplePath)
if err != nil {
t.Fatalf("ParseFile: %v", err)
}
src, err := GenerateGo(msgs, "messages")
if err != nil {
t.Fatalf("GenerateGo: %v", err)
}
// Записываем в temp dir вместе с оригинальными структурами и тестом round-trip
dir := t.TempDir()
// Копируем testdata/sample.go (определения структур)
sampleSrc, err := os.ReadFile(samplePath)
if err != nil {
t.Fatalf("ReadFile sample: %v", err)
}
if err := os.WriteFile(filepath.Join(dir, "messages.go"), sampleSrc, 0644); err != nil {
t.Fatal(err)
}
// Сохраняем сгенерированный код
if err := os.WriteFile(filepath.Join(dir, "messages_arpack.go"), src, 0644); err != nil {
t.Fatal(err)
}
// Пишем round-trip тест
roundTrip := `package messages
import (
"math"
"testing"
)
func TestRoundTrip_Vector3(t *testing.T) {
orig := Vector3{X: 123.45, Y: -200.0, Z: 0.0}
buf := orig.Marshal(nil)
var got Vector3
n, err := got.Unmarshal(buf)
if err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if n != len(buf) {
t.Errorf("consumed %d bytes, want %d", n, len(buf))
}
// Квантизация 16-бит даёт точность ≈ 1000/65535 ≈ 0.015
const eps = float32(0.02)
if math.Abs(float64(got.X-orig.X)) > float64(eps) {
t.Errorf("X: got %v, want %v", got.X, orig.X)
}
if math.Abs(float64(got.Y-orig.Y)) > float64(eps) {
t.Errorf("Y: got %v, want %v", got.Y, orig.Y)
}
}
func TestRoundTrip_SpawnMessage(t *testing.T) {
orig := SpawnMessage{
EntityID: 42,
Position: Vector3{X: 10, Y: 20, Z: 30},
Health: -100,
Tags: []string{"hero", "player"},
Data: []uint8{1, 2, 3},
}
buf := orig.Marshal(nil)
var got SpawnMessage
_, err := got.Unmarshal(buf)
if err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if got.EntityID != orig.EntityID {
t.Errorf("EntityID: got %d, want %d", got.EntityID, orig.EntityID)
}
if got.Health != orig.Health {
t.Errorf("Health: got %d, want %d", got.Health, orig.Health)
}
if len(got.Tags) != len(orig.Tags) {
t.Fatalf("Tags len: got %d, want %d", len(got.Tags), len(orig.Tags))
}
for i, tag := range orig.Tags {
if got.Tags[i] != tag {
t.Errorf("Tags[%d]: got %q, want %q", i, got.Tags[i], tag)
}
}
if len(got.Data) != len(orig.Data) {
t.Fatalf("Data len: got %d, want %d", len(got.Data), len(orig.Data))
}
}
func TestRoundTrip_MoveMessage(t *testing.T) {
orig := MoveMessage{
Position: Vector3{X: 100, Y: -50, Z: 0},
Velocity: [3]float32{1.5, -2.5, 0},
Waypoints: []Vector3{{X: 10, Y: 20, Z: 0}, {X: -10, Y: 0, Z: 100}},
PlayerID: 999,
Active: true,
Visible: false,
Ghost: true,
Name: "Alice",
}
buf := orig.Marshal(nil)
var got MoveMessage
_, err := got.Unmarshal(buf)
if err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if got.PlayerID != orig.PlayerID {
t.Errorf("PlayerID: got %d, want %d", got.PlayerID, orig.PlayerID)
}
if got.Name != orig.Name {
t.Errorf("Name: got %q, want %q", got.Name, orig.Name)
}
if got.Active != orig.Active {
t.Errorf("Active: got %v, want %v", got.Active, orig.Active)
}
if got.Visible != orig.Visible {
t.Errorf("Visible: got %v, want %v", got.Visible, orig.Visible)
}
if got.Ghost != orig.Ghost {
t.Errorf("Ghost: got %v, want %v", got.Ghost, orig.Ghost)
}
if len(got.Waypoints) != len(orig.Waypoints) {
t.Fatalf("Waypoints len: got %d, want %d", len(got.Waypoints), len(orig.Waypoints))
}
}
func TestRoundTrip_EnvelopeMessage(t *testing.T) {
orig := EnvelopeMessage{
Code: OpcodeJoinRoom,
Counter: 7,
}
buf := orig.Marshal(nil)
var got EnvelopeMessage
_, err := got.Unmarshal(buf)
if err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if got.Code != orig.Code {
t.Errorf("Code: got %v, want %v", got.Code, orig.Code)
}
if got.Counter != orig.Counter {
t.Errorf("Counter: got %d, want %d", got.Counter, orig.Counter)
}
}
`
if err := os.WriteFile(filepath.Join(dir, "roundtrip_test.go"), []byte(roundTrip), 0644); err != nil {
t.Fatal(err)
}
// go.mod для temp пакета
goMod := "module messages\n\ngo 1.21\n"
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte(goMod), 0644); err != nil {
t.Fatal(err)
}
// Запускаем go test
cmd := exec.Command("go", "test", "./...")
cmd.Dir = dir
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("go test failed:\n%s", out)
}
t.Logf("go test output:\n%s", out)
}
func TestGenerateCSharp_Output(t *testing.T) {
schema, err := parser.ParseSchemaFile(samplePath)
if err != nil {
t.Fatalf("ParseSchemaFile: %v", err)
}
src, err := GenerateCSharpSchema(schema, "Ragono.Messages")
if err != nil {
t.Fatalf("GenerateCSharpSchema: %v", err)
}
code := string(src)
// Проверяем структуру выходного кода
for _, name := range []string{"Vector3", "MoveMessage", "SpawnMessage", "EnvelopeMessage"} {
if !strings.Contains(code, "public unsafe struct "+name) {
t.Errorf("missing struct %s", name)
}
if !strings.Contains(code, "public int Serialize") {
t.Errorf("missing Serialize in %s", name)
}
if !strings.Contains(code, "public static int Deserialize") {
t.Errorf("missing Deserialize in %s", name)
}
}
// Unsafe паттерны
if !strings.Contains(code, "*(ushort*)ptr") {
t.Error("missing unsafe ushort pointer cast")
}
if !strings.Contains(code, "byte* ptr = buffer") {
t.Error("missing byte* ptr pattern")
}
// Нет BinaryWriter
if strings.Contains(code, "BinaryWriter") || strings.Contains(code, "BinaryReader") {
t.Error("should not contain BinaryWriter/BinaryReader")
}
if !strings.Contains(code, "public enum Opcode : ushort") {
t.Error("missing Opcode enum")
}
if !strings.Contains(code, "Authorize = 1") || !strings.Contains(code, "JoinRoom = 2") {
t.Error("missing enum values for Opcode")
}
if !strings.Contains(code, "public Opcode Code;") {
t.Error("EnvelopeMessage.Code should use generated enum type")
}
t.Logf("Generated C# (%d bytes):\n%s", len(src), code)
}
func TestBoolPacking_GoCode(t *testing.T) {
msgs, err := parser.ParseFile(samplePath)
if err != nil {
t.Fatalf("ParseFile: %v", err)
}
src, err := GenerateGo(msgs, "messages")
if err != nil {
t.Fatalf("GenerateGo: %v", err)
}
code := string(src)
// Должны быть битовые операции
if !strings.Contains(code, "_boolByte") {
t.Error("missing bool bit-packing variable (_boolByte)")
}
if !strings.Contains(code, "|= 1 <<") {
t.Error("missing bit OR operation for bool packing")
}
if !strings.Contains(code, "&(1<<") {
t.Error("missing bit AND operation for bool unpacking")
}
// НЕ должно быть per-byte записи для bool-полей из MoveMessage
// (Active, Visible, Ghost упакованы в 1 байт)
if strings.Contains(code, "append(buf, 1)") {
t.Error("should not emit per-byte bool encoding for packed bools")
}
}
func TestBoolPacking_WireSize(t *testing.T) {
src := `package p
type Flags struct {
A bool
B bool
C bool
D bool
E bool
F bool
G bool
H bool
I bool
}
`
msgs, err := parser.ParseSource(src)
if err != nil {
t.Fatalf("ParseSource: %v", err)
}
// 9 bool: первые 8 → 1 байт, последний → 1 байт = 2 байта
got := packedMinWireSize(msgs[0].Fields)
if got != 2 {
t.Errorf("packedMinWireSize(9 consecutive bools): got %d, want 2", got)
}
src3 := `package p
type Flags3 struct {
A bool
B bool
C bool
}
`
msgs3, err := parser.ParseSource(src3)
if err != nil {
t.Fatalf("ParseSource: %v", err)
}
// 3 bool → 1 байт
got3 := packedMinWireSize(msgs3[0].Fields)
if got3 != 1 {
t.Errorf("packedMinWireSize(3 consecutive bools): got %d, want 1", got3)
}
}
func TestBoolPacking_SegmentFields(t *testing.T) {
src := `package p
type Msg struct {
A bool
B bool
ID uint32
C bool
}
`
msgs, err := parser.ParseSource(src)
if err != nil {
t.Fatalf("ParseSource: %v", err)
}
segs := segmentFields(msgs[0].Fields)
// Ожидаем: [bool-group(A,B)] [single(ID)] [bool-group(C)]
if len(segs) != 3 {
t.Fatalf("expected 3 segments, got %d", len(segs))
}
if len(segs[0].bools) != 2 {
t.Errorf("seg[0]: expected 2 bools, got %d", len(segs[0].bools))
}
if segs[1].single == nil || segs[1].single.Name != "ID" {
t.Errorf("seg[1]: expected single field ID")
}
if len(segs[2].bools) != 1 {
t.Errorf("seg[2]: expected 1 bool, got %d", len(segs[2].bools))
}
}
func TestBoolPacking_CSharpCode(t *testing.T) {
schema, err := parser.ParseSchemaFile(samplePath)
if err != nil {
t.Fatalf("ParseSchemaFile: %v", err)
}
src, err := GenerateCSharpSchema(schema, "Ragono.Messages")
if err != nil {
t.Fatalf("GenerateCSharpSchema: %v", err)
}
code := string(src)
if !strings.Contains(code, "_boolByte") {
t.Error("C#: missing bool bit-packing variable")
}
if !strings.Contains(code, "|= (byte)(1 <<") {
t.Error("C#: missing bit OR for bool packing")
}
if !strings.Contains(code, "& (1 <<") {
t.Error("C#: missing bit AND for bool unpacking")
}
}
+398
View File
@@ -0,0 +1,398 @@
package generator
import (
"edmand46/arpack/parser"
"fmt"
"go/format"
"strings"
)
// GenerateGo генерирует Go-код сериализации для списка сообщений.
// pkgName — имя пакета в котором будет сгенерированный файл.
func GenerateGo(messages []parser.Message, pkgName string) ([]byte, error) {
return GenerateGoSchema(parser.Schema{Messages: messages}, pkgName)
}
// GenerateGoSchema генерирует Go-код сериализации для полной схемы.
func GenerateGoSchema(schema parser.Schema, pkgName string) ([]byte, error) {
messages := schema.Messages
var b strings.Builder
b.WriteString("// Code generated by arpack. DO NOT EDIT.\n\n")
fmt.Fprintf(&b, "package %s\n\n", pkgName)
b.WriteString("import (\n")
b.WriteString("\t\"encoding/binary\"\n")
b.WriteString("\t\"errors\"\n")
if needsMathImport(messages) {
b.WriteString("\t\"math\"\n")
}
b.WriteString(")\n\n")
for _, msg := range messages {
if err := writeGoMessage(&b, msg); err != nil {
return nil, fmt.Errorf("message %s: %w", msg.Name, err)
}
}
src := b.String()
formatted, err := format.Source([]byte(src))
if err != nil {
return []byte(src), fmt.Errorf("go/format: %w\n\nSource:\n%s", err, src)
}
return formatted, nil
}
func writeGoMessage(b *strings.Builder, msg parser.Message) error {
segs := segmentFields(msg.Fields)
// Marshal
fmt.Fprintf(b, "func (m *%s) Marshal(buf []byte) []byte {\n", msg.Name)
for i, seg := range segs {
if seg.single != nil {
if err := writeGoMarshalField(b, "m", *seg.single, "\t"); err != nil {
return err
}
} else {
writeGoBoolGroupMarshal(b, "m", seg.bools, i, "\t")
}
}
b.WriteString("\treturn buf\n}\n\n")
// Unmarshal — возвращает кол-во потреблённых байт
fmt.Fprintf(b, "func (m *%s) Unmarshal(data []byte) (int, error) {\n", msg.Name)
minSize := packedMinWireSize(msg.Fields)
fmt.Fprintf(b, "\tif len(data) < %d {\n", minSize)
fmt.Fprintf(b, "\t\treturn 0, errors.New(\"arpack: buffer too short for %s\")\n", msg.Name)
b.WriteString("\t}\n")
b.WriteString("\toffset := 0\n")
for i, seg := range segs {
if seg.single != nil {
if err := writeGoUnmarshalField(b, "m", *seg.single, "\t"); err != nil {
return err
}
} else {
writeGoBoolGroupUnmarshal(b, "m", seg.bools, i, "\t")
}
}
b.WriteString("\treturn offset, nil\n}\n\n")
return nil
}
func writeGoBoolGroupMarshal(b *strings.Builder, recv string, bools []parser.Field, groupIdx int, indent string) {
varName := fmt.Sprintf("_boolByte%d", groupIdx)
fmt.Fprintf(b, "%svar %s uint8\n", indent, varName)
for bit, f := range bools {
fmt.Fprintf(b, "%sif %s.%s { %s |= 1 << %d }\n", indent, recv, f.Name, varName, bit)
}
fmt.Fprintf(b, "%sbuf = append(buf, %s)\n", indent, varName)
}
func writeGoBoolGroupUnmarshal(b *strings.Builder, recv string, bools []parser.Field, groupIdx int, indent string) {
varName := fmt.Sprintf("_boolByte%d", groupIdx)
fmt.Fprintf(b, "%sif len(data) < offset+1 { return 0, errors.New(\"arpack: buffer too short\") }\n", indent)
fmt.Fprintf(b, "%s%s := data[offset]; offset++\n", indent, varName)
for bit, f := range bools {
expr := fmt.Sprintf("%s&(1<<%d) != 0", varName, bit)
fmt.Fprintf(b, "%s%s.%s = %s\n", indent, recv, f.Name, goUnmarshalValueExpr(expr, f))
}
}
// --- Marshal ---
func writeGoMarshalField(b *strings.Builder, recv string, f parser.Field, indent string) error {
access := recv + "." + f.Name
switch f.Kind {
case parser.KindPrimitive:
return writeGoMarshalPrimitive(b, access, f, indent)
case parser.KindNested:
fmt.Fprintf(b, "%sbuf = %s.Marshal(buf)\n", indent, access)
case parser.KindFixedArray:
fmt.Fprintf(b, "%sfor _i%s := 0; _i%s < %d; _i%s++ {\n",
indent, f.Name, f.Name, f.FixedLen, f.Name)
elemField := parser.Field{
Name: f.Name + "[_i" + f.Name + "]",
Kind: f.Elem.Kind,
Primitive: f.Elem.Primitive,
NamedType: f.Elem.NamedType,
Quant: f.Elem.Quant,
TypeName: f.Elem.TypeName,
Elem: f.Elem.Elem,
FixedLen: f.Elem.FixedLen,
}
if err := writeGoMarshalField(b, recv, elemField, indent+"\t"); err != nil {
return err
}
fmt.Fprintf(b, "%s}\n", indent)
case parser.KindSlice:
fmt.Fprintf(b, "%sbuf = binary.LittleEndian.AppendUint16(buf, uint16(len(%s)))\n", indent, access)
fmt.Fprintf(b, "%sfor _i%s := range %s {\n", indent, f.Name, access)
elemField := parser.Field{
Name: f.Name + "[_i" + f.Name + "]",
Kind: f.Elem.Kind,
Primitive: f.Elem.Primitive,
NamedType: f.Elem.NamedType,
Quant: f.Elem.Quant,
TypeName: f.Elem.TypeName,
Elem: f.Elem.Elem,
FixedLen: f.Elem.FixedLen,
}
if err := writeGoMarshalField(b, recv, elemField, indent+"\t"); err != nil {
return err
}
fmt.Fprintf(b, "%s}\n", indent)
}
return nil
}
func writeGoMarshalPrimitive(b *strings.Builder, access string, f parser.Field, indent string) error {
if f.Quant != nil {
return writeGoMarshalQuant(b, access, f, indent)
}
valueExpr := goMarshalValueExpr(access, f)
switch f.Primitive {
case parser.KindFloat32:
fmt.Fprintf(b, "%sbuf = binary.LittleEndian.AppendUint32(buf, math.Float32bits(%s))\n", indent, valueExpr)
case parser.KindFloat64:
fmt.Fprintf(b, "%sbuf = binary.LittleEndian.AppendUint64(buf, math.Float64bits(%s))\n", indent, valueExpr)
case parser.KindInt8:
fmt.Fprintf(b, "%sbuf = append(buf, uint8(%s))\n", indent, valueExpr)
case parser.KindUint8:
fmt.Fprintf(b, "%sbuf = append(buf, %s)\n", indent, valueExpr)
case parser.KindBool:
fmt.Fprintf(b, "%sif %s { buf = append(buf, 1) } else { buf = append(buf, 0) }\n", indent, valueExpr)
case parser.KindInt16:
fmt.Fprintf(b, "%sbuf = binary.LittleEndian.AppendUint16(buf, uint16(%s))\n", indent, valueExpr)
case parser.KindUint16:
fmt.Fprintf(b, "%sbuf = binary.LittleEndian.AppendUint16(buf, %s)\n", indent, valueExpr)
case parser.KindInt32:
fmt.Fprintf(b, "%sbuf = binary.LittleEndian.AppendUint32(buf, uint32(%s))\n", indent, valueExpr)
case parser.KindUint32:
fmt.Fprintf(b, "%sbuf = binary.LittleEndian.AppendUint32(buf, %s)\n", indent, valueExpr)
case parser.KindInt64:
fmt.Fprintf(b, "%sbuf = binary.LittleEndian.AppendUint64(buf, uint64(%s))\n", indent, valueExpr)
case parser.KindUint64:
fmt.Fprintf(b, "%sbuf = binary.LittleEndian.AppendUint64(buf, %s)\n", indent, valueExpr)
case parser.KindString:
fmt.Fprintf(b, "%sbuf = binary.LittleEndian.AppendUint16(buf, uint16(len(%s)))\n", indent, valueExpr)
fmt.Fprintf(b, "%sbuf = append(buf, %s...)\n", indent, valueExpr)
}
return nil
}
func writeGoMarshalQuant(b *strings.Builder, access string, f parser.Field, indent string) error {
q := f.Quant
varName := "_q" + sanitizeVarName(access)
valueExpr := goMarshalValueExpr(access, f)
if q.Bits == 8 {
fmt.Fprintf(b, "%s%s := uint8((%s - (%g)) / (%g - (%g)) * %g)\n",
indent, varName, valueExpr, q.Min, q.Max, q.Min, q.MaxUint())
fmt.Fprintf(b, "%sbuf = append(buf, %s)\n", indent, varName)
} else {
fmt.Fprintf(b, "%s%s := uint16((%s - (%g)) / (%g - (%g)) * %g)\n",
indent, varName, valueExpr, q.Min, q.Max, q.Min, q.MaxUint())
fmt.Fprintf(b, "%sbuf = binary.LittleEndian.AppendUint16(buf, %s)\n", indent, varName)
}
return nil
}
// --- Unmarshal ---
func writeGoUnmarshalField(b *strings.Builder, recv string, f parser.Field, indent string) error {
access := recv + "." + f.Name
switch f.Kind {
case parser.KindPrimitive:
return writeGoUnmarshalPrimitive(b, access, f, indent)
case parser.KindNested:
nVar := "_n" + sanitizeVarName(f.Name)
fmt.Fprintf(b, "%s%s, _err := %s.Unmarshal(data[offset:])\n", indent, nVar, access)
fmt.Fprintf(b, "%sif _err != nil { return 0, _err }\n", indent)
fmt.Fprintf(b, "%soffset += %s\n", indent, nVar)
case parser.KindFixedArray:
iVar := "_i" + f.Name
fmt.Fprintf(b, "%sfor %s := 0; %s < %d; %s++ {\n", indent, iVar, iVar, f.FixedLen, iVar)
elemField := parser.Field{
Name: f.Name + "[" + iVar + "]",
Kind: f.Elem.Kind,
Primitive: f.Elem.Primitive,
NamedType: f.Elem.NamedType,
Quant: f.Elem.Quant,
TypeName: f.Elem.TypeName,
Elem: f.Elem.Elem,
FixedLen: f.Elem.FixedLen,
}
if err := writeGoUnmarshalField(b, recv, elemField, indent+"\t"); err != nil {
return err
}
fmt.Fprintf(b, "%s}\n", indent)
case parser.KindSlice:
lenVar := "_len" + sanitizeVarName(f.Name)
fmt.Fprintf(b, "%sif len(data) < offset+2 { return 0, errors.New(\"arpack: buffer too short\") }\n", indent)
fmt.Fprintf(b, "%s%s := int(binary.LittleEndian.Uint16(data[offset:]))\n", indent, lenVar)
fmt.Fprintf(b, "%soffset += 2\n", indent)
fmt.Fprintf(b, "%s%s = make(%s, %s)\n", indent, access, f.GoTypeName(), lenVar)
iVar := "_i" + sanitizeVarName(f.Name)
fmt.Fprintf(b, "%sfor %s := 0; %s < %s; %s++ {\n", indent, iVar, iVar, lenVar, iVar)
elemField := parser.Field{
Name: f.Name + "[" + iVar + "]",
Kind: f.Elem.Kind,
Primitive: f.Elem.Primitive,
NamedType: f.Elem.NamedType,
Quant: f.Elem.Quant,
TypeName: f.Elem.TypeName,
Elem: f.Elem.Elem,
FixedLen: f.Elem.FixedLen,
}
if err := writeGoUnmarshalField(b, recv, elemField, indent+"\t"); err != nil {
return err
}
fmt.Fprintf(b, "%s}\n", indent)
}
return nil
}
func writeGoUnmarshalPrimitive(b *strings.Builder, access string, f parser.Field, indent string) error {
if f.Quant != nil {
return writeGoUnmarshalQuant(b, access, f, indent)
}
switch f.Primitive {
case parser.KindFloat32:
fmt.Fprintf(b, "%sif len(data) < offset+4 { return 0, errors.New(\"arpack: buffer too short\") }\n", indent)
fmt.Fprintf(b, "%s%s = %s\n", indent, access, goUnmarshalValueExpr("math.Float32frombits(binary.LittleEndian.Uint32(data[offset:]))", f))
fmt.Fprintf(b, "%soffset += 4\n", indent)
case parser.KindFloat64:
fmt.Fprintf(b, "%sif len(data) < offset+8 { return 0, errors.New(\"arpack: buffer too short\") }\n", indent)
fmt.Fprintf(b, "%s%s = %s\n", indent, access, goUnmarshalValueExpr("math.Float64frombits(binary.LittleEndian.Uint64(data[offset:]))", f))
fmt.Fprintf(b, "%soffset += 8\n", indent)
case parser.KindInt8:
fmt.Fprintf(b, "%sif len(data) < offset+1 { return 0, errors.New(\"arpack: buffer too short\") }\n", indent)
fmt.Fprintf(b, "%s%s = %s\n", indent, access, goUnmarshalValueExpr("int8(data[offset])", f))
fmt.Fprintf(b, "%soffset += 1\n", indent)
case parser.KindUint8:
fmt.Fprintf(b, "%sif len(data) < offset+1 { return 0, errors.New(\"arpack: buffer too short\") }\n", indent)
fmt.Fprintf(b, "%s%s = %s\n", indent, access, goUnmarshalValueExpr("data[offset]", f))
fmt.Fprintf(b, "%soffset += 1\n", indent)
case parser.KindBool:
fmt.Fprintf(b, "%sif len(data) < offset+1 { return 0, errors.New(\"arpack: buffer too short\") }\n", indent)
fmt.Fprintf(b, "%s%s = %s\n", indent, access, goUnmarshalValueExpr("data[offset] != 0", f))
fmt.Fprintf(b, "%soffset += 1\n", indent)
case parser.KindInt16:
fmt.Fprintf(b, "%sif len(data) < offset+2 { return 0, errors.New(\"arpack: buffer too short\") }\n", indent)
fmt.Fprintf(b, "%s%s = %s\n", indent, access, goUnmarshalValueExpr("int16(binary.LittleEndian.Uint16(data[offset:]))", f))
fmt.Fprintf(b, "%soffset += 2\n", indent)
case parser.KindUint16:
fmt.Fprintf(b, "%sif len(data) < offset+2 { return 0, errors.New(\"arpack: buffer too short\") }\n", indent)
fmt.Fprintf(b, "%s%s = %s\n", indent, access, goUnmarshalValueExpr("binary.LittleEndian.Uint16(data[offset:])", f))
fmt.Fprintf(b, "%soffset += 2\n", indent)
case parser.KindInt32:
fmt.Fprintf(b, "%sif len(data) < offset+4 { return 0, errors.New(\"arpack: buffer too short\") }\n", indent)
fmt.Fprintf(b, "%s%s = %s\n", indent, access, goUnmarshalValueExpr("int32(binary.LittleEndian.Uint32(data[offset:]))", f))
fmt.Fprintf(b, "%soffset += 4\n", indent)
case parser.KindUint32:
fmt.Fprintf(b, "%sif len(data) < offset+4 { return 0, errors.New(\"arpack: buffer too short\") }\n", indent)
fmt.Fprintf(b, "%s%s = %s\n", indent, access, goUnmarshalValueExpr("binary.LittleEndian.Uint32(data[offset:])", f))
fmt.Fprintf(b, "%soffset += 4\n", indent)
case parser.KindInt64:
fmt.Fprintf(b, "%sif len(data) < offset+8 { return 0, errors.New(\"arpack: buffer too short\") }\n", indent)
fmt.Fprintf(b, "%s%s = %s\n", indent, access, goUnmarshalValueExpr("int64(binary.LittleEndian.Uint64(data[offset:]))", f))
fmt.Fprintf(b, "%soffset += 8\n", indent)
case parser.KindUint64:
fmt.Fprintf(b, "%sif len(data) < offset+8 { return 0, errors.New(\"arpack: buffer too short\") }\n", indent)
fmt.Fprintf(b, "%s%s = %s\n", indent, access, goUnmarshalValueExpr("binary.LittleEndian.Uint64(data[offset:])", f))
fmt.Fprintf(b, "%soffset += 8\n", indent)
case parser.KindString:
lenVar := "_slen" + sanitizeVarName(access)
fmt.Fprintf(b, "%sif len(data) < offset+2 { return 0, errors.New(\"arpack: buffer too short\") }\n", indent)
fmt.Fprintf(b, "%s%s := int(binary.LittleEndian.Uint16(data[offset:]))\n", indent, lenVar)
fmt.Fprintf(b, "%soffset += 2\n", indent)
fmt.Fprintf(b, "%sif len(data) < offset+%s { return 0, errors.New(\"arpack: buffer too short\") }\n", indent, lenVar)
fmt.Fprintf(b, "%s%s = %s\n", indent, access, goUnmarshalValueExpr("string(data[offset : offset+"+lenVar+"])",
f))
fmt.Fprintf(b, "%soffset += %s\n", indent, lenVar)
}
return nil
}
func writeGoUnmarshalQuant(b *strings.Builder, access string, f parser.Field, indent string) error {
q := f.Quant
varName := "_q" + sanitizeVarName(access)
maxUint := q.MaxUint()
if q.Bits == 8 {
fmt.Fprintf(b, "%sif len(data) < offset+1 { return 0, errors.New(\"arpack: buffer too short\") }\n", indent)
fmt.Fprintf(b, "%s%s := data[offset]\n", indent, varName)
fmt.Fprintf(b, "%soffset += 1\n", indent)
if f.Primitive == parser.KindFloat32 {
expr := fmt.Sprintf("float32(%s) / %g * (%g - (%g)) + (%g)", varName, maxUint, q.Max, q.Min, q.Min)
fmt.Fprintf(b, "%s%s = %s\n", indent, access, goUnmarshalValueExpr(expr, f))
} else {
expr := fmt.Sprintf("float64(%s) / %g * (%g - (%g)) + (%g)", varName, maxUint, q.Max, q.Min, q.Min)
fmt.Fprintf(b, "%s%s = %s\n", indent, access, goUnmarshalValueExpr(expr, f))
}
} else {
fmt.Fprintf(b, "%sif len(data) < offset+2 { return 0, errors.New(\"arpack: buffer too short\") }\n", indent)
fmt.Fprintf(b, "%s%s := binary.LittleEndian.Uint16(data[offset:])\n", indent, varName)
fmt.Fprintf(b, "%soffset += 2\n", indent)
if f.Primitive == parser.KindFloat32 {
expr := fmt.Sprintf("float32(%s) / %g * (%g - (%g)) + (%g)", varName, maxUint, q.Max, q.Min, q.Min)
fmt.Fprintf(b, "%s%s = %s\n", indent, access, goUnmarshalValueExpr(expr, f))
} else {
expr := fmt.Sprintf("float64(%s) / %g * (%g - (%g)) + (%g)", varName, maxUint, q.Max, q.Min, q.Min)
fmt.Fprintf(b, "%s%s = %s\n", indent, access, goUnmarshalValueExpr(expr, f))
}
}
return nil
}
func goMarshalValueExpr(access string, f parser.Field) string {
if f.NamedType == "" {
return access
}
return f.GoPrimitiveTypeName() + "(" + access + ")"
}
func goUnmarshalValueExpr(expr string, f parser.Field) string {
if f.NamedType == "" {
return expr
}
return f.NamedType + "(" + expr + ")"
}
// sanitizeVarName превращает "m.Pos[_i]" в "_mPos_i".
func sanitizeVarName(s string) string {
var b strings.Builder
for _, c := range s {
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') {
b.WriteRune(c)
} else {
b.WriteRune('_')
}
}
return b.String()
}
func needsMathImport(messages []parser.Message) bool {
for _, msg := range messages {
for _, f := range msg.Fields {
if needsMathField(f) {
return true
}
}
}
return false
}
func needsMathField(f parser.Field) bool {
switch f.Kind {
case parser.KindPrimitive:
return f.Quant == nil && (f.Primitive == parser.KindFloat32 || f.Primitive == parser.KindFloat64)
case parser.KindFixedArray, parser.KindSlice:
if f.Elem != nil {
return needsMathField(*f.Elem)
}
}
return false
}
+58
View File
@@ -0,0 +1,58 @@
package generator
import "edmand46/arpack/parser"
// segment — либо группа bool (1–8 полей → 1 байт), либо одиночное поле.
type segment struct {
bools []parser.Field // non-empty: bool-группа
single *parser.Field // non-nil: любое не-bool поле
}
// isBoolField возвращает true если поле — нативный bool (не массив, не слайс).
func isBoolField(f parser.Field) bool {
return f.Kind == parser.KindPrimitive && f.Primitive == parser.KindBool
}
// segmentFields разбивает поля структуры на сегменты.
// Последовательные bool-поля группируются по 8 в один сегмент.
func segmentFields(fields []parser.Field) []segment {
var segs []segment
i := 0
for i < len(fields) {
if !isBoolField(fields[i]) {
f := fields[i]
segs = append(segs, segment{single: &f})
i++
continue
}
// Собираем последовательные bool-поля группами по 8
for i < len(fields) && isBoolField(fields[i]) {
var group []parser.Field
for i < len(fields) && isBoolField(fields[i]) && len(group) < 8 {
group = append(group, fields[i])
i++
}
segs = append(segs, segment{bools: group})
}
}
return segs
}
// packedMinWireSize вычисляет минимальный размер буфера с учётом упаковки bool.
func packedMinWireSize(fields []parser.Field) int {
total := 0
for _, seg := range segmentFields(fields) {
if seg.single != nil {
s := seg.single.WireSize()
if s == -1 {
total += 2
} else {
total += s
}
} else {
// Группа bool → 1 байт
total += 1
}
}
return total
}