Files
arpack/generator/c.go
T

1047 lines
39 KiB
Go
Raw Normal View History

2026-03-25 19:20:25 +03:00
package generator
import (
"fmt"
"sort"
"strings"
"github.com/edmand46/arpack/parser"
)
func collectEnumTypes(enums []parser.Enum) map[string]struct{} {
enumTypes := make(map[string]struct{}, len(enums))
for _, enum := range enums {
if len(enum.Values) == 0 {
continue
}
enumTypes[enum.Name] = struct{}{}
}
return enumTypes
}
func validateCSchema(schema parser.Schema) error {
msgIndex := make(map[string]parser.Message, len(schema.Messages))
for _, msg := range schema.Messages {
msgIndex[msg.Name] = msg
}
for _, msg := range schema.Messages {
for _, field := range msg.Fields {
if err := validateCField(msg.Name, field, msgIndex, false); err != nil {
return err
}
}
}
return nil
}
func validateCField(msgName string, field parser.Field, msgIndex map[string]parser.Message, insideSlice bool) error {
switch field.Kind {
case parser.KindNested:
nested, ok := msgIndex[field.TypeName]
if !ok {
return fmt.Errorf("c target: unknown nested message %q in %s", field.TypeName, msgName)
}
if hasNonByteSlices(&nested) {
return fmt.Errorf(
"c target does not support nested message %s in %s because nested decode contexts are not implemented",
field.TypeName,
msgName,
)
}
for _, nestedField := range nested.Fields {
if err := validateCField(nested.Name, nestedField, msgIndex, false); err != nil {
return err
}
}
case parser.KindFixedArray:
if field.Elem == nil {
return fmt.Errorf("c target: fixed array %s in %s has nil element", field.Name, msgName)
}
if field.Elem.Kind == parser.KindSlice {
return fmt.Errorf("c target does not support fixed arrays of slices for field %s in %s", field.Name, msgName)
}
return validateCField(msgName, *field.Elem, msgIndex, insideSlice)
case parser.KindSlice:
if field.Elem == nil {
return fmt.Errorf("c target: slice %s in %s has nil element", field.Name, msgName)
}
if field.Elem.Kind == parser.KindSlice || field.Elem.Kind == parser.KindFixedArray {
return fmt.Errorf(
"c target does not support slice element kind %d for field %s in %s",
field.Elem.Kind,
field.Name,
msgName,
)
}
return validateCField(msgName, *field.Elem, msgIndex, true)
}
return nil
}
type cSliceViewDef struct {
Name string
ElemType string
}
func collectSliceViewTypes(messages []parser.Message, baseName string, enumTypes map[string]struct{}) ([]cSliceViewDef, bool) {
viewTypes := make(map[string]string)
needStringViewSlice := false
var collectFields func(fields []parser.Field)
collectFields = func(fields []parser.Field) {
for _, field := range fields {
switch field.Kind {
case parser.KindSlice:
viewType, _ := cFieldTypeInfo(&field, baseName, enumTypes)
if viewType == "arpack_string_view_slice_view" {
needStringViewSlice = true
} else if viewType != "arpack_bytes_view" && viewType != "arpack_string_view_slice_view" {
elemType, _ := cFieldTypeInfo(field.Elem, baseName, enumTypes)
viewTypes[viewType] = elemType
}
if field.Elem != nil {
collectFields([]parser.Field{*field.Elem})
}
case parser.KindFixedArray:
if field.Elem != nil {
collectFields([]parser.Field{*field.Elem})
}
case parser.KindNested:
// Nested messages don't need special handling here
}
}
}
for _, msg := range messages {
collectFields(msg.Fields)
}
names := make([]string, 0, len(viewTypes))
for viewType := range viewTypes {
names = append(names, viewType)
}
sort.Strings(names)
result := make([]cSliceViewDef, 0, len(names))
for _, name := range names {
result = append(result, cSliceViewDef{Name: name, ElemType: viewTypes[name]})
}
return result, needStringViewSlice
}
func GenerateCSchema(schema parser.Schema, baseName string) (header []byte, source []byte, err error) {
if err := validateCSchema(schema); err != nil {
return nil, nil, err
}
messages := schema.Messages
enums := schema.Enums
enumTypes := collectEnumTypes(enums)
var headerBuilder strings.Builder
var sourceBuilder strings.Builder
// Write file headers
headerGuard := strings.ToUpper(baseName) + "_GEN_H"
headerBuilder.WriteString("// Code generated by arpack. DO NOT EDIT.\n\n")
headerBuilder.WriteString(fmt.Sprintf("#ifndef %s\n", headerGuard))
headerBuilder.WriteString(fmt.Sprintf("#define %s\n\n", headerGuard))
sourceBuilder.WriteString("// Code generated by arpack. DO NOT EDIT.\n\n")
sourceBuilder.WriteString(fmt.Sprintf("#include \"%s.gen.h\"\n\n", baseName))
// Write private runtime helpers
writeCRuntimeHelpers(&sourceBuilder)
// Write includes in header
headerBuilder.WriteString("#include <stdint.h>\n")
headerBuilder.WriteString("#include <stddef.h>\n")
headerBuilder.WriteString("#include <stdbool.h>\n\n")
// Write shared runtime types
headerBuilder.WriteString("typedef enum arpack_status {\n")
headerBuilder.WriteString(" ARPACK_OK = 0,\n")
headerBuilder.WriteString(" ARPACK_ERR_BUFFER_TOO_SHORT = 1,\n")
headerBuilder.WriteString(" ARPACK_ERR_LENGTH_OVERFLOW = 2,\n")
headerBuilder.WriteString(" ARPACK_ERR_INVALID_ARGUMENT = 3,\n")
headerBuilder.WriteString(" ARPACK_ERR_CAPACITY_TOO_SMALL = 4\n")
headerBuilder.WriteString("} arpack_status;\n\n")
headerBuilder.WriteString("typedef struct arpack_string_view {\n")
headerBuilder.WriteString(" const char *data;\n")
headerBuilder.WriteString(" uint16_t len;\n")
headerBuilder.WriteString("} arpack_string_view;\n\n")
headerBuilder.WriteString("typedef struct arpack_bytes_view {\n")
headerBuilder.WriteString(" const uint8_t *data;\n")
headerBuilder.WriteString(" uint16_t len;\n")
headerBuilder.WriteString("} arpack_bytes_view;\n\n")
// Collect slice view types needed
sliceViewTypes, needStringViewSlice := collectSliceViewTypes(messages, baseName, enumTypes)
// Forward declare message typedefs (for slice views that reference them)
for _, msg := range messages {
msgName := baseName + "_" + snakeCase(msg.Name)
headerBuilder.WriteString(fmt.Sprintf("typedef struct %s %s;\n", msgName, msgName))
}
headerBuilder.WriteString("\n")
// Add arpack_string_view_slice_view if needed
if needStringViewSlice {
headerBuilder.WriteString("typedef struct arpack_string_view_slice_view {\n")
headerBuilder.WriteString(" const arpack_string_view *data;\n")
headerBuilder.WriteString(" uint16_t len;\n")
headerBuilder.WriteString("} arpack_string_view_slice_view;\n\n")
}
// Forward declare slice view typedefs
for _, viewType := range sliceViewTypes {
headerBuilder.WriteString(fmt.Sprintf("typedef struct %s %s;\n", viewType.Name, viewType.Name))
}
if len(sliceViewTypes) > 0 {
headerBuilder.WriteString("\n")
}
// Define slice view types
for _, viewType := range sliceViewTypes {
headerBuilder.WriteString(fmt.Sprintf("typedef struct %s {\n", viewType.Name))
headerBuilder.WriteString(fmt.Sprintf(" const %s *data;\n", viewType.ElemType))
headerBuilder.WriteString(" uint16_t len;\n")
headerBuilder.WriteString(fmt.Sprintf("} %s;\n\n", viewType.Name))
}
// Write enums
for _, enum := range enums {
if len(enum.Values) == 0 {
continue
}
writeCEnum(&headerBuilder, enum, baseName)
headerBuilder.WriteString("\n")
}
// Write message declarations
for _, msg := range messages {
writeCMessageDecl(&headerBuilder, msg, baseName, enumTypes)
headerBuilder.WriteString("\n")
}
// Write function declarations for fixed-size messages
for _, msg := range messages {
if !msg.HasVariableFields() {
writeCFixedSizeFuncDecls(&headerBuilder, msg, baseName)
headerBuilder.WriteString("\n")
}
}
// Write decode context declarations for messages with non-byte slices
for _, msg := range messages {
if hasNonByteSlices(&msg) {
writeCDecodeCtxDecl(&headerBuilder, msg, baseName, enumTypes)
headerBuilder.WriteString("\n")
}
}
// Write function declarations for variable-length messages
for _, msg := range messages {
if msg.HasVariableFields() {
writeCVariableSizeFuncDecls(&headerBuilder, msg, baseName)
headerBuilder.WriteString("\n")
}
}
// Close header guard
headerBuilder.WriteString(fmt.Sprintf("#endif // %s\n", headerGuard))
// Write function implementations for fixed-size messages
for _, msg := range messages {
if !msg.HasVariableFields() {
writeCFixedSizeFuncImpls(&sourceBuilder, msg, baseName, enumTypes)
sourceBuilder.WriteString("\n")
}
}
// Write function implementations for variable-length messages
for _, msg := range messages {
if msg.HasVariableFields() {
writeCVariableSizeFuncImpls(&sourceBuilder, msg, baseName, enumTypes)
sourceBuilder.WriteString("\n")
}
}
return []byte(headerBuilder.String()), []byte(sourceBuilder.String()), nil
}
func writeCEnum(b *strings.Builder, enum parser.Enum, baseName string) {
enumName := baseName + "_" + snakeCase(enum.Name)
b.WriteString(fmt.Sprintf("typedef enum %s {\n", enumName))
for _, value := range enum.Values {
valueName := baseName + "_" + snakeCase(enum.Name) + "_" + snakeCase(value.Name)
b.WriteString(fmt.Sprintf(" %s = %s,\n", valueName, value.Value))
}
b.WriteString(fmt.Sprintf("} %s;\n", enumName))
}
func writeCMessageDecl(b *strings.Builder, msg parser.Message, baseName string, enumTypes map[string]struct{}) {
msgName := baseName + "_" + snakeCase(msg.Name)
b.WriteString(fmt.Sprintf("typedef struct %s {\n", msgName))
for _, field := range msg.Fields {
typeStr, arraySuffix := cFieldTypeInfo(&field, baseName, enumTypes)
fieldDecl := typeStr + " " + snakeCase(field.Name) + arraySuffix + ";"
b.WriteString(fmt.Sprintf(" %s\n", fieldDecl))
}
b.WriteString(fmt.Sprintf("} %s;\n", msgName))
}
func writeCFixedSizeFuncDecls(b *strings.Builder, msg parser.Message, baseName string) {
msgName := baseName + "_" + snakeCase(msg.Name)
b.WriteString(fmt.Sprintf("size_t %s_min_size(void);\n", msgName))
b.WriteString(fmt.Sprintf("arpack_status %s_size(const %s *msg, size_t *out_size);\n", msgName, msgName))
b.WriteString(fmt.Sprintf("arpack_status %s_encode(const %s *msg, uint8_t *buf, size_t buf_len, size_t *out_written);\n", msgName, msgName))
b.WriteString(fmt.Sprintf("arpack_status %s_decode(%s *msg, const uint8_t *buf, size_t buf_len, size_t *out_read);\n", msgName, msgName))
}
func writeCFixedSizeFuncImpls(b *strings.Builder, msg parser.Message, baseName string, enumTypes map[string]struct{}) {
msgName := baseName + "_" + snakeCase(msg.Name)
minSize := msg.MinWireSize()
segs := segmentFields(msg.Fields)
// min_size function
b.WriteString(fmt.Sprintf("size_t %s_min_size(void) {\n", msgName))
b.WriteString(fmt.Sprintf(" return %d;\n", minSize))
b.WriteString("}\n\n")
// size function (same as min_size for fixed-size messages)
b.WriteString(fmt.Sprintf("arpack_status %s_size(const %s *msg, size_t *out_size) {\n", msgName, msgName))
b.WriteString(fmt.Sprintf(" *out_size = %d;\n", minSize))
b.WriteString(" return ARPACK_OK;\n")
b.WriteString("}\n\n")
// encode function
b.WriteString(fmt.Sprintf("arpack_status %s_encode(const %s *msg, uint8_t *buf, size_t buf_len, size_t *out_written) {\n", msgName, msgName))
b.WriteString(fmt.Sprintf(" if (buf_len < %d) return ARPACK_ERR_BUFFER_TOO_SHORT;\n", minSize))
b.WriteString(" size_t offset = 0;\n")
// Generate encode logic using segments for bool packing
for _, seg := range segs {
if seg.single != nil {
writeCFieldEncode(b, seg.single, "msg->"+snakeCase(seg.single.Name), baseName, enumTypes, " ", 0)
} else {
// Encode bool group
writeCBoolGroupEncode(b, "msg", seg.bools)
}
}
b.WriteString(" *out_written = offset;\n")
b.WriteString(" return ARPACK_OK;\n")
b.WriteString("}\n\n")
// decode function
b.WriteString(fmt.Sprintf("arpack_status %s_decode(%s *msg, const uint8_t *buf, size_t buf_len, size_t *out_read) {\n", msgName, msgName))
b.WriteString(fmt.Sprintf(" if (buf_len < %d) return ARPACK_ERR_BUFFER_TOO_SHORT;\n", minSize))
b.WriteString(" size_t offset = 0;\n")
// Generate decode logic using segments for bool packing
for _, seg := range segs {
if seg.single != nil {
writeCFieldDecode(b, seg.single, "msg->"+snakeCase(seg.single.Name), baseName, enumTypes, "", " ", 0)
} else {
// Decode bool group
writeCBoolGroupDecode(b, "msg", seg.bools)
}
}
b.WriteString(" *out_read = offset;\n")
b.WriteString(" return ARPACK_OK;\n")
b.WriteString("}\n")
}
func hasNonByteSlices(msg *parser.Message) bool {
for _, field := range msg.Fields {
if field.Kind == parser.KindSlice {
// []uint8 doesn't need context
if isRawUint8Element(field.Elem) {
continue
}
return true
}
}
return false
}
func writeCDecodeCtxDecl(b *strings.Builder, msg parser.Message, baseName string, enumTypes map[string]struct{}) {
ctxName := baseName + "_" + snakeCase(msg.Name) + "_decode_ctx"
b.WriteString(fmt.Sprintf("typedef struct %s {\n", ctxName))
for _, field := range msg.Fields {
if field.Kind == parser.KindSlice {
// Skip []uint8 - it uses bytes_view which doesn't need context
if isRawUint8Element(field.Elem) {
continue
}
// Generate context field for non-byte slices
fieldName := snakeCase(field.Name)
elemType, _ := cFieldTypeInfo(field.Elem, baseName, enumTypes)
b.WriteString(fmt.Sprintf(" %s *%s_data;\n", elemType, fieldName))
b.WriteString(fmt.Sprintf(" uint16_t %s_cap;\n", fieldName))
}
}
b.WriteString(fmt.Sprintf("} %s;\n", ctxName))
}
func writeCVariableSizeFuncDecls(b *strings.Builder, msg parser.Message, baseName string) {
msgName := baseName + "_" + snakeCase(msg.Name)
b.WriteString(fmt.Sprintf("size_t %s_min_size(void);\n", msgName))
b.WriteString(fmt.Sprintf("arpack_status %s_size(const %s *msg, size_t *out_size);\n", msgName, msgName))
b.WriteString(fmt.Sprintf("arpack_status %s_encode(const %s *msg, uint8_t *buf, size_t buf_len, size_t *out_written);\n", msgName, msgName))
if hasNonByteSlices(&msg) {
ctxName := msgName + "_decode_ctx"
b.WriteString(fmt.Sprintf("arpack_status %s_decode(%s *msg, const uint8_t *buf, size_t buf_len, %s *ctx, size_t *out_read);\n", msgName, msgName, ctxName))
} else {
b.WriteString(fmt.Sprintf("arpack_status %s_decode(%s *msg, const uint8_t *buf, size_t buf_len, size_t *out_read);\n", msgName, msgName))
}
}
func writeCVariableSizeFuncImpls(b *strings.Builder, msg parser.Message, baseName string, enumTypes map[string]struct{}) {
msgName := baseName + "_" + snakeCase(msg.Name)
minSize := msg.MinWireSize()
segs := segmentFields(msg.Fields)
hasNonByteSliceFields := hasNonByteSlices(&msg)
// min_size function
b.WriteString(fmt.Sprintf("size_t %s_min_size(void) {\n", msgName))
b.WriteString(fmt.Sprintf(" return %d;\n", minSize))
b.WriteString("}\n\n")
// size function
b.WriteString(fmt.Sprintf("arpack_status %s_size(const %s *msg, size_t *out_size) {\n", msgName, msgName))
b.WriteString(" size_t size = 0;\n")
for _, seg := range segs {
if seg.single != nil {
writeCFieldSize(b, seg.single, "msg->"+snakeCase(seg.single.Name), baseName, enumTypes, " ", 0)
} else {
b.WriteString(" size += 1;\n") // bool group is 1 byte
}
}
b.WriteString(" *out_size = size;\n")
b.WriteString(" return ARPACK_OK;\n")
b.WriteString("}\n\n")
// encode function
b.WriteString(fmt.Sprintf("arpack_status %s_encode(const %s *msg, uint8_t *buf, size_t buf_len, size_t *out_written) {\n", msgName, msgName))
b.WriteString(" size_t total_size;\n")
b.WriteString(fmt.Sprintf(" arpack_status status = %s_size(msg, &total_size);\n", msgName))
b.WriteString(" if (status != ARPACK_OK) return status;\n")
b.WriteString(" if (buf_len < total_size) return ARPACK_ERR_BUFFER_TOO_SHORT;\n")
b.WriteString(" size_t offset = 0;\n")
for _, seg := range segs {
if seg.single != nil {
writeCFieldEncode(b, seg.single, "msg->"+snakeCase(seg.single.Name), baseName, enumTypes, " ", 0)
} else {
writeCBoolGroupEncode(b, "msg", seg.bools)
}
}
b.WriteString(" *out_written = offset;\n")
b.WriteString(" return ARPACK_OK;\n")
b.WriteString("}\n\n")
// decode function
if hasNonByteSliceFields {
ctxName := msgName + "_decode_ctx"
b.WriteString(fmt.Sprintf("arpack_status %s_decode(%s *msg, const uint8_t *buf, size_t buf_len, %s *ctx, size_t *out_read) {\n", msgName, msgName, ctxName))
b.WriteString(" if (ctx == NULL) return ARPACK_ERR_INVALID_ARGUMENT;\n")
} else {
b.WriteString(fmt.Sprintf("arpack_status %s_decode(%s *msg, const uint8_t *buf, size_t buf_len, size_t *out_read) {\n", msgName, msgName))
}
b.WriteString(fmt.Sprintf(" if (buf_len < %d) return ARPACK_ERR_BUFFER_TOO_SHORT;\n", minSize))
b.WriteString(" size_t offset = 0;\n")
for _, seg := range segs {
if seg.single != nil {
ctxVar := ""
if hasNonByteSliceFields {
ctxVar = "ctx"
}
writeCFieldDecode(b, seg.single, "msg->"+snakeCase(seg.single.Name), baseName, enumTypes, ctxVar, " ", 0)
} else {
writeCBoolGroupDecode(b, "msg", seg.bools)
}
}
b.WriteString(" *out_read = offset;\n")
b.WriteString(" return ARPACK_OK;\n")
b.WriteString("}\n")
}
func writeCFieldSize(
b *strings.Builder,
field *parser.Field,
access string,
baseName string,
enumTypes map[string]struct{},
indent string,
depth int,
) {
switch field.Kind {
case parser.KindPrimitive:
if field.Quant != nil {
b.WriteString(fmt.Sprintf("%ssize += %d;\n", indent, field.Quant.WireBytes()))
return
}
if field.Primitive == parser.KindString {
b.WriteString(fmt.Sprintf("%ssize += 2 + %s.len;\n", indent, access))
return
}
ws := field.WireSize()
if ws > 0 {
b.WriteString(fmt.Sprintf("%ssize += %d;\n", indent, ws))
}
case parser.KindNested:
nestedName := baseName + "_" + snakeCase(field.TypeName)
b.WriteString(fmt.Sprintf("%s{\n", indent))
b.WriteString(fmt.Sprintf("%s size_t nested_size;\n", indent))
b.WriteString(fmt.Sprintf("%s arpack_status status = %s_size(&%s, &nested_size);\n", indent, nestedName, access))
b.WriteString(fmt.Sprintf("%s if (status != ARPACK_OK) return status;\n", indent))
b.WriteString(fmt.Sprintf("%s size += nested_size;\n", indent))
b.WriteString(fmt.Sprintf("%s}\n", indent))
case parser.KindFixedArray:
idxVar := fmt.Sprintf("_i%d", depth)
b.WriteString(fmt.Sprintf("%sfor (uint16_t %s = 0; %s < %d; %s++) {\n", indent, idxVar, idxVar, field.FixedLen, idxVar))
writeCFieldSize(b, field.Elem, access+"["+idxVar+"]", baseName, enumTypes, indent+" ", depth+1)
b.WriteString(fmt.Sprintf("%s}\n", indent))
case parser.KindSlice:
b.WriteString(fmt.Sprintf("%ssize += 2;\n", indent))
if isRawUint8Element(field.Elem) {
b.WriteString(fmt.Sprintf("%ssize += %s.len;\n", indent, access))
return
}
idxVar := fmt.Sprintf("_i%d", depth)
b.WriteString(fmt.Sprintf("%sfor (uint16_t %s = 0; %s < %s.len; %s++) {\n", indent, idxVar, idxVar, access, idxVar))
writeCFieldSize(b, field.Elem, access+".data["+idxVar+"]", baseName, enumTypes, indent+" ", depth+1)
b.WriteString(fmt.Sprintf("%s}\n", indent))
}
}
func writeCBoundsCheck(b *strings.Builder, indent string, needed string) {
b.WriteString(fmt.Sprintf("%s{\n", indent))
b.WriteString(fmt.Sprintf("%s arpack_status status = _arpack_check_bounds(offset, %s, buf_len);\n", indent, needed))
b.WriteString(fmt.Sprintf("%s if (status != ARPACK_OK) return status;\n", indent))
b.WriteString(fmt.Sprintf("%s}\n", indent))
}
func writeCBoolGroupEncode(b *strings.Builder, msgVar string, bools []parser.Field) {
b.WriteString(" {\n")
b.WriteString(" uint8_t _boolByte = 0;\n")
for i, field := range bools {
fieldName := snakeCase(field.Name)
access := msgVar + "->" + fieldName
b.WriteString(fmt.Sprintf(" if (%s) _boolByte |= (1 << %d);\n", access, i))
}
b.WriteString(" _arpack_write_u8(buf, &offset, _boolByte);\n")
b.WriteString(" }\n")
}
func writeCBoolGroupDecode(b *strings.Builder, msgVar string, bools []parser.Field) {
b.WriteString(" {\n")
writeCBoundsCheck(b, " ", "1")
b.WriteString(" uint8_t _boolByte = _arpack_read_u8(buf, &offset);\n")
for i, field := range bools {
fieldName := snakeCase(field.Name)
access := msgVar + "->" + fieldName
b.WriteString(fmt.Sprintf(" %s = (_boolByte & (1 << %d)) != 0;\n", access, i))
}
b.WriteString(" }\n")
}
func writeCFieldEncode(
b *strings.Builder,
field *parser.Field,
access string,
baseName string,
enumTypes map[string]struct{},
indent string,
depth int,
) {
switch field.Kind {
case parser.KindPrimitive:
if field.Quant != nil && (field.Primitive == parser.KindFloat32 || field.Primitive == parser.KindFloat64) {
writeCQuantizedEncode(b, field, access, indent)
return
}
if field.Primitive == parser.KindString {
b.WriteString(fmt.Sprintf("%s_arpack_write_u16_le(buf, &offset, %s.len);\n", indent, access))
idxVar := fmt.Sprintf("_i%d", depth)
b.WriteString(fmt.Sprintf("%sfor (uint16_t %s = 0; %s < %s.len; %s++) {\n", indent, idxVar, idxVar, access, idxVar))
b.WriteString(fmt.Sprintf("%s _arpack_write_u8(buf, &offset, (uint8_t)%s.data[%s]);\n", indent, access, idxVar))
b.WriteString(fmt.Sprintf("%s}\n", indent))
return
}
helper := cPrimitiveWriteHelper(field.Primitive)
if field.Primitive == parser.KindBool {
b.WriteString(fmt.Sprintf("%s_arpack_write_%s(buf, &offset, %s ? 1 : 0);\n", indent, helper, access))
return
}
b.WriteString(fmt.Sprintf("%s_arpack_write_%s(buf, &offset, %s);\n", indent, helper, access))
case parser.KindNested:
nestedName := baseName + "_" + snakeCase(field.TypeName)
b.WriteString(fmt.Sprintf("%s{\n", indent))
b.WriteString(fmt.Sprintf("%s size_t nested_written;\n", indent))
b.WriteString(fmt.Sprintf("%s arpack_status status = %s_encode(&%s, buf + offset, buf_len - offset, &nested_written);\n", indent, nestedName, access))
b.WriteString(fmt.Sprintf("%s if (status != ARPACK_OK) return status;\n", indent))
b.WriteString(fmt.Sprintf("%s offset += nested_written;\n", indent))
b.WriteString(fmt.Sprintf("%s}\n", indent))
case parser.KindFixedArray:
idxVar := fmt.Sprintf("_i%d", depth)
b.WriteString(fmt.Sprintf("%sfor (uint16_t %s = 0; %s < %d; %s++) {\n", indent, idxVar, idxVar, field.FixedLen, idxVar))
writeCFieldEncode(b, field.Elem, access+"["+idxVar+"]", baseName, enumTypes, indent+" ", depth+1)
b.WriteString(fmt.Sprintf("%s}\n", indent))
case parser.KindSlice:
b.WriteString(fmt.Sprintf("%s_arpack_write_u16_le(buf, &offset, %s.len);\n", indent, access))
if isRawUint8Element(field.Elem) {
idxVar := fmt.Sprintf("_i%d", depth)
b.WriteString(fmt.Sprintf("%sfor (uint16_t %s = 0; %s < %s.len; %s++) {\n", indent, idxVar, idxVar, access, idxVar))
b.WriteString(fmt.Sprintf("%s _arpack_write_u8(buf, &offset, %s.data[%s]);\n", indent, access, idxVar))
b.WriteString(fmt.Sprintf("%s}\n", indent))
return
}
idxVar := fmt.Sprintf("_i%d", depth)
b.WriteString(fmt.Sprintf("%sfor (uint16_t %s = 0; %s < %s.len; %s++) {\n", indent, idxVar, idxVar, access, idxVar))
writeCFieldEncode(b, field.Elem, access+".data["+idxVar+"]", baseName, enumTypes, indent+" ", depth+1)
b.WriteString(fmt.Sprintf("%s}\n", indent))
}
}
func writeCQuantizedEncode(b *strings.Builder, field *parser.Field, access string, indent string) {
maxUint := int(field.Quant.MaxUint())
if field.Quant.Bits == 8 {
b.WriteString(fmt.Sprintf("%s{\n", indent))
b.WriteString(fmt.Sprintf("%s double _q = ((double)%s - (%g)) / ((%g) - (%g)) * %d;\n", indent, access, field.Quant.Min, field.Quant.Max, field.Quant.Min, maxUint))
b.WriteString(fmt.Sprintf("%s if (_q < 0.0 || _q > %d.0) return ARPACK_ERR_INVALID_ARGUMENT;\n", indent, maxUint))
b.WriteString(fmt.Sprintf("%s uint8_t _qv = (uint8_t)_q;\n", indent))
b.WriteString(fmt.Sprintf("%s _arpack_write_u8(buf, &offset, _qv);\n", indent))
b.WriteString(fmt.Sprintf("%s}\n", indent))
return
}
b.WriteString(fmt.Sprintf("%s{\n", indent))
b.WriteString(fmt.Sprintf("%s double _q = ((double)%s - (%g)) / ((%g) - (%g)) * %d;\n", indent, access, field.Quant.Min, field.Quant.Max, field.Quant.Min, maxUint))
b.WriteString(fmt.Sprintf("%s if (_q < 0.0 || _q > %d.0) return ARPACK_ERR_INVALID_ARGUMENT;\n", indent, maxUint))
b.WriteString(fmt.Sprintf("%s uint16_t _qv = (uint16_t)_q;\n", indent))
b.WriteString(fmt.Sprintf("%s _arpack_write_u16_le(buf, &offset, _qv);\n", indent))
b.WriteString(fmt.Sprintf("%s}\n", indent))
}
func writeCFieldDecode(
b *strings.Builder,
field *parser.Field,
access string,
baseName string,
enumTypes map[string]struct{},
ctxVar string,
indent string,
depth int,
) {
switch field.Kind {
case parser.KindPrimitive:
if field.Quant != nil && (field.Primitive == parser.KindFloat32 || field.Primitive == parser.KindFloat64) {
writeCQuantizedDecode(b, field, access, indent)
return
}
if field.Primitive == parser.KindString {
writeCBoundsCheck(b, indent, "2")
b.WriteString(fmt.Sprintf("%s%s.len = _arpack_read_u16_le(buf, &offset);\n", indent, access))
writeCBoundsCheck(b, indent, access+".len")
b.WriteString(fmt.Sprintf("%s%s.data = (const char *)(buf + offset);\n", indent, access))
b.WriteString(fmt.Sprintf("%soffset += %s.len;\n", indent, access))
return
}
writeCBoundsCheck(b, indent, fmt.Sprintf("%d", field.WireSize()))
helper := cPrimitiveReadHelper(field.Primitive)
if field.Primitive == parser.KindBool {
b.WriteString(fmt.Sprintf("%s%s = _arpack_read_%s(buf, &offset) != 0;\n", indent, access, helper))
return
}
b.WriteString(fmt.Sprintf("%s%s = _arpack_read_%s(buf, &offset);\n", indent, access, helper))
case parser.KindNested:
nestedName := baseName + "_" + snakeCase(field.TypeName)
b.WriteString(fmt.Sprintf("%s{\n", indent))
b.WriteString(fmt.Sprintf("%s size_t nested_read;\n", indent))
b.WriteString(fmt.Sprintf("%s arpack_status status = %s_decode(&%s, buf + offset, buf_len - offset, &nested_read);\n", indent, nestedName, access))
b.WriteString(fmt.Sprintf("%s if (status != ARPACK_OK) return status;\n", indent))
b.WriteString(fmt.Sprintf("%s offset += nested_read;\n", indent))
b.WriteString(fmt.Sprintf("%s}\n", indent))
case parser.KindFixedArray:
idxVar := fmt.Sprintf("_i%d", depth)
b.WriteString(fmt.Sprintf("%sfor (uint16_t %s = 0; %s < %d; %s++) {\n", indent, idxVar, idxVar, field.FixedLen, idxVar))
writeCFieldDecode(b, field.Elem, access+"["+idxVar+"]", baseName, enumTypes, "", indent+" ", depth+1)
b.WriteString(fmt.Sprintf("%s}\n", indent))
case parser.KindSlice:
writeCBoundsCheck(b, indent, "2")
b.WriteString(fmt.Sprintf("%s%s.len = _arpack_read_u16_le(buf, &offset);\n", indent, access))
if isRawUint8Element(field.Elem) {
writeCBoundsCheck(b, indent, access+".len")
b.WriteString(fmt.Sprintf("%s%s.data = buf + offset;\n", indent, access))
b.WriteString(fmt.Sprintf("%soffset += %s.len;\n", indent, access))
return
}
ctxField := snakeCase(field.Name)
b.WriteString(fmt.Sprintf("%sif (%s.len > %s->%s_cap) return ARPACK_ERR_CAPACITY_TOO_SMALL;\n", indent, access, ctxVar, ctxField))
b.WriteString(fmt.Sprintf("%s%s.data = %s->%s_data;\n", indent, access, ctxVar, ctxField))
idxVar := fmt.Sprintf("_i%d", depth)
b.WriteString(fmt.Sprintf("%sfor (uint16_t %s = 0; %s < %s.len; %s++) {\n", indent, idxVar, idxVar, access, idxVar))
writeCFieldDecode(b, field.Elem, ctxVar+"->"+ctxField+"_data["+idxVar+"]", baseName, enumTypes, "", indent+" ", depth+1)
b.WriteString(fmt.Sprintf("%s}\n", indent))
}
}
func writeCQuantizedDecode(b *strings.Builder, field *parser.Field, access string, indent string) {
maxUint := field.Quant.MaxUint()
if field.Quant.Bits == 8 {
writeCBoundsCheck(b, indent, "1")
b.WriteString(fmt.Sprintf("%s{\n", indent))
b.WriteString(fmt.Sprintf("%s uint8_t _qv = _arpack_read_u8(buf, &offset);\n", indent))
b.WriteString(fmt.Sprintf("%s %s = ((double)_qv / %g) * ((%g) - (%g)) + (%g);\n", indent, access, maxUint, field.Quant.Max, field.Quant.Min, field.Quant.Min))
b.WriteString(fmt.Sprintf("%s}\n", indent))
return
}
writeCBoundsCheck(b, indent, "2")
b.WriteString(fmt.Sprintf("%s{\n", indent))
b.WriteString(fmt.Sprintf("%s uint16_t _qv = _arpack_read_u16_le(buf, &offset);\n", indent))
b.WriteString(fmt.Sprintf("%s %s = ((double)_qv / %g) * ((%g) - (%g)) + (%g);\n", indent, access, maxUint, field.Quant.Max, field.Quant.Min, field.Quant.Min))
b.WriteString(fmt.Sprintf("%s}\n", indent))
}
func cFieldTypeInfo(field *parser.Field, baseName string, enumTypes map[string]struct{}) (typeStr string, arraySuffix string) {
switch field.Kind {
case parser.KindPrimitive:
return cPrimitiveTypeInfo(field, baseName, enumTypes), ""
case parser.KindNested:
return baseName + "_" + snakeCase(field.TypeName), ""
case parser.KindFixedArray:
elemType, elemSuffix := cFieldTypeInfo(field.Elem, baseName, enumTypes)
return elemType, fmt.Sprintf("[%d]%s", field.FixedLen, elemSuffix)
case parser.KindSlice:
if isRawUint8Element(field.Elem) {
return "arpack_bytes_view", ""
}
if field.Elem.Kind == parser.KindPrimitive && field.Elem.Primitive == parser.KindString {
return "arpack_string_view_slice_view", ""
}
return cSliceViewTypeName(field.Elem, baseName, enumTypes), ""
default:
return "void*", ""
}
}
func cFieldType(field *parser.Field, baseName string, enumTypes map[string]struct{}) string {
typeStr, suffix := cFieldTypeInfo(field, baseName, enumTypes)
if suffix != "" {
return typeStr + suffix
}
return typeStr
}
func cSliceViewTypeName(field *parser.Field, baseName string, enumTypes map[string]struct{}) string {
return baseName + "_" + cSliceViewElemKey(field, enumTypes) + "_slice_view"
}
func cSliceViewElemKey(field *parser.Field, enumTypes map[string]struct{}) string {
switch field.Kind {
case parser.KindPrimitive:
if field.NamedType != "" {
if _, ok := enumTypes[field.NamedType]; ok {
return snakeCase(field.NamedType)
}
}
return cPrimitiveTypeToken(field.Primitive)
case parser.KindNested:
return snakeCase(field.TypeName)
default:
return "unsupported"
}
}
func cPrimitiveTypeInfo(field *parser.Field, baseName string, enumTypes map[string]struct{}) string {
if field.NamedType != "" {
if _, ok := enumTypes[field.NamedType]; ok {
return baseName + "_" + snakeCase(field.NamedType)
}
}
switch field.Primitive {
case parser.KindInt8:
return "int8_t"
case parser.KindInt16:
return "int16_t"
case parser.KindInt32:
return "int32_t"
case parser.KindInt64:
return "int64_t"
case parser.KindUint8:
return "uint8_t"
case parser.KindUint16:
return "uint16_t"
case parser.KindUint32:
return "uint32_t"
case parser.KindUint64:
return "uint64_t"
case parser.KindFloat32:
return "float"
case parser.KindFloat64:
return "double"
case parser.KindBool:
return "bool"
case parser.KindString:
return "arpack_string_view"
default:
return "void*"
}
}
func cPrimitiveTypeToken(k parser.PrimitiveKind) string {
switch k {
case parser.KindInt8:
return "int8"
case parser.KindInt16:
return "int16"
case parser.KindInt32:
return "int32"
case parser.KindInt64:
return "int64"
case parser.KindUint8:
return "uint8"
case parser.KindUint16:
return "uint16"
case parser.KindUint32:
return "uint32"
case parser.KindUint64:
return "uint64"
case parser.KindFloat32:
return "float32"
case parser.KindFloat64:
return "float64"
case parser.KindBool:
return "bool"
case parser.KindString:
return "string"
default:
return "unknown"
}
}
func cPrimitiveReadHelper(k parser.PrimitiveKind) string {
switch k {
case parser.KindInt8:
return "i8"
case parser.KindInt16:
return "i16_le"
case parser.KindInt32:
return "i32_le"
case parser.KindInt64:
return "i64_le"
case parser.KindUint8, parser.KindBool:
return "u8"
case parser.KindUint16:
return "u16_le"
case parser.KindUint32:
return "u32_le"
case parser.KindUint64:
return "u64_le"
case parser.KindFloat32:
return "f32_le"
case parser.KindFloat64:
return "f64_le"
default:
return ""
}
}
func cPrimitiveWriteHelper(k parser.PrimitiveKind) string {
return cPrimitiveReadHelper(k)
}
func isRawUint8Element(field *parser.Field) bool {
return field != nil &&
field.Kind == parser.KindPrimitive &&
field.Primitive == parser.KindUint8 &&
field.NamedType == "" &&
field.Quant == nil
}
func writeCRuntimeHelpers(b *strings.Builder) {
b.WriteString("// Private runtime helpers\n\n")
// Bounds check helper
b.WriteString("static inline arpack_status _arpack_check_bounds(size_t offset, size_t needed, size_t buf_len) {\n")
b.WriteString(" if (offset + needed > buf_len) return ARPACK_ERR_BUFFER_TOO_SHORT;\n")
b.WriteString(" return ARPACK_OK;\n")
b.WriteString("}\n\n")
// Read helpers (little-endian)
b.WriteString("static inline uint8_t _arpack_read_u8(const uint8_t *buf, size_t *offset) {\n")
b.WriteString(" uint8_t val = buf[*offset];\n")
b.WriteString(" *offset += 1;\n")
b.WriteString(" return val;\n")
b.WriteString("}\n\n")
b.WriteString("static inline uint16_t _arpack_read_u16_le(const uint8_t *buf, size_t *offset) {\n")
b.WriteString(" uint16_t val = (uint16_t)buf[*offset] | ((uint16_t)buf[*offset + 1] << 8);\n")
b.WriteString(" *offset += 2;\n")
b.WriteString(" return val;\n")
b.WriteString("}\n\n")
b.WriteString("static inline uint32_t _arpack_read_u32_le(const uint8_t *buf, size_t *offset) {\n")
b.WriteString(" uint32_t val = (uint32_t)buf[*offset] | ((uint32_t)buf[*offset + 1] << 8) |\\\n")
b.WriteString(" ((uint32_t)buf[*offset + 2] << 16) | ((uint32_t)buf[*offset + 3] << 24);\n")
b.WriteString(" *offset += 4;\n")
b.WriteString(" return val;\n")
b.WriteString("}\n\n")
b.WriteString("static inline uint64_t _arpack_read_u64_le(const uint8_t *buf, size_t *offset) {\n")
b.WriteString(" uint64_t val = (uint64_t)buf[*offset] | ((uint64_t)buf[*offset + 1] << 8) |\\\n")
b.WriteString(" ((uint64_t)buf[*offset + 2] << 16) | ((uint64_t)buf[*offset + 3] << 24) |\\\n")
b.WriteString(" ((uint64_t)buf[*offset + 4] << 32) | ((uint64_t)buf[*offset + 5] << 40) |\\\n")
b.WriteString(" ((uint64_t)buf[*offset + 6] << 48) | ((uint64_t)buf[*offset + 7] << 56);\n")
b.WriteString(" *offset += 8;\n")
b.WriteString(" return val;\n")
b.WriteString("}\n\n")
b.WriteString("static inline int8_t _arpack_read_i8(const uint8_t *buf, size_t *offset) {\n")
b.WriteString(" return (int8_t)_arpack_read_u8(buf, offset);\n")
b.WriteString("}\n\n")
b.WriteString("static inline int16_t _arpack_read_i16_le(const uint8_t *buf, size_t *offset) {\n")
b.WriteString(" return (int16_t)_arpack_read_u16_le(buf, offset);\n")
b.WriteString("}\n\n")
b.WriteString("static inline int32_t _arpack_read_i32_le(const uint8_t *buf, size_t *offset) {\n")
b.WriteString(" return (int32_t)_arpack_read_u32_le(buf, offset);\n")
b.WriteString("}\n\n")
b.WriteString("static inline int64_t _arpack_read_i64_le(const uint8_t *buf, size_t *offset) {\n")
b.WriteString(" return (int64_t)_arpack_read_u64_le(buf, offset);\n")
b.WriteString("}\n\n")
// Write helpers (little-endian)
b.WriteString("static inline void _arpack_write_u8(uint8_t *buf, size_t *offset, uint8_t val) {\n")
b.WriteString(" buf[*offset] = val;\n")
b.WriteString(" *offset += 1;\n")
b.WriteString("}\n\n")
b.WriteString("static inline void _arpack_write_u16_le(uint8_t *buf, size_t *offset, uint16_t val) {\n")
b.WriteString(" buf[*offset] = val & 0xFF;\n")
b.WriteString(" buf[*offset + 1] = (val >> 8) & 0xFF;\n")
b.WriteString(" *offset += 2;\n")
b.WriteString("}\n\n")
b.WriteString("static inline void _arpack_write_u32_le(uint8_t *buf, size_t *offset, uint32_t val) {\n")
b.WriteString(" buf[*offset] = val & 0xFF;\n")
b.WriteString(" buf[*offset + 1] = (val >> 8) & 0xFF;\n")
b.WriteString(" buf[*offset + 2] = (val >> 16) & 0xFF;\n")
b.WriteString(" buf[*offset + 3] = (val >> 24) & 0xFF;\n")
b.WriteString(" *offset += 4;\n")
b.WriteString("}\n\n")
b.WriteString("static inline void _arpack_write_u64_le(uint8_t *buf, size_t *offset, uint64_t val) {\n")
b.WriteString(" buf[*offset] = val & 0xFF;\n")
b.WriteString(" buf[*offset + 1] = (val >> 8) & 0xFF;\n")
b.WriteString(" buf[*offset + 2] = (val >> 16) & 0xFF;\n")
b.WriteString(" buf[*offset + 3] = (val >> 24) & 0xFF;\n")
b.WriteString(" buf[*offset + 4] = (val >> 32) & 0xFF;\n")
b.WriteString(" buf[*offset + 5] = (val >> 40) & 0xFF;\n")
b.WriteString(" buf[*offset + 6] = (val >> 48) & 0xFF;\n")
b.WriteString(" buf[*offset + 7] = (val >> 56) & 0xFF;\n")
b.WriteString(" *offset += 8;\n")
b.WriteString("}\n\n")
b.WriteString("static inline void _arpack_write_i8(uint8_t *buf, size_t *offset, int8_t val) {\n")
b.WriteString(" _arpack_write_u8(buf, offset, (uint8_t)val);\n")
b.WriteString("}\n\n")
b.WriteString("static inline void _arpack_write_i16_le(uint8_t *buf, size_t *offset, int16_t val) {\n")
b.WriteString(" _arpack_write_u16_le(buf, offset, (uint16_t)val);\n")
b.WriteString("}\n\n")
b.WriteString("static inline void _arpack_write_i32_le(uint8_t *buf, size_t *offset, int32_t val) {\n")
b.WriteString(" _arpack_write_u32_le(buf, offset, (uint32_t)val);\n")
b.WriteString("}\n\n")
b.WriteString("static inline void _arpack_write_i64_le(uint8_t *buf, size_t *offset, int64_t val) {\n")
b.WriteString(" _arpack_write_u64_le(buf, offset, (uint64_t)val);\n")
b.WriteString("}\n\n")
// Float bit conversion helpers
b.WriteString("static inline float _arpack_u32_to_f(uint32_t bits) {\n")
b.WriteString(" union { uint32_t u; float f; } conv;\n")
b.WriteString(" conv.u = bits;\n")
b.WriteString(" return conv.f;\n")
b.WriteString("}\n\n")
b.WriteString("static inline uint32_t _arpack_f_to_u32(float val) {\n")
b.WriteString(" union { uint32_t u; float f; } conv;\n")
b.WriteString(" conv.f = val;\n")
b.WriteString(" return conv.u;\n")
b.WriteString("}\n\n")
b.WriteString("static inline double _arpack_u64_to_f(uint64_t bits) {\n")
b.WriteString(" union { uint64_t u; double f; } conv;\n")
b.WriteString(" conv.u = bits;\n")
b.WriteString(" return conv.f;\n")
b.WriteString("}\n\n")
b.WriteString("static inline uint64_t _arpack_f_to_u64(double val) {\n")
b.WriteString(" union { uint64_t u; double f; } conv;\n")
b.WriteString(" conv.f = val;\n")
b.WriteString(" return conv.u;\n")
b.WriteString("}\n\n")
// Float read/write helpers
b.WriteString("static inline float _arpack_read_f32_le(const uint8_t *buf, size_t *offset) {\n")
b.WriteString(" return _arpack_u32_to_f(_arpack_read_u32_le(buf, offset));\n")
b.WriteString("}\n\n")
b.WriteString("static inline double _arpack_read_f64_le(const uint8_t *buf, size_t *offset) {\n")
b.WriteString(" return _arpack_u64_to_f(_arpack_read_u64_le(buf, offset));\n")
b.WriteString("}\n\n")
b.WriteString("static inline void _arpack_write_f32_le(uint8_t *buf, size_t *offset, float val) {\n")
b.WriteString(" _arpack_write_u32_le(buf, offset, _arpack_f_to_u32(val));\n")
b.WriteString("}\n\n")
b.WriteString("static inline void _arpack_write_f64_le(uint8_t *buf, size_t *offset, double val) {\n")
b.WriteString(" _arpack_write_u64_le(buf, offset, _arpack_f_to_u64(val));\n")
b.WriteString("}\n\n")
}
func snakeCase(s string) string {
if s == "" {
return ""
}
var b strings.Builder
var prevUpper bool
for i, c := range s {
isUpper := c >= 'A' && c <= 'Z'
if i > 0 && isUpper {
nextLower := false
if i+1 < len(s) {
nextChar := rune(s[i+1])
nextLower = nextChar >= 'a' && nextChar <= 'z'
}
if !prevUpper || nextLower {
b.WriteByte('_')
}
}
b.WriteRune(c)
prevUpper = isUpper
}
return strings.ToLower(b.String())
}