c-next 0.2.11 → 0.2.13

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 (75) hide show
  1. package/README.md +80 -649
  2. package/dist/index.js +8273 -6357
  3. package/dist/index.js.map +4 -4
  4. package/grammar/C.g4 +17 -4
  5. package/package.json +3 -3
  6. package/src/__tests__/index.test.ts +1 -1
  7. package/src/cli/CleanCommand.ts +8 -12
  8. package/src/cli/Cli.ts +29 -6
  9. package/src/cli/Runner.ts +42 -62
  10. package/src/cli/__tests__/CleanCommand.test.ts +10 -10
  11. package/src/cli/__tests__/Cli.test.ts +59 -7
  12. package/src/cli/__tests__/ConfigPrinter.test.ts +12 -12
  13. package/src/cli/__tests__/PathNormalizer.test.ts +5 -5
  14. package/src/cli/__tests__/Runner.test.ts +108 -82
  15. package/src/cli/serve/ServeCommand.ts +1 -1
  16. package/src/cli/types/ICliConfig.ts +2 -2
  17. package/src/lib/parseWithSymbols.ts +21 -21
  18. package/src/transpiler/Transpiler.ts +99 -46
  19. package/src/transpiler/__tests__/DualCodePaths.test.ts +29 -29
  20. package/src/transpiler/__tests__/Transpiler.coverage.test.ts +244 -72
  21. package/src/transpiler/__tests__/Transpiler.test.ts +32 -72
  22. package/src/transpiler/__tests__/determineProjectRoot.test.ts +30 -28
  23. package/src/transpiler/__tests__/needsConditionalPreprocessing.test.ts +1 -1
  24. package/src/transpiler/data/CNextMarkerDetector.ts +34 -0
  25. package/src/transpiler/data/CppEntryPointScanner.ts +174 -0
  26. package/src/transpiler/data/FileDiscovery.ts +2 -105
  27. package/src/transpiler/data/InputExpansion.ts +37 -81
  28. package/src/transpiler/data/__tests__/CNextMarkerDetector.test.ts +62 -0
  29. package/src/transpiler/data/__tests__/CppEntryPointScanner.test.ts +239 -0
  30. package/src/transpiler/data/__tests__/FileDiscovery.test.ts +45 -191
  31. package/src/transpiler/data/__tests__/InputExpansion.test.ts +36 -204
  32. package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +2 -2
  33. package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +4 -5
  34. package/src/transpiler/logic/parser/c/grammar/C.interp +33 -3
  35. package/src/transpiler/logic/parser/c/grammar/C.tokens +237 -207
  36. package/src/transpiler/logic/parser/c/grammar/CLexer.interp +48 -3
  37. package/src/transpiler/logic/parser/c/grammar/CLexer.tokens +237 -207
  38. package/src/transpiler/logic/parser/c/grammar/CLexer.ts +702 -611
  39. package/src/transpiler/logic/parser/c/grammar/CParser.ts +1221 -1107
  40. package/src/transpiler/logic/symbols/SymbolTable.ts +147 -73
  41. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +157 -14
  42. package/src/transpiler/logic/symbols/c/__tests__/CResolver.integration.test.ts +3 -3
  43. package/src/transpiler/logic/symbols/c/collectors/StructCollector.ts +7 -37
  44. package/src/transpiler/logic/symbols/cnext/__tests__/TSymbolInfoAdapter.test.ts +6 -6
  45. package/src/transpiler/logic/symbols/cnext/adapters/TSymbolInfoAdapter.ts +28 -27
  46. package/src/transpiler/logic/symbols/cnext/index.ts +4 -4
  47. package/src/transpiler/logic/symbols/cnext/utils/SymbolNameUtils.ts +5 -5
  48. package/src/transpiler/output/codegen/CodeGenerator.ts +16 -1
  49. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +15 -0
  50. package/src/transpiler/output/codegen/__tests__/ExpressionWalker.test.ts +3 -3
  51. package/src/transpiler/output/codegen/__tests__/RequireInclude.test.ts +14 -14
  52. package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +2 -2
  53. package/src/transpiler/output/codegen/helpers/FunctionContextManager.ts +15 -3
  54. package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +14 -6
  55. package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +1 -0
  56. package/src/transpiler/output/codegen/types/IFunctionContextCallbacks.ts +2 -0
  57. package/src/transpiler/output/codegen/utils/QualifiedNameGenerator.ts +7 -7
  58. package/src/transpiler/output/codegen/utils/__tests__/QualifiedNameGenerator.test.ts +3 -3
  59. package/src/transpiler/output/headers/BaseHeaderGenerator.ts +10 -1
  60. package/src/transpiler/output/headers/HeaderGenerator.ts +3 -0
  61. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +6 -2
  62. package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +16 -0
  63. package/src/transpiler/output/headers/adapters/HeaderSymbolAdapter.ts +19 -19
  64. package/src/transpiler/output/headers/adapters/__tests__/HeaderSymbolAdapter.test.ts +5 -5
  65. package/src/transpiler/state/SymbolRegistry.ts +10 -12
  66. package/src/transpiler/state/__tests__/SymbolRegistry.test.ts +11 -13
  67. package/src/transpiler/types/ICachedFileEntry.ts +4 -0
  68. package/src/transpiler/types/IPipelineFile.ts +3 -0
  69. package/src/transpiler/types/ITranspilerConfig.ts +2 -2
  70. package/src/transpiler/types/symbols/IScopeSymbol.ts +1 -1
  71. package/src/utils/FunctionUtils.ts +3 -3
  72. package/src/utils/__tests__/FunctionUtils.test.ts +6 -4
  73. package/src/utils/cache/CacheManager.ts +28 -15
  74. package/src/utils/cache/__tests__/CacheManager.test.ts +6 -4
  75. package/src/transpiler/data/types/IDiscoveryOptions.ts +0 -15
@@ -466,7 +466,7 @@ describe("TSymbolInfoAdapter", () => {
466
466
  const motorScope = TestScopeUtils.createMockScope("Motor");
467
467
  const variable: IVariableSymbol = {
468
468
  kind: "variable",
469
- name: "MAX_SPEED", // Bare name - adapter computes mangled name
469
+ name: "MAX_SPEED", // Bare name - adapter computes transpiled C name
470
470
  scope: motorScope,
471
471
  sourceFile: "test.cnx",
472
472
  sourceLine: 1,
@@ -481,7 +481,7 @@ describe("TSymbolInfoAdapter", () => {
481
481
 
482
482
  const info = TSymbolInfoAdapter.convert([variable]);
483
483
 
484
- // Adapter stores using mangled name (scope + bare name)
484
+ // Adapter stores using transpiled C name (scope + bare name)
485
485
  expect(info.scopePrivateConstValues.get("Motor_MAX_SPEED")).toBe("255");
486
486
  });
487
487
 
@@ -489,7 +489,7 @@ describe("TSymbolInfoAdapter", () => {
489
489
  const motorScope = TestScopeUtils.createMockScope("Motor");
490
490
  const variable: IVariableSymbol = {
491
491
  kind: "variable",
492
- name: "PUBLIC_CONST", // Bare name - adapter computes mangled name
492
+ name: "PUBLIC_CONST", // Bare name - adapter computes transpiled C name
493
493
  scope: motorScope,
494
494
  sourceFile: "test.cnx",
495
495
  sourceLine: 1,
@@ -513,7 +513,7 @@ describe("TSymbolInfoAdapter", () => {
513
513
  const motorScope = TestScopeUtils.createMockScope("Motor");
514
514
  const variable: IVariableSymbol = {
515
515
  kind: "variable",
516
- name: "counter", // Bare name - adapter computes mangled name
516
+ name: "counter", // Bare name - adapter computes transpiled C name
517
517
  scope: motorScope,
518
518
  sourceFile: "test.cnx",
519
519
  sourceLine: 1,
@@ -535,7 +535,7 @@ describe("TSymbolInfoAdapter", () => {
535
535
  const motorScope = TestScopeUtils.createMockScope("Motor");
536
536
  const variable: IVariableSymbol = {
537
537
  kind: "variable",
538
- name: "LOOKUP_TABLE", // Bare name - adapter computes mangled name
538
+ name: "LOOKUP_TABLE", // Bare name - adapter computes transpiled C name
539
539
  scope: motorScope,
540
540
  sourceFile: "test.cnx",
541
541
  sourceLine: 1,
@@ -562,7 +562,7 @@ describe("TSymbolInfoAdapter", () => {
562
562
  const motorScope = TestScopeUtils.createMockScope("Motor");
563
563
  const variable: IVariableSymbol = {
564
564
  kind: "variable",
565
- name: "MATRIX", // Bare name - adapter computes mangled name
565
+ name: "MATRIX", // Bare name - adapter computes transpiled C name
566
566
  scope: motorScope,
567
567
  sourceFile: "test.cnx",
568
568
  sourceLine: 1,
@@ -239,8 +239,9 @@ class TSymbolInfoAdapter {
239
239
 
240
240
  // === Private Processing Methods ===
241
241
 
242
- // Use shared utility for name mangling
243
- private static readonly getMangledName = SymbolNameUtils.getMangledName;
242
+ // Use shared utility for transpiled C names
243
+ private static readonly getTranspiledCName =
244
+ SymbolNameUtils.getTranspiledCName;
244
245
 
245
246
  private static processStruct(
246
247
  struct: IStructSymbol,
@@ -249,9 +250,9 @@ class TSymbolInfoAdapter {
249
250
  structFieldArrays: Map<string, Set<string>>,
250
251
  structFieldDimensions: Map<string, Map<string, number[]>>,
251
252
  ): void {
252
- // Use mangled name for lookups (e.g., "Geometry_Point")
253
- const mangledName = TSymbolInfoAdapter.getMangledName(struct);
254
- knownStructs.add(mangledName);
253
+ // Use transpiled C name for lookups (e.g., "Geometry_Point")
254
+ const cName = TSymbolInfoAdapter.getTranspiledCName(struct);
255
+ knownStructs.add(cName);
255
256
 
256
257
  const fields = new Map<string, string>();
257
258
  const arrayFields = new Set<string>();
@@ -277,10 +278,10 @@ class TSymbolInfoAdapter {
277
278
  }
278
279
  }
279
280
 
280
- structFields.set(mangledName, fields);
281
- structFieldArrays.set(mangledName, arrayFields);
281
+ structFields.set(cName, fields);
282
+ structFieldArrays.set(cName, arrayFields);
282
283
  if (dimensions.size > 0) {
283
- structFieldDimensions.set(mangledName, dimensions);
284
+ structFieldDimensions.set(cName, dimensions);
284
285
  }
285
286
  }
286
287
 
@@ -289,9 +290,9 @@ class TSymbolInfoAdapter {
289
290
  knownEnums: Set<string>,
290
291
  enumMembers: Map<string, Map<string, number>>,
291
292
  ): void {
292
- const mangledName = TSymbolInfoAdapter.getMangledName(enumSym);
293
- knownEnums.add(mangledName);
294
- enumMembers.set(mangledName, new Map(enumSym.members));
293
+ const cName = TSymbolInfoAdapter.getTranspiledCName(enumSym);
294
+ knownEnums.add(cName);
295
+ enumMembers.set(cName, new Map(enumSym.members));
295
296
  }
296
297
 
297
298
  private static processBitmap(
@@ -301,10 +302,10 @@ class TSymbolInfoAdapter {
301
302
  bitmapBackingType: Map<string, string>,
302
303
  bitmapBitWidth: Map<string, number>,
303
304
  ): void {
304
- const mangledName = TSymbolInfoAdapter.getMangledName(bitmap);
305
- knownBitmaps.add(mangledName);
306
- bitmapBackingType.set(mangledName, bitmap.backingType);
307
- bitmapBitWidth.set(mangledName, bitmap.bitWidth);
305
+ const cName = TSymbolInfoAdapter.getTranspiledCName(bitmap);
306
+ knownBitmaps.add(cName);
307
+ bitmapBackingType.set(cName, bitmap.backingType);
308
+ bitmapBitWidth.set(cName, bitmap.bitWidth);
308
309
 
309
310
  const fields = new Map<string, { offset: number; width: number }>();
310
311
  for (const [fieldName, fieldInfo] of bitmap.fields) {
@@ -313,7 +314,7 @@ class TSymbolInfoAdapter {
313
314
  width: fieldInfo.width,
314
315
  });
315
316
  }
316
- bitmapFields.set(mangledName, fields);
317
+ bitmapFields.set(cName, fields);
317
318
  }
318
319
 
319
320
  private static processScope(
@@ -338,18 +339,18 @@ class TSymbolInfoAdapter {
338
339
  knownBitmaps: Set<string>,
339
340
  maps: IRegisterMaps,
340
341
  ): void {
341
- const mangledName = TSymbolInfoAdapter.getMangledName(register);
342
- maps.knownRegisters.add(mangledName);
343
- maps.registerBaseAddresses.set(mangledName, register.baseAddress);
342
+ const cName = TSymbolInfoAdapter.getTranspiledCName(register);
343
+ maps.knownRegisters.add(cName);
344
+ maps.registerBaseAddresses.set(cName, register.baseAddress);
344
345
 
345
346
  // Check if this is a scoped register (has non-global scope)
346
347
  const isScoped = register.scope.name !== "";
347
348
  if (isScoped) {
348
- maps.scopedRegisters.set(mangledName, register.baseAddress);
349
+ maps.scopedRegisters.set(cName, register.baseAddress);
349
350
  }
350
351
 
351
352
  for (const [memberName, memberInfo] of register.members) {
352
- const fullName = `${mangledName}_${memberName}`;
353
+ const fullName = `${cName}_${memberName}`;
353
354
 
354
355
  maps.registerMemberAccess.set(fullName, memberInfo.access);
355
356
  maps.registerMemberOffsets.set(fullName, memberInfo.offset);
@@ -370,7 +371,7 @@ class TSymbolInfoAdapter {
370
371
  scopeMembers: Map<string, Set<string>>,
371
372
  scopePrivateConstValues: Map<string, string>,
372
373
  ): void {
373
- const mangledName = TSymbolInfoAdapter.getMangledName(variable);
374
+ const cName = TSymbolInfoAdapter.getTranspiledCName(variable);
374
375
  const scopeName = variable.scope.name;
375
376
  const isScoped = scopeName !== "";
376
377
 
@@ -381,7 +382,7 @@ class TSymbolInfoAdapter {
381
382
  members = new Set<string>();
382
383
  scopeMembers.set(scopeName, members);
383
384
  }
384
- members.add(variable.name); // Add local name (e.g., "value"), not mangled
385
+ members.add(variable.name); // Add local name (e.g., "value"), not transpiled C name
385
386
  }
386
387
 
387
388
  // Issue #282: Track private const values for inlining
@@ -395,7 +396,7 @@ class TSymbolInfoAdapter {
395
396
  variable.initialValue &&
396
397
  !variable.isArray
397
398
  ) {
398
- scopePrivateConstValues.set(mangledName, variable.initialValue);
399
+ scopePrivateConstValues.set(cName, variable.initialValue);
399
400
  }
400
401
  }
401
402
 
@@ -405,10 +406,10 @@ class TSymbolInfoAdapter {
405
406
  ): void {
406
407
  // Track function return types for enum validation in assignments
407
408
  // This enables recognizing that Motor.getMode() returns Motor_EMode
408
- // Use mangled name (e.g., "Motor_getMode") for lookup consistency
409
- const mangledName = TSymbolInfoAdapter.getMangledName(func);
409
+ // Use transpiled C name (e.g., "Motor_getMode") for lookup consistency
410
+ const cName = TSymbolInfoAdapter.getTranspiledCName(func);
410
411
  const returnTypeStr = TypeResolver.getTypeName(func.returnType);
411
- functionReturnTypes.set(mangledName, returnTypeStr);
412
+ functionReturnTypes.set(cName, returnTypeStr);
412
413
  }
413
414
 
414
415
  private static cnextTypeToCType(typeName: string): string {
@@ -164,7 +164,7 @@ class CNextResolver {
164
164
  globalScope,
165
165
  );
166
166
  symbols.push(symbol);
167
- // Use mangled name (global bitmaps have no scope prefix)
167
+ // Use transpiled C name (global bitmaps have no scope prefix)
168
168
  knownBitmaps.add(symbol.name);
169
169
  }
170
170
 
@@ -200,9 +200,9 @@ class CNextResolver {
200
200
  const bitmapCtx = member.bitmapDeclaration()!;
201
201
  const symbol = BitmapCollector.collect(bitmapCtx, sourceFile, scope);
202
202
  symbols.push(symbol);
203
- // Use mangled name (e.g., "Timer_ControlBits") for scoped bitmaps
204
- const mangledName = `${scopeName}_${symbol.name}`;
205
- knownBitmaps.add(mangledName);
203
+ // Use transpiled C name (e.g., "Timer_ControlBits") for scoped bitmaps
204
+ const cName = `${scopeName}_${symbol.name}`;
205
+ knownBitmaps.add(cName);
206
206
  }
207
207
 
208
208
  // Collect structs early so they're available as types
@@ -3,13 +3,13 @@
3
3
  */
4
4
 
5
5
  /**
6
- * Get the C-mangled name for a symbol (e.g., "Geometry_Point" for Point in Geometry scope).
7
- * Works with any symbol that has a name and scope reference.
6
+ * Get the transpiled C name for a symbol (e.g., "Geometry_Point" for Point in Geometry scope).
7
+ * Use only at the output layer for C code generation, not for input-side logic.
8
8
  *
9
9
  * @param symbol Object with name and scope.name properties
10
- * @returns The mangled name (e.g., "Motor_init") or bare name if global scope
10
+ * @returns The C output name (e.g., "Motor_init") or bare name if global scope
11
11
  */
12
- function getMangledName(symbol: {
12
+ function getTranspiledCName(symbol: {
13
13
  name: string;
14
14
  scope: { name: string };
15
15
  }): string {
@@ -21,7 +21,7 @@ function getMangledName(symbol: {
21
21
  }
22
22
 
23
23
  class SymbolNameUtils {
24
- static readonly getMangledName = getMangledName;
24
+ static readonly getTranspiledCName = getTranspiledCName;
25
25
  }
26
26
 
27
27
  export default SymbolNameUtils;
@@ -3,6 +3,7 @@
3
3
  * Transforms C-Next AST to clean, readable C code
4
4
  */
5
5
 
6
+ import { basename } from "node:path";
6
7
  import { CommonTokenStream, ParserRuleContext } from "antlr4ng";
7
8
  import * as Parser from "../../logic/parser/grammar/CNextParser";
8
9
 
@@ -2225,9 +2226,14 @@ export default class CodeGenerator implements IOrchestrator {
2225
2226
  const output: string[] = [];
2226
2227
  const symbols = CodeGenState.symbols!;
2227
2228
  // Add header comment
2229
+ const sourcePath = CodeGenState.sourcePath;
2230
+ const generatedLine = sourcePath
2231
+ ? ` * Generated by C-Next Transpiler from: ${basename(sourcePath)}`
2232
+ : " * Generated by C-Next Transpiler";
2233
+
2228
2234
  output.push(
2229
2235
  "/**",
2230
- " * Generated by C-Next Transpiler",
2236
+ generatedLine,
2231
2237
  " * A safer C for embedded systems",
2232
2238
  " */",
2233
2239
  "",
@@ -3675,6 +3681,8 @@ export default class CodeGenerator implements IOrchestrator {
3675
3681
  isStructType: (typeName: string) => this.isStructType(typeName),
3676
3682
  resolveQualifiedType: (identifiers: string[]) =>
3677
3683
  this.resolveQualifiedType(identifiers),
3684
+ isTypedefStructType: (t: string) =>
3685
+ CodeGenState.symbolTable?.isTypedefStructType(t) ?? false,
3678
3686
  };
3679
3687
  }
3680
3688
 
@@ -3766,6 +3774,8 @@ export default class CodeGenerator implements IOrchestrator {
3766
3774
  isCallbackCompatible,
3767
3775
  forcePassByReference,
3768
3776
  forceConst,
3777
+ isTypedefStructType: (t) =>
3778
+ CodeGenState.symbolTable?.isTypedefStructType(t) ?? false,
3769
3779
  });
3770
3780
 
3771
3781
  // Use shared builder with C/C++ mode
@@ -3892,6 +3902,11 @@ export default class CodeGenerator implements IOrchestrator {
3892
3902
  ): string {
3893
3903
  const type = this.generateType(ctx.type());
3894
3904
 
3905
+ // Issue #958: C-header typedef struct types always need pointer semantics
3906
+ if (CodeGenState.symbolTable?.isTypedefStructType(type)) {
3907
+ return `${type}*`;
3908
+ }
3909
+
3895
3910
  if (!ctx.expression()) {
3896
3911
  return type;
3897
3912
  }
@@ -164,6 +164,21 @@ describe("CodeGenerator", () => {
164
164
 
165
165
  expect(code).toContain("void foo");
166
166
  });
167
+
168
+ it("should include source path in generation comment", () => {
169
+ const source = `void test() { }`;
170
+ const { tree, tokenStream } = CNextSourceParser.parse(source);
171
+ const generator = new CodeGenerator();
172
+ const tSymbols = CNextResolver.resolve(tree, "test.cnx");
173
+ const symbols = TSymbolInfoAdapter.convert(tSymbols);
174
+
175
+ const code = generator.generate(tree, tokenStream, {
176
+ symbolInfo: symbols,
177
+ sourcePath: "led.cnx",
178
+ });
179
+
180
+ expect(code).toContain("Generated by C-Next Transpiler from: led.cnx");
181
+ });
167
182
  });
168
183
 
169
184
  describe("IOrchestrator interface", () => {
@@ -311,7 +311,7 @@ describe("ExpressionWalker - const inference integration", () => {
311
311
  caller(val);
312
312
  }
313
313
  `;
314
- const transpiler = new Transpiler({ inputs: [] });
314
+ const transpiler = new Transpiler({ input: "" });
315
315
  const transpileResult = (
316
316
  await transpiler.transpile({ kind: "source", source: source })
317
317
  ).files[0];
@@ -341,7 +341,7 @@ describe("ExpressionWalker - const inference integration", () => {
341
341
  caller(val);
342
342
  }
343
343
  `;
344
- const transpiler = new Transpiler({ inputs: [] });
344
+ const transpiler = new Transpiler({ input: "" });
345
345
  const transpileResult = (
346
346
  await transpiler.transpile({ kind: "source", source: source })
347
347
  ).files[0];
@@ -370,7 +370,7 @@ describe("ExpressionWalker - const inference integration", () => {
370
370
  caller(val);
371
371
  }
372
372
  `;
373
- const transpiler = new Transpiler({ inputs: [] });
373
+ const transpiler = new Transpiler({ input: "" });
374
374
  const transpileResult = (
375
375
  await transpiler.transpile({ kind: "source", source: source })
376
376
  ).files[0];
@@ -18,7 +18,7 @@ describe("CodeGenerator requireInclude", () => {
18
18
 
19
19
  describe("stdint includes", () => {
20
20
  it("includes stdint.h for u8 type", async () => {
21
- const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
21
+ const transpiler = new Transpiler({ input: "", noCache: true }, mockFs);
22
22
 
23
23
  const result = (
24
24
  await transpiler.transpile({ kind: "source", source: "u8 value <- 0;" })
@@ -29,7 +29,7 @@ describe("CodeGenerator requireInclude", () => {
29
29
  });
30
30
 
31
31
  it("includes stdint.h for u16 type", async () => {
32
- const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
32
+ const transpiler = new Transpiler({ input: "", noCache: true }, mockFs);
33
33
 
34
34
  const result = (
35
35
  await transpiler.transpile({
@@ -43,7 +43,7 @@ describe("CodeGenerator requireInclude", () => {
43
43
  });
44
44
 
45
45
  it("includes stdint.h for u32 type", async () => {
46
- const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
46
+ const transpiler = new Transpiler({ input: "", noCache: true }, mockFs);
47
47
 
48
48
  const result = (
49
49
  await transpiler.transpile({
@@ -57,7 +57,7 @@ describe("CodeGenerator requireInclude", () => {
57
57
  });
58
58
 
59
59
  it("includes stdint.h for i32 type", async () => {
60
- const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
60
+ const transpiler = new Transpiler({ input: "", noCache: true }, mockFs);
61
61
 
62
62
  const result = (
63
63
  await transpiler.transpile({
@@ -71,7 +71,7 @@ describe("CodeGenerator requireInclude", () => {
71
71
  });
72
72
 
73
73
  it("includes stdint.h for bitmap types", async () => {
74
- const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
74
+ const transpiler = new Transpiler({ input: "", noCache: true }, mockFs);
75
75
 
76
76
  const result = (
77
77
  await transpiler.transpile({
@@ -94,7 +94,7 @@ describe("CodeGenerator requireInclude", () => {
94
94
 
95
95
  describe("stdbool includes", () => {
96
96
  it("includes stdbool.h for bool type", async () => {
97
- const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
97
+ const transpiler = new Transpiler({ input: "", noCache: true }, mockFs);
98
98
 
99
99
  const result = (
100
100
  await transpiler.transpile({
@@ -110,7 +110,7 @@ describe("CodeGenerator requireInclude", () => {
110
110
 
111
111
  describe("string includes", () => {
112
112
  it("includes string.h for bounded string type", async () => {
113
- const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
113
+ const transpiler = new Transpiler({ input: "", noCache: true }, mockFs);
114
114
 
115
115
  const result = (
116
116
  await transpiler.transpile({
@@ -124,7 +124,7 @@ describe("CodeGenerator requireInclude", () => {
124
124
  });
125
125
 
126
126
  it("includes string.h for const string inference", async () => {
127
- const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
127
+ const transpiler = new Transpiler({ input: "", noCache: true }, mockFs);
128
128
 
129
129
  const result = (
130
130
  await transpiler.transpile({
@@ -140,7 +140,7 @@ describe("CodeGenerator requireInclude", () => {
140
140
 
141
141
  describe("isr includes", () => {
142
142
  it("generates ISR typedef for ISR type", async () => {
143
- const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
143
+ const transpiler = new Transpiler({ input: "", noCache: true }, mockFs);
144
144
 
145
145
  const result = (
146
146
  await transpiler.transpile({
@@ -156,7 +156,7 @@ describe("CodeGenerator requireInclude", () => {
156
156
 
157
157
  describe("float static assert includes", () => {
158
158
  it("generates static assert for float bit indexing write", async () => {
159
- const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
159
+ const transpiler = new Transpiler({ input: "", noCache: true }, mockFs);
160
160
 
161
161
  const result = (
162
162
  await transpiler.transpile({
@@ -177,7 +177,7 @@ describe("CodeGenerator requireInclude", () => {
177
177
  });
178
178
 
179
179
  it("generates static assert for float bit indexing read (no string.h)", async () => {
180
- const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
180
+ const transpiler = new Transpiler({ input: "", noCache: true }, mockFs);
181
181
 
182
182
  const result = (
183
183
  await transpiler.transpile({
@@ -201,7 +201,7 @@ describe("CodeGenerator requireInclude", () => {
201
201
 
202
202
  describe("limits includes", () => {
203
203
  it("includes limits.h for float-to-int clamp cast", async () => {
204
- const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
204
+ const transpiler = new Transpiler({ input: "", noCache: true }, mockFs);
205
205
 
206
206
  const result = (
207
207
  await transpiler.transpile({
@@ -221,7 +221,7 @@ describe("CodeGenerator requireInclude", () => {
221
221
 
222
222
  describe("multiple includes", () => {
223
223
  it("includes multiple headers when needed", async () => {
224
- const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
224
+ const transpiler = new Transpiler({ input: "", noCache: true }, mockFs);
225
225
 
226
226
  const result = (
227
227
  await transpiler.transpile({
@@ -241,7 +241,7 @@ describe("CodeGenerator requireInclude", () => {
241
241
  });
242
242
 
243
243
  it("does not include unused headers", async () => {
244
- const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
244
+ const transpiler = new Transpiler({ input: "", noCache: true }, mockFs);
245
245
 
246
246
  const result = (
247
247
  await transpiler.transpile({
@@ -11,7 +11,7 @@ import Transpiler from "../../../Transpiler";
11
11
  * Helper to transpile C-Next source and return the C output
12
12
  */
13
13
  async function transpileSource(source: string): Promise<string> {
14
- const transpiler = new Transpiler({ inputs: [] });
14
+ const transpiler = new Transpiler({ input: "" });
15
15
  const result = (
16
16
  await transpiler.transpile({ kind: "source", source: source })
17
17
  ).files[0];
@@ -130,7 +130,7 @@ describe("trackVariableTypeWithName helpers", () => {
130
130
  }
131
131
  `;
132
132
  const code = await transpileSource(source);
133
- // Scoped type should be mangled to Motor_State
133
+ // Scoped type should be transpiled to Motor_State
134
134
  expect(code).toContain("Motor_State");
135
135
  });
136
136
 
@@ -160,19 +160,27 @@ class FunctionContextManager {
160
160
  const isCallbackPointerParam =
161
161
  callbackTypedefInfo?.shouldBePointer ?? false;
162
162
 
163
+ // Issue #958: Check if type is a typedef'd struct from C headers
164
+ const isTypedefStruct =
165
+ callbacks.isTypedefStructType?.(typeInfo.typeName) ?? false;
166
+
163
167
  // Determine isStruct: for callback-compatible params, both typedef AND type info matter
164
168
  // - If typedef says pointer AND it's actually a struct, use -> access (isStruct=true)
165
169
  // - If typedef says pointer BUT it's a primitive (like u8), don't treat as struct
166
170
  // (primitives use forcePointerSemantics for dereference instead)
171
+ // Issue #958: C-header typedef struct types are always treated as struct (pointer semantics)
167
172
  const isStruct = callbackTypedefInfo
168
173
  ? isCallbackPointerParam && typeInfo.isStruct
169
- : typeInfo.isStruct;
174
+ : typeInfo.isStruct || isTypedefStruct;
170
175
 
171
176
  // Issue #895: Primitive types that become pointers need dereferencing when used as values
172
177
  // e.g., "u8 buf" becoming "uint8_t* buf" requires "*buf" when accessing the value
173
178
  const isCallbackPointerPrimitive =
174
179
  isCallbackPointerParam && !typeInfo.isStruct && !isArray;
175
180
 
181
+ // Issue #958: typedef struct params need pointer semantics (like callback pointer params)
182
+ const forcePointerSemantics = isCallbackPointerParam || isTypedefStruct;
183
+
176
184
  // Register in currentParameters
177
185
  const paramInfo = {
178
186
  name,
@@ -183,8 +191,8 @@ class FunctionContextManager {
183
191
  isCallback: typeInfo.isCallback,
184
192
  isString: typeInfo.isString,
185
193
  isCallbackPointerPrimitive,
186
- // Issue #895: Callback-compatible params need pointer semantics even in C++ mode
187
- forcePointerSemantics: isCallbackPointerParam,
194
+ // Issue #895/#958: Force pointer semantics for callback-compatible and typedef struct params
195
+ forcePointerSemantics,
188
196
  };
189
197
  CodeGenState.currentParameters.set(name, paramInfo);
190
198
 
@@ -195,6 +203,7 @@ class FunctionContextManager {
195
203
  param,
196
204
  isArray,
197
205
  isConst,
206
+ isTypedefStruct,
198
207
  );
199
208
  }
200
209
 
@@ -320,6 +329,7 @@ class FunctionContextManager {
320
329
  param: Parser.ParameterContext,
321
330
  isArray: boolean,
322
331
  isConst: boolean,
332
+ isTypedefStruct = false,
323
333
  ): void {
324
334
  const { typeName, isString } = typeInfo;
325
335
  const typeCtx = param.type();
@@ -358,6 +368,8 @@ class FunctionContextManager {
358
368
  isString,
359
369
  stringCapacity,
360
370
  isParameter: true,
371
+ // Issue #958: typedef struct params are already pointers — prevent &arg in call sites
372
+ ...(isTypedefStruct && { isPointer: true }),
361
373
  };
362
374
  CodeGenState.setVariableTypeInfo(name, registeredType);
363
375
  }
@@ -52,6 +52,9 @@ interface IFromASTDeps {
52
52
  */
53
53
  forcePassByReference?: boolean;
54
54
 
55
+ /** Issue #958: Check if a type name is a typedef'd struct from C headers */
56
+ isTypedefStructType: (typeName: string) => boolean;
57
+
55
58
  /**
56
59
  * Issue #895: Force const qualifier from callback typedef signature.
57
60
  * When the C typedef has `const T*`, this preserves const on the generated param.
@@ -137,15 +140,19 @@ class ParameterInputAdapter {
137
140
  // Determine classification for non-array, non-string types
138
141
  const isKnownStruct = deps.isKnownStruct(typeName);
139
142
  const isKnownPrimitive = !!deps.typeMap[typeName];
143
+ // Issue #958: C-header typedef struct types need pointer semantics
144
+ const isTypedefStruct = deps.isTypedefStructType(typeName);
140
145
  // Issue #895: Don't add auto-const for callback-compatible functions
141
146
  // because it would change the signature and break typedef compatibility
142
147
  const isAutoConst =
143
148
  !deps.isCallbackCompatible && !deps.isModified && !isConst;
144
149
 
145
- // Issue #895: For callback-compatible functions, force pass-by-reference
146
- // when the typedef signature requires a pointer (e.g., opaque types)
150
+ // Issue #895/#958: Force pass-by-reference for callback or typedef struct types
147
151
  const isPassByReference =
148
- deps.forcePassByReference || isKnownStruct || isKnownPrimitive;
152
+ deps.forcePassByReference ||
153
+ isKnownStruct ||
154
+ isKnownPrimitive ||
155
+ isTypedefStruct;
149
156
 
150
157
  return {
151
158
  name,
@@ -158,9 +165,10 @@ class ParameterInputAdapter {
158
165
  isString: false,
159
166
  isPassByValue: deps.isPassByValue,
160
167
  isPassByReference,
161
- // Issue #895: Force pointer syntax in C++ mode for callback-compatible functions
162
- // because C callback typedefs expect pointers, not C++ references
163
- forcePointerSyntax: deps.forcePassByReference,
168
+ // Issue #895/#958: Force pointer syntax in C++ mode for callback-compatible
169
+ // and typedef struct params (C types expect pointers, not C++ references)
170
+ forcePointerSyntax:
171
+ deps.forcePassByReference || isTypedefStruct || undefined,
164
172
  // Issue #895: Preserve const from callback typedef signature
165
173
  forceConst: deps.forceConst,
166
174
  };
@@ -62,6 +62,7 @@ function createDefaultASTDeps(overrides?: {
62
62
  isModified: overrides?.isModified ?? false,
63
63
  isPassByValue: overrides?.isPassByValue ?? false,
64
64
  isCallbackCompatible: false,
65
+ isTypedefStructType: () => false,
65
66
  };
66
67
  }
67
68
 
@@ -7,6 +7,8 @@ interface IFunctionContextCallbacks {
7
7
  isStructType: (typeName: string) => boolean;
8
8
  /** Resolve qualified type identifiers to a type name */
9
9
  resolveQualifiedType: (identifiers: string[]) => string;
10
+ /** Issue #958: Check if a type name is a typedef'd struct from C headers */
11
+ isTypedefStructType?: (typeName: string) => boolean;
10
12
  }
11
13
 
12
14
  export default IFunctionContextCallbacks;
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * QualifiedNameGenerator - C-style name generation for C-Next symbols
3
3
  *
4
- * Provides C-style mangled name generation for use in the output layer.
5
- * Delegates to FunctionUtils.getCMangledName() for the actual implementation
4
+ * Provides transpiled C name generation for use in the output layer.
5
+ * Delegates to FunctionUtils.getTranspiledCName() for the actual implementation
6
6
  * to avoid duplication with the types layer.
7
7
  *
8
8
  * Design decisions:
@@ -24,16 +24,16 @@ class QualifiedNameGenerator {
24
24
  // ============================================================================
25
25
 
26
26
  /**
27
- * Generate the C-style mangled name for a function.
27
+ * Generate the transpiled C name for a function.
28
28
  *
29
29
  * For global scope functions, returns the bare name (e.g., "main").
30
30
  * For scoped functions, returns "Scope_name" (e.g., "Test_fillData").
31
31
  * For nested scopes, returns "Outer_Inner_name" (e.g., "Outer_Inner_deepFunc").
32
32
  *
33
- * Delegates to FunctionUtils.getCMangledName() to avoid duplication.
33
+ * Delegates to FunctionUtils.getTranspiledCName() to avoid duplication.
34
34
  */
35
35
  static forFunction(func: IFunctionSymbol): string {
36
- return FunctionUtils.getCMangledName(func);
36
+ return FunctionUtils.getTranspiledCName(func);
37
37
  }
38
38
 
39
39
  /**
@@ -61,7 +61,7 @@ class QualifiedNameGenerator {
61
61
  *
62
62
  * @param scopeName Scope name (e.g., "Test", "Outer.Inner") or undefined for global
63
63
  * @param funcName Bare function name (e.g., "fillData")
64
- * @returns C-mangled name (e.g., "Test_fillData")
64
+ * @returns Transpiled C name (e.g., "Test_fillData")
65
65
  */
66
66
  static forFunctionStrings(
67
67
  scopeName: string | undefined,
@@ -100,7 +100,7 @@ class QualifiedNameGenerator {
100
100
  *
101
101
  * @param scopeName Scope name or undefined for global
102
102
  * @param memberName Member name
103
- * @returns C-mangled name
103
+ * @returns Transpiled C name
104
104
  */
105
105
  static forMember(scopeName: string | undefined, memberName: string): string {
106
106
  if (!scopeName) {