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("// arpack \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 }