c-next 0.2.6 → 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 (37) hide show
  1. package/dist/index.js +387 -433
  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/PassByValueAnalyzer.ts +1 -12
  9. package/src/transpiler/logic/analysis/SignedShiftAnalyzer.ts +239 -0
  10. package/src/transpiler/logic/analysis/__tests__/SignedShiftAnalyzer.test.ts +414 -0
  11. package/src/transpiler/logic/analysis/__tests__/StructFieldAnalyzer.test.ts +15 -75
  12. package/src/transpiler/logic/analysis/__tests__/runAnalyzers.test.ts +3 -15
  13. package/src/transpiler/logic/analysis/runAnalyzers.ts +11 -2
  14. package/src/transpiler/logic/analysis/types/ISignedShiftError.ts +15 -0
  15. package/src/transpiler/logic/symbols/SymbolUtils.ts +4 -1
  16. package/src/transpiler/logic/symbols/__tests__/SymbolUtils.test.ts +10 -11
  17. package/src/transpiler/logic/symbols/c/__tests__/CResolver.integration.test.ts +4 -4
  18. package/src/transpiler/logic/symbols/cpp/__tests__/CppResolver.integration.test.ts +4 -4
  19. package/src/transpiler/output/codegen/CodeGenerator.ts +12 -4
  20. package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +3 -3
  21. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +26 -26
  22. package/src/transpiler/output/codegen/analysis/StringLengthCounter.ts +12 -11
  23. package/src/transpiler/output/codegen/analysis/__tests__/StringLengthCounter.test.ts +21 -21
  24. package/src/transpiler/output/codegen/generators/expressions/AccessExprGenerator.ts +7 -326
  25. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +14 -275
  26. package/src/transpiler/output/codegen/generators/expressions/UnaryExprGenerator.ts +13 -1
  27. package/src/transpiler/output/codegen/generators/expressions/__tests__/AccessExprGenerator.test.ts +0 -573
  28. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +8 -439
  29. package/src/transpiler/output/codegen/generators/expressions/__tests__/UnaryExprGenerator.test.ts +196 -0
  30. package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +7 -2
  31. package/src/transpiler/output/codegen/helpers/TypedefParamParser.ts +34 -0
  32. package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +48 -0
  33. package/src/transpiler/output/codegen/helpers/__tests__/TypedefParamParser.test.ts +88 -0
  34. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +15 -2
  35. package/src/transpiler/output/headers/__tests__/BaseHeaderGenerator.test.ts +87 -0
  36. package/src/utils/ExpressionUtils.ts +51 -0
  37. package/src/utils/types/IParameterSymbol.ts +2 -0
@@ -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;
@@ -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
  });
@@ -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
  });
@@ -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
  *
@@ -8,6 +8,8 @@ interface IParameterSymbol {
8
8
  isArray: boolean;
9
9
  arrayDimensions?: string[]; // e.g., ["10", "20"] or ["", ""] for unbounded
10
10
  isAutoConst?: boolean; // Issue #268: true if parameter should get auto-const (unmodified pointer)
11
+ isCallbackPointer?: boolean; // Issue #914: typedef says this param must be a pointer
12
+ isCallbackConst?: boolean; // Issue #914: typedef says this param must be const
11
13
  }
12
14
 
13
15
  export default IParameterSymbol;