c-next 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/index.js +617 -474
  2. package/dist/index.js.map +4 -4
  3. package/package.json +1 -1
  4. package/src/transpiler/Transpiler.ts +50 -29
  5. package/src/transpiler/__tests__/Transpiler.coverage.test.ts +2 -1
  6. package/src/transpiler/data/PathResolver.ts +10 -6
  7. package/src/transpiler/data/__tests__/PathResolver.test.ts +32 -0
  8. package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +82 -7
  9. package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +1 -12
  10. package/src/transpiler/logic/analysis/SignedShiftAnalyzer.ts +239 -0
  11. package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +92 -0
  12. package/src/transpiler/logic/analysis/__tests__/SignedShiftAnalyzer.test.ts +414 -0
  13. package/src/transpiler/logic/analysis/__tests__/StructFieldAnalyzer.test.ts +15 -75
  14. package/src/transpiler/logic/analysis/__tests__/runAnalyzers.test.ts +3 -15
  15. package/src/transpiler/logic/analysis/runAnalyzers.ts +11 -2
  16. package/src/transpiler/logic/analysis/types/ISignedShiftError.ts +15 -0
  17. package/src/transpiler/logic/symbols/SymbolUtils.ts +4 -1
  18. package/src/transpiler/logic/symbols/__tests__/SymbolUtils.test.ts +10 -11
  19. package/src/transpiler/logic/symbols/c/__tests__/CResolver.integration.test.ts +27 -4
  20. package/src/transpiler/logic/symbols/c/collectors/FunctionCollector.ts +11 -5
  21. package/src/transpiler/logic/symbols/cpp/__tests__/CppResolver.integration.test.ts +4 -4
  22. package/src/transpiler/output/codegen/CodeGenerator.ts +169 -11
  23. package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +132 -3
  24. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +56 -31
  25. package/src/transpiler/output/codegen/analysis/StringLengthCounter.ts +12 -11
  26. package/src/transpiler/output/codegen/analysis/__tests__/StringLengthCounter.test.ts +21 -21
  27. package/src/transpiler/output/codegen/assignment/handlers/AccessPatternHandlers.ts +2 -2
  28. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +2 -2
  29. package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +2 -2
  30. package/src/transpiler/output/codegen/assignment/handlers/RegisterHandlers.ts +4 -4
  31. package/src/transpiler/output/codegen/assignment/handlers/__tests__/AccessPatternHandlers.test.ts +4 -4
  32. package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +8 -8
  33. package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitmapHandlers.test.ts +5 -5
  34. package/src/transpiler/output/codegen/assignment/handlers/__tests__/RegisterHandlers.test.ts +4 -4
  35. package/src/transpiler/output/codegen/generators/expressions/AccessExprGenerator.ts +7 -326
  36. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +8 -1
  37. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +14 -275
  38. package/src/transpiler/output/codegen/generators/expressions/UnaryExprGenerator.ts +13 -1
  39. package/src/transpiler/output/codegen/generators/expressions/__tests__/AccessExprGenerator.test.ts +0 -573
  40. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +8 -439
  41. package/src/transpiler/output/codegen/generators/expressions/__tests__/UnaryExprGenerator.test.ts +196 -0
  42. package/src/transpiler/output/codegen/helpers/ArgumentGenerator.ts +5 -0
  43. package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +7 -2
  44. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +8 -1
  45. package/src/transpiler/output/codegen/helpers/TypedefParamParser.ts +34 -0
  46. package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +11 -0
  47. package/src/transpiler/output/codegen/helpers/VariableModifierBuilder.ts +16 -1
  48. package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +48 -0
  49. package/src/transpiler/output/codegen/helpers/__tests__/TypedefParamParser.test.ts +88 -0
  50. package/src/transpiler/output/codegen/helpers/__tests__/VariableModifierBuilder.test.ts +34 -2
  51. package/src/transpiler/output/codegen/types/TTypeInfo.ts +1 -0
  52. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +15 -2
  53. package/src/transpiler/output/headers/__tests__/BaseHeaderGenerator.test.ts +87 -0
  54. package/src/utils/BitUtils.ts +17 -13
  55. package/src/utils/ExpressionUtils.ts +51 -0
  56. package/src/utils/__tests__/BitUtils.test.ts +56 -56
  57. package/src/utils/types/IParameterSymbol.ts +2 -0
@@ -303,7 +303,14 @@ class StringDeclHelper {
303
303
  initValue,
304
304
  arrayDims,
305
305
  );
306
- return { code: `${decl} = ${finalInitValue};`, handled: true };
306
+ // MISRA C:2012 Rules 9.3/9.4 - String literals don't fill all inner array bytes,
307
+ // but C standard guarantees zero-initialization of remaining elements
308
+ const suppression =
309
+ "// cppcheck-suppress misra-c2012-9.3\n// cppcheck-suppress misra-c2012-9.4\n";
310
+ return {
311
+ code: `${suppression}${decl} = ${finalInitValue};`,
312
+ handled: true,
313
+ };
307
314
  }
308
315
 
309
316
  /**
@@ -6,8 +6,11 @@
6
6
  * "void (*)(Point p)"
7
7
  *
8
8
  * Used by Issue #895 to determine if callback params should be pointers or values.
9
+ * Used by Issue #914 to resolve callback pointer/const info onto IParameterSymbol.
9
10
  */
10
11
 
12
+ import IParameterSymbol from "../../../../utils/types/IParameterSymbol";
13
+
11
14
  /**
12
15
  * Parsed parameter info from a typedef.
13
16
  */
@@ -215,6 +218,37 @@ class TypedefParamParser {
215
218
  TypedefParamParser.getParamAt(typedefType, paramIndex)?.isConst ?? null
216
219
  );
217
220
  }
221
+
222
+ /**
223
+ * Resolve callback pointer/const overrides onto an array of IParameterSymbol.
224
+ *
225
+ * Issue #914: This is the single point where callback typedef info is applied
226
+ * to parameters — used by both .c and .h generation paths via IParameterSymbol.
227
+ *
228
+ * @param params - The original parameter symbols
229
+ * @param callbackTypedefType - The typedef type string (e.g., "void (*)(widget_t *, const rect_t *)")
230
+ * @returns New array with isCallbackPointer/isCallbackConst resolved
231
+ */
232
+ static resolveCallbackParams(
233
+ params: readonly IParameterSymbol[],
234
+ callbackTypedefType: string,
235
+ ): IParameterSymbol[] {
236
+ return params.map((param, index) => {
237
+ const shouldBePointer = TypedefParamParser.shouldBePointer(
238
+ callbackTypedefType,
239
+ index,
240
+ );
241
+ const shouldBeConst = TypedefParamParser.shouldBeConst(
242
+ callbackTypedefType,
243
+ index,
244
+ );
245
+ return {
246
+ ...param,
247
+ isCallbackPointer: shouldBePointer ?? undefined,
248
+ isCallbackConst: shouldBeConst ?? undefined,
249
+ };
250
+ });
251
+ }
218
252
  }
219
253
 
220
254
  export default TypedefParamParser;
@@ -139,6 +139,8 @@ interface IVariableDeclCallbacks {
139
139
  ctx: Parser.VariableDeclarationContext,
140
140
  name: string,
141
141
  ) => void;
142
+ /** Issue #895 Bug B: Mark variable as pointer in type registry */
143
+ markVariableAsPointer: (name: string) => void;
142
144
  /** Get string concatenation operands */
143
145
  getStringConcatOperands: (
144
146
  ctx: Parser.ExpressionContext,
@@ -571,9 +573,13 @@ class VariableDeclHelper {
571
573
  }
572
574
 
573
575
  // Issue #696: Use helper for modifier extraction and validation
576
+ // Issue #852 (MISRA Rule 8.5): Pass hasInitializer and cppMode for correct extern behavior
577
+ const hasInitializer = ctx.expression() !== null;
574
578
  const modifiers = VariableModifierBuilder.build(
575
579
  ctx,
576
580
  CodeGenState.inFunctionBody,
581
+ hasInitializer,
582
+ CodeGenState.cppMode,
577
583
  );
578
584
 
579
585
  const name = ctx.IDENTIFIER().getText();
@@ -587,6 +593,11 @@ class VariableDeclHelper {
587
593
  // Track local variable metadata
588
594
  callbacks.trackLocalVariable(ctx, name);
589
595
 
596
+ // Issue #895 Bug B: If type was inferred as pointer, mark it in the registry
597
+ if (type.endsWith("*")) {
598
+ callbacks.markVariableAsPointer(name);
599
+ }
600
+
590
601
  // ADR-045: Handle bounded string type specially - early return
591
602
  const stringResult = StringDeclHelper.generateStringDecl(
592
603
  typeCtx,
@@ -46,12 +46,16 @@ class VariableModifierBuilder {
46
46
  *
47
47
  * @param ctx - Parser context with modifier methods
48
48
  * @param inFunctionBody - Whether we're inside a function body (affects extern)
49
+ * @param hasInitializer - Whether the variable has an initializer (affects extern in C mode)
50
+ * @param cppMode - Whether we're generating C++ code (affects extern behavior)
49
51
  * @returns Modifier strings ready for use in generated code
50
52
  * @throws Error if both atomic and volatile are specified
51
53
  */
52
54
  static build(
53
55
  ctx: IModifierContext,
54
56
  inFunctionBody: boolean,
57
+ hasInitializer: boolean = false,
58
+ cppMode: boolean = false,
55
59
  ): IVariableModifiers {
56
60
  const hasConst = ctx.constModifier?.() ?? false;
57
61
  const constMod = hasConst ? "const " : "";
@@ -59,7 +63,18 @@ class VariableModifierBuilder {
59
63
  const volatileMod = ctx.volatileModifier() ? "volatile " : "";
60
64
 
61
65
  // Issue #525: Add extern for top-level const in C++ for external linkage
62
- const externMod = hasConst && !inFunctionBody ? "extern " : "";
66
+ // In C++, const at file scope has internal linkage by default, so extern is needed.
67
+ //
68
+ // Issue #852 (MISRA Rule 8.5): In C mode, do NOT add extern to definitions
69
+ // (variables with initializers). The extern declaration comes from the header.
70
+ //
71
+ // Summary:
72
+ // - C mode + no initializer: extern (declaration)
73
+ // - C mode + initializer: NO extern (definition - MISRA 8.5)
74
+ // - C++ mode: ALWAYS extern for external linkage (both declarations and definitions)
75
+ const needsExtern =
76
+ hasConst && !inFunctionBody && (cppMode || !hasInitializer);
77
+ const externMod = needsExtern ? "extern " : "";
63
78
 
64
79
  // Validate: cannot use both atomic and volatile
65
80
  if (ctx.atomicModifier() && ctx.volatileModifier()) {
@@ -285,6 +285,54 @@ describe("ParameterInputAdapter", () => {
285
285
 
286
286
  expect(result.isAutoConst).toBe(false);
287
287
  });
288
+
289
+ it("forces pointer when isCallbackPointer is set (Issue #914)", () => {
290
+ const param: IParameterSymbol = {
291
+ name: "buf",
292
+ type: "u8",
293
+ isConst: false,
294
+ isArray: false,
295
+ isCallbackPointer: true,
296
+ };
297
+
298
+ // Even though deps says pass-by-value, callback overrides it
299
+ const deps = { ...defaultDeps, isPassByValue: true };
300
+ const result = ParameterInputAdapter.fromSymbol(param, deps);
301
+
302
+ expect(result.isPassByValue).toBe(false);
303
+ expect(result.isPassByReference).toBe(true);
304
+ expect(result.forcePointerSyntax).toBe(true);
305
+ });
306
+
307
+ it("forces const when isCallbackConst is set (Issue #914)", () => {
308
+ const param: IParameterSymbol = {
309
+ name: "area",
310
+ type: "rect_t",
311
+ isConst: false,
312
+ isArray: false,
313
+ isCallbackPointer: true,
314
+ isCallbackConst: true,
315
+ };
316
+
317
+ const result = ParameterInputAdapter.fromSymbol(param, defaultDeps);
318
+
319
+ expect(result.forceConst).toBe(true);
320
+ expect(result.forcePointerSyntax).toBe(true);
321
+ });
322
+
323
+ it("does not force pointer when isCallbackPointer is not set", () => {
324
+ const param: IParameterSymbol = {
325
+ name: "value",
326
+ type: "u32",
327
+ isConst: false,
328
+ isArray: false,
329
+ };
330
+
331
+ const result = ParameterInputAdapter.fromSymbol(param, defaultDeps);
332
+
333
+ expect(result.forcePointerSyntax).toBeUndefined();
334
+ expect(result.forceConst).toBeUndefined();
335
+ });
288
336
  });
289
337
 
290
338
  describe("fromAST", () => {
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { describe, expect, it } from "vitest";
6
6
  import TypedefParamParser from "../TypedefParamParser";
7
+ import IParameterSymbol from "../../../../../utils/types/IParameterSymbol";
7
8
 
8
9
  describe("TypedefParamParser", () => {
9
10
  describe("parse", () => {
@@ -206,4 +207,91 @@ describe("TypedefParamParser", () => {
206
207
  expect(TypedefParamParser.shouldBeConst(typedef, 2)).toBe(false);
207
208
  });
208
209
  });
210
+
211
+ describe("resolveCallbackParams (Issue #914)", () => {
212
+ function makeParam(
213
+ name: string,
214
+ type: string,
215
+ overrides?: Partial<IParameterSymbol>,
216
+ ): IParameterSymbol {
217
+ return {
218
+ name,
219
+ type,
220
+ isConst: false,
221
+ isArray: false,
222
+ ...overrides,
223
+ };
224
+ }
225
+
226
+ it("should set isCallbackPointer for pointer params", () => {
227
+ const params = [makeParam("w", "widget_t"), makeParam("buf", "u8")];
228
+ const typedef = "void (*)(widget_t *, uint8_t *)";
229
+
230
+ const result = TypedefParamParser.resolveCallbackParams(params, typedef);
231
+
232
+ expect(result[0].isCallbackPointer).toBe(true);
233
+ expect(result[1].isCallbackPointer).toBe(true);
234
+ });
235
+
236
+ it("should set isCallbackConst for const pointer params", () => {
237
+ const params = [
238
+ makeParam("w", "widget_t"),
239
+ makeParam("area", "rect_t"),
240
+ makeParam("buf", "u8"),
241
+ ];
242
+ const typedef = "void (*)(widget_t *, const rect_t *, uint8_t *)";
243
+
244
+ const result = TypedefParamParser.resolveCallbackParams(params, typedef);
245
+
246
+ expect(result[0].isCallbackPointer).toBe(true);
247
+ expect(result[0].isCallbackConst).toBe(false);
248
+
249
+ expect(result[1].isCallbackPointer).toBe(true);
250
+ expect(result[1].isCallbackConst).toBe(true);
251
+
252
+ expect(result[2].isCallbackPointer).toBe(true);
253
+ expect(result[2].isCallbackConst).toBe(false);
254
+ });
255
+
256
+ it("should not set callback flags for value params", () => {
257
+ const params = [makeParam("count", "u32")];
258
+ const typedef = "void (*)(int count)";
259
+
260
+ const result = TypedefParamParser.resolveCallbackParams(params, typedef);
261
+
262
+ expect(result[0].isCallbackPointer).toBe(false);
263
+ expect(result[0].isCallbackConst).toBe(false);
264
+ });
265
+
266
+ it("should preserve existing param fields", () => {
267
+ const params = [
268
+ makeParam("data", "u8", {
269
+ isConst: true,
270
+ isArray: true,
271
+ arrayDimensions: ["10"],
272
+ }),
273
+ ];
274
+ const typedef = "void (*)(uint8_t *)";
275
+
276
+ const result = TypedefParamParser.resolveCallbackParams(params, typedef);
277
+
278
+ expect(result[0].name).toBe("data");
279
+ expect(result[0].type).toBe("u8");
280
+ expect(result[0].isConst).toBe(true);
281
+ expect(result[0].isArray).toBe(true);
282
+ expect(result[0].arrayDimensions).toEqual(["10"]);
283
+ expect(result[0].isCallbackPointer).toBe(true);
284
+ });
285
+
286
+ it("should handle params beyond typedef length gracefully", () => {
287
+ const params = [makeParam("w", "widget_t"), makeParam("extra", "u32")];
288
+ const typedef = "void (*)(widget_t *)";
289
+
290
+ const result = TypedefParamParser.resolveCallbackParams(params, typedef);
291
+
292
+ expect(result[0].isCallbackPointer).toBe(true);
293
+ expect(result[1].isCallbackPointer).toBeUndefined();
294
+ expect(result[1].isCallbackConst).toBeUndefined();
295
+ });
296
+ });
209
297
  });
@@ -54,14 +54,46 @@ describe("VariableModifierBuilder", () => {
54
54
  expect(result.volatile).toBe("volatile ");
55
55
  });
56
56
 
57
- it("returns extern for const at file scope", () => {
57
+ it("returns extern for const at file scope (declaration without initializer, C mode)", () => {
58
58
  const ctx = {
59
59
  constModifier: () => ({}),
60
60
  atomicModifier: () => null,
61
61
  volatileModifier: () => null,
62
62
  };
63
63
 
64
- const result = VariableModifierBuilder.build(ctx, false);
64
+ // Declaration (no initializer) in C mode - should have extern
65
+ const result = VariableModifierBuilder.build(ctx, false, false, false);
66
+
67
+ expect(result.extern).toBe("extern ");
68
+ });
69
+
70
+ it("does not return extern for const at file scope with initializer in C mode (MISRA 8.5)", () => {
71
+ // MISRA Rule 8.5: External object/function shall be declared once in one file only.
72
+ // When a variable has an initializer, it's a DEFINITION, not a declaration.
73
+ // The extern declaration comes from the header; the .c file should not duplicate it.
74
+ const ctx = {
75
+ constModifier: () => ({}),
76
+ atomicModifier: () => null,
77
+ volatileModifier: () => null,
78
+ };
79
+
80
+ // Definition (has initializer) in C mode - should NOT have extern
81
+ const result = VariableModifierBuilder.build(ctx, false, true, false);
82
+
83
+ expect(result.extern).toBe("");
84
+ });
85
+
86
+ it("returns extern for const at file scope with initializer in C++ mode (Issue #525)", () => {
87
+ // In C++, const at file scope has internal linkage by default.
88
+ // extern is needed for cross-file access, even for definitions.
89
+ const ctx = {
90
+ constModifier: () => ({}),
91
+ atomicModifier: () => null,
92
+ volatileModifier: () => null,
93
+ };
94
+
95
+ // Definition (has initializer) in C++ mode - SHOULD have extern for external linkage
96
+ const result = VariableModifierBuilder.build(ctx, false, true, true);
65
97
 
66
98
  expect(result.extern).toBe("extern ");
67
99
  });
@@ -19,6 +19,7 @@ type TTypeInfo = {
19
19
  isAtomic?: boolean;
20
20
  isExternalCppType?: boolean; // Issue #375: C++ types instantiated via constructor
21
21
  isParameter?: boolean; // Issue #579: Track if this is a function parameter (becomes pointer in C)
22
+ isPointer?: boolean; // Issue #895 Bug B: Track if variable is a pointer (inferred from C function return type)
22
23
  };
23
24
 
24
25
  export default TTypeInfo;
@@ -278,15 +278,28 @@ class HeaderGeneratorUtils {
278
278
  }
279
279
 
280
280
  // User includes
281
+ // Issue #933: Transform .h to .hpp in C++ mode so headers include correct files
281
282
  if (options.userIncludes && options.userIncludes.length > 0) {
282
283
  for (const include of options.userIncludes) {
283
- lines.push(include);
284
+ const transformedInclude = options.cppMode
285
+ ? include.replace(/\.h"/, '.hpp"').replace(/\.h>/, ".hpp>")
286
+ : include;
287
+ lines.push(transformedInclude);
284
288
  }
285
289
  }
286
290
 
287
291
  // External type header includes (skip duplicates of user includes)
288
- const userIncludeSet = new Set(options.userIncludes ?? []);
292
+ // Note: External C headers stay as .h - only user includes from .cnx files get
293
+ // transformed to .hpp in C++ mode (those are C-Next generated headers)
294
+ const userIncludeSet = new Set(
295
+ options.userIncludes?.map((inc) =>
296
+ options.cppMode
297
+ ? inc.replace(/\.h"/, '.hpp"').replace(/\.h>/, ".hpp>")
298
+ : inc,
299
+ ) ?? [],
300
+ );
289
301
  for (const directive of headersToInclude) {
302
+ // Don't transform external C headers - they should stay as .h
290
303
  if (!userIncludeSet.has(directive)) {
291
304
  lines.push(directive);
292
305
  }
@@ -20,6 +20,8 @@ function createFunctionSymbol(
20
20
  isAutoConst?: boolean;
21
21
  isArray?: boolean;
22
22
  arrayDimensions?: string[];
23
+ isCallbackPointer?: boolean;
24
+ isCallbackConst?: boolean;
23
25
  }>,
24
26
  ): IHeaderSymbol {
25
27
  return {
@@ -36,6 +38,8 @@ function createFunctionSymbol(
36
38
  isAutoConst: p.isAutoConst ?? false,
37
39
  isArray: p.isArray ?? false,
38
40
  arrayDimensions: p.arrayDimensions,
41
+ isCallbackPointer: p.isCallbackPointer,
42
+ isCallbackConst: p.isCallbackConst,
39
43
  })),
40
44
  };
41
45
  }
@@ -239,4 +243,87 @@ describe("BaseHeaderGenerator", () => {
239
243
  expect(result).toContain("void processData(const MyStruct* data);");
240
244
  });
241
245
  });
246
+
247
+ describe("Callback-compatible functions (Issue #914)", () => {
248
+ it("should use pointer for primitive param when isCallbackPointer is set (C mode)", () => {
249
+ const generator = new CHeaderGenerator();
250
+ const symbols: IHeaderSymbol[] = [
251
+ createFunctionSymbol("Renderer_flush", "void", [
252
+ { name: "buf", type: "u8", isCallbackPointer: true },
253
+ ]),
254
+ ];
255
+
256
+ const result = generator.generate(symbols, "test.h");
257
+
258
+ expect(result).toContain("uint8_t* buf");
259
+ });
260
+
261
+ it("should use pointer for primitive param when isCallbackPointer is set (C++ mode)", () => {
262
+ const generator = new CppHeaderGenerator();
263
+ const symbols: IHeaderSymbol[] = [
264
+ createFunctionSymbol("Renderer_flush", "void", [
265
+ { name: "buf", type: "u8", isCallbackPointer: true },
266
+ ]),
267
+ ];
268
+
269
+ const result = generator.generate(symbols, "test.h");
270
+
271
+ // Must use * not & because headers are wrapped in extern "C"
272
+ expect(result).toContain("uint8_t* buf");
273
+ });
274
+
275
+ it("should use const pointer for struct param with isCallbackConst (C mode)", () => {
276
+ const generator = new CHeaderGenerator();
277
+ const symbols: IHeaderSymbol[] = [
278
+ createFunctionSymbol("Renderer_flush", "void", [
279
+ {
280
+ name: "area",
281
+ type: "rect_t",
282
+ isCallbackPointer: true,
283
+ isCallbackConst: true,
284
+ },
285
+ ]),
286
+ ];
287
+
288
+ const result = generator.generate(symbols, "test.h");
289
+
290
+ expect(result).toContain("const rect_t* area");
291
+ });
292
+
293
+ it("should use non-const pointer for struct param without isCallbackConst (C++ mode)", () => {
294
+ const generator = new CppHeaderGenerator();
295
+ const symbols: IHeaderSymbol[] = [
296
+ createFunctionSymbol("Renderer_flush", "void", [
297
+ { name: "w", type: "widget_t", isCallbackPointer: true },
298
+ ]),
299
+ ];
300
+
301
+ const result = generator.generate(symbols, "test.h");
302
+
303
+ // Must use * not & for callback params
304
+ expect(result).toContain("widget_t* w");
305
+ });
306
+
307
+ it("should handle mixed callback and non-callback params", () => {
308
+ const generator = new CHeaderGenerator();
309
+ const symbols: IHeaderSymbol[] = [
310
+ createFunctionSymbol("Renderer_flush", "void", [
311
+ { name: "w", type: "widget_t", isCallbackPointer: true },
312
+ {
313
+ name: "area",
314
+ type: "rect_t",
315
+ isCallbackPointer: true,
316
+ isCallbackConst: true,
317
+ },
318
+ { name: "buf", type: "u8", isCallbackPointer: true },
319
+ ]),
320
+ ];
321
+
322
+ const result = generator.generate(symbols, "test.h");
323
+
324
+ expect(result).toContain(
325
+ "void Renderer_flush(widget_t* w, const rect_t* area, uint8_t* buf);",
326
+ );
327
+ });
328
+ });
242
329
  });
@@ -6,16 +6,17 @@
6
6
  */
7
7
  class BitUtils {
8
8
  /**
9
- * Convert a boolean expression to an integer (0 or 1).
9
+ * Convert a boolean expression to an unsigned integer (0U or 1U).
10
10
  * Handles literal "true"/"false" and generates ternary for expressions.
11
+ * Uses unsigned literals for MISRA C:2012 Rule 10.1 compliance.
11
12
  *
12
13
  * @param expr - The expression to convert
13
- * @returns C code string representing the integer value
14
+ * @returns C code string representing the unsigned integer value
14
15
  */
15
16
  static boolToInt(expr: string): string {
16
- if (expr === "true") return "1";
17
- if (expr === "false") return "0";
18
- return `(${expr} ? 1 : 0)`;
17
+ if (expr === "true") return "1U";
18
+ if (expr === "false") return "0U";
19
+ return `(${expr} ? 1U : 0U)`;
19
20
  }
20
21
 
21
22
  /**
@@ -78,25 +79,27 @@ class BitUtils {
78
79
  }
79
80
 
80
81
  /**
81
- * Return the appropriate "1" literal for a given type.
82
- * Uses "1ULL" for 64-bit types to avoid undefined behavior on large shifts.
82
+ * Return the appropriate unsigned "1" literal for a given type.
83
+ * Uses "1ULL" for 64-bit types, "1U" for others.
84
+ * MISRA C:2012 Rule 10.1 requires unsigned operands for bitwise operations.
83
85
  *
84
86
  * @param typeName - The C-Next type name (e.g., "u64", "i32")
85
- * @returns "1ULL" for 64-bit types, "1" otherwise
87
+ * @returns "1ULL" for 64-bit types, "1U" otherwise
86
88
  */
87
89
  static oneForType(typeName: string): string {
88
- return typeName === "u64" || typeName === "i64" ? "1ULL" : "1";
90
+ return typeName === "u64" || typeName === "i64" ? "1ULL" : "1U";
89
91
  }
90
92
 
91
93
  /**
92
- * Format a number as an uppercase hex string (e.g., 255 -> "0xFF").
94
+ * Format a number as an unsigned uppercase hex string (e.g., 255 -> "0xFFU").
93
95
  * Used for generating hex mask literals in generated C code.
96
+ * Includes U suffix for MISRA C:2012 Rule 10.1 compliance.
94
97
  *
95
98
  * @param value - The numeric value to format
96
- * @returns Hex string like "0xFF" or "0x1F"
99
+ * @returns Hex string like "0xFFU" or "0x1FU"
97
100
  */
98
101
  static formatHex(value: number): string {
99
- return `0x${value.toString(16).toUpperCase()}`;
102
+ return `0x${value.toString(16).toUpperCase()}U`;
100
103
  }
101
104
 
102
105
  /**
@@ -154,7 +157,7 @@ class BitUtils {
154
157
  ): string {
155
158
  const intValue = BitUtils.boolToInt(value);
156
159
  const is64Bit = targetType === "u64" || targetType === "i64";
157
- const one = is64Bit ? "1ULL" : "1";
160
+ const one = is64Bit ? "1ULL" : "1U";
158
161
  // For 64-bit types, cast the value to ensure shift doesn't overflow
159
162
  const valueShift = is64Bit
160
163
  ? `((uint64_t)${intValue} << ${offset})`
@@ -203,6 +206,7 @@ class BitUtils {
203
206
  ): string {
204
207
  const intValue = BitUtils.boolToInt(value);
205
208
  // For 64-bit types, cast to ensure correct shift width
209
+ // boolToInt already returns unsigned values (1U/0U) for MISRA 10.1 compliance
206
210
  const castPrefix =
207
211
  targetType === "u64" || targetType === "i64" ? "(uint64_t)" : "";
208
212
  return `${target} = (${castPrefix}${intValue} << ${offset});`;
@@ -140,6 +140,57 @@ class ExpressionUtils {
140
140
  return identifier?.getText() ?? null;
141
141
  }
142
142
 
143
+ /**
144
+ * Collect all additive expressions from a ternary expression.
145
+ *
146
+ * Uses flatMap chains to traverse the expression grammar efficiently.
147
+ * Useful for analyzers that need to examine operands at the additive level.
148
+ *
149
+ * @param ctx - The ternary expression context
150
+ * @returns Array of all additive expression contexts in the tree
151
+ */
152
+ static collectAdditiveExpressions(
153
+ ctx: Parser.TernaryExpressionContext,
154
+ ): Parser.AdditiveExpressionContext[] {
155
+ return ExpressionUtils.collectAdditiveFromOrExprs(ctx.orExpression());
156
+ }
157
+
158
+ /**
159
+ * Collect additive expressions from an array of orExpression contexts.
160
+ *
161
+ * Internal helper that performs the actual flatMap traversal.
162
+ */
163
+ private static collectAdditiveFromOrExprs(
164
+ orExprs: Parser.OrExpressionContext[],
165
+ ): Parser.AdditiveExpressionContext[] {
166
+ return orExprs
167
+ .flatMap((or) => or.andExpression())
168
+ .flatMap((and) => and.equalityExpression())
169
+ .flatMap((eq) => eq.relationalExpression())
170
+ .flatMap((rel) => rel.bitwiseOrExpression())
171
+ .flatMap((bor) => bor.bitwiseXorExpression())
172
+ .flatMap((bxor) => bxor.bitwiseAndExpression())
173
+ .flatMap((band) => band.shiftExpression())
174
+ .flatMap((shift) => shift.additiveExpression());
175
+ }
176
+
177
+ /**
178
+ * Collect all unary expressions from an orExpression context.
179
+ *
180
+ * Traverses through the entire expression hierarchy to find all unary expressions.
181
+ * Useful for analyzers that need to examine leaf operands.
182
+ *
183
+ * @param orExpr - The orExpression context
184
+ * @returns Array of all unary expression contexts in the tree
185
+ */
186
+ static collectUnaryFromOrExpr(
187
+ orExpr: Parser.OrExpressionContext,
188
+ ): Parser.UnaryExpressionContext[] {
189
+ return ExpressionUtils.collectAdditiveFromOrExprs([orExpr])
190
+ .flatMap((add) => add.multiplicativeExpression())
191
+ .flatMap((mul) => mul.unaryExpression());
192
+ }
193
+
143
194
  /**
144
195
  * ADR-023: Check if an expression contains a function call anywhere in its tree.
145
196
  *