479 lines
16 KiB
Go
479 lines
16 KiB
Go
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
|
|
}
|