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 \n") headerBuilder.WriteString("#include \n") headerBuilder.WriteString("#include \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()) }