Files
arpack/generator/csharp.go
T

465 lines
15 KiB
Go
Raw Normal View History

2026-03-19 14:52:12 +03:00
package generator
import (
"fmt"
"strings"
2026-03-23 16:19:17 +03:00
"github.com/edmand46/arpack/parser"
2026-03-19 14:52:12 +03:00
)
func GenerateCSharp(messages []parser.Message, namespace string) ([]byte, error) {
return GenerateCSharpSchema(parser.Schema{Messages: messages}, namespace)
}
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{}{}
}
for _, enum := range schema.Enums {
writeCSharpEnum(&b, enum)
b.WriteString("\n")
}
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")
}
}
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")
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")
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)
}
}
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:
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
}
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]
2026-03-23 16:19:17 +03:00
if (first < 'A' || first > 'Z') && (first < '0' || first > '9') && first != '_' {
2026-03-19 14:52:12 +03:00
return valueName
}
return suffix
}