c-next 0.2.2 → 0.2.4

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 (48) hide show
  1. package/README.md +59 -57
  2. package/dist/index.js +641 -191
  3. package/dist/index.js.map +4 -4
  4. package/package.json +1 -1
  5. package/src/cli/Runner.ts +1 -1
  6. package/src/cli/__tests__/Runner.test.ts +8 -8
  7. package/src/cli/serve/ServeCommand.ts +29 -9
  8. package/src/transpiler/Transpiler.ts +105 -200
  9. package/src/transpiler/__tests__/DualCodePaths.test.ts +117 -68
  10. package/src/transpiler/__tests__/Transpiler.coverage.test.ts +87 -51
  11. package/src/transpiler/__tests__/Transpiler.test.ts +150 -48
  12. package/src/transpiler/__tests__/determineProjectRoot.test.ts +2 -2
  13. package/src/transpiler/data/IncludeResolver.ts +11 -3
  14. package/src/transpiler/data/__tests__/IncludeResolver.test.ts +2 -2
  15. package/src/transpiler/logic/analysis/ArrayIndexTypeAnalyzer.ts +346 -0
  16. package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +170 -14
  17. package/src/transpiler/logic/analysis/__tests__/ArrayIndexTypeAnalyzer.test.ts +545 -0
  18. package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +327 -0
  19. package/src/transpiler/logic/analysis/runAnalyzers.ts +9 -2
  20. package/src/transpiler/logic/analysis/types/IArrayIndexTypeError.ts +15 -0
  21. package/src/transpiler/logic/symbols/TransitiveEnumCollector.ts +1 -1
  22. package/src/transpiler/logic/symbols/c/index.ts +50 -1
  23. package/src/transpiler/logic/symbols/c/utils/DeclaratorUtils.ts +99 -2
  24. package/src/transpiler/logic/symbols/c/utils/__tests__/DeclaratorUtils.test.ts +128 -0
  25. package/src/transpiler/output/codegen/CodeGenerator.ts +31 -5
  26. package/src/transpiler/output/codegen/TypeValidator.ts +10 -7
  27. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +49 -36
  28. package/src/transpiler/output/codegen/__tests__/ExpressionWalker.test.ts +9 -3
  29. package/src/transpiler/output/codegen/__tests__/RequireInclude.test.ts +90 -25
  30. package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +3 -1
  31. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +43 -29
  32. package/src/transpiler/output/codegen/generators/IOrchestrator.ts +5 -2
  33. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +23 -14
  34. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +10 -7
  35. package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +12 -3
  36. package/src/transpiler/output/codegen/generators/statements/SwitchGenerator.ts +10 -1
  37. package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +4 -4
  38. package/src/transpiler/output/codegen/helpers/ArrayAccessHelper.ts +6 -3
  39. package/src/transpiler/output/codegen/helpers/FloatBitHelper.ts +36 -22
  40. package/src/transpiler/output/codegen/helpers/FunctionContextManager.ts +9 -1
  41. package/src/transpiler/output/codegen/helpers/__tests__/ArrayAccessHelper.test.ts +8 -6
  42. package/src/transpiler/output/codegen/helpers/__tests__/FloatBitHelper.test.ts +34 -18
  43. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +5 -2
  44. package/src/transpiler/state/CodeGenState.ts +6 -0
  45. package/src/transpiler/types/IPipelineFile.ts +2 -2
  46. package/src/transpiler/types/IPipelineInput.ts +1 -1
  47. package/src/transpiler/types/TTranspileInput.ts +18 -0
  48. package/src/utils/constants/TypeConstants.ts +22 -0
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { hideBin } from "yargs/helpers";
10
10
  // package.json
11
11
  var package_default = {
12
12
  name: "c-next",
13
- version: "0.2.2",
13
+ version: "0.2.4",
14
14
  description: "A safer C for embedded systems development. Transpiles to clean, readable C.",
15
15
  packageManager: "npm@11.9.0",
16
16
  type: "module",
@@ -110764,6 +110764,8 @@ var CodeGenState = class {
110764
110764
  static callbackTypes = /* @__PURE__ */ new Map();
110765
110765
  /** Callback field types: "Struct.field" -> callbackTypeName */
110766
110766
  static callbackFieldTypes = /* @__PURE__ */ new Map();
110767
+ /** Functions that need C-callback-compatible (by-value) struct parameters */
110768
+ static callbackCompatibleFunctions = /* @__PURE__ */ new Set();
110767
110769
  // ===========================================================================
110768
110770
  // PASS-BY-VALUE ANALYSIS (Issue #269)
110769
110771
  // ===========================================================================
@@ -114074,14 +114076,14 @@ var TypeValidator = class _TypeValidator {
114074
114076
  }
114075
114077
  }
114076
114078
  // ========================================================================
114077
- // Do-While Validation (ADR-027)
114079
+ // Condition Boolean Validation (ADR-027, Issue #884)
114078
114080
  // ========================================================================
114079
- static validateDoWhileCondition(ctx) {
114081
+ static validateConditionIsBoolean(ctx, conditionType) {
114080
114082
  const ternaryExpr = ctx.ternaryExpression();
114081
114083
  const orExprs = ternaryExpr.orExpression();
114082
114084
  if (orExprs.length !== 1) {
114083
114085
  throw new Error(
114084
- `Error E0701: do-while condition must be a boolean expression, not a ternary (MISRA C:2012 Rule 14.4)`
114086
+ `Error E0701: ${conditionType} condition must be a boolean expression, not a ternary (MISRA C:2012 Rule 14.4)`
114085
114087
  );
114086
114088
  }
114087
114089
  const orExpr = orExprs[0];
@@ -114092,7 +114094,7 @@ var TypeValidator = class _TypeValidator {
114092
114094
  const andExpr = orExpr.andExpression(0);
114093
114095
  if (!andExpr) {
114094
114096
  throw new Error(
114095
- `Error E0701: do-while condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)`
114097
+ `Error E0701: ${conditionType} condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)`
114096
114098
  );
114097
114099
  }
114098
114100
  if (andExpr.equalityExpression().length > 1) {
@@ -114101,7 +114103,7 @@ var TypeValidator = class _TypeValidator {
114101
114103
  const equalityExpr = andExpr.equalityExpression(0);
114102
114104
  if (!equalityExpr) {
114103
114105
  throw new Error(
114104
- `Error E0701: do-while condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)`
114106
+ `Error E0701: ${conditionType} condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)`
114105
114107
  );
114106
114108
  }
114107
114109
  if (equalityExpr.relationalExpression().length > 1) {
@@ -114110,7 +114112,7 @@ var TypeValidator = class _TypeValidator {
114110
114112
  const relationalExpr = equalityExpr.relationalExpression(0);
114111
114113
  if (!relationalExpr) {
114112
114114
  throw new Error(
114113
- `Error E0701: do-while condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)`
114115
+ `Error E0701: ${conditionType} condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)`
114114
114116
  );
114115
114117
  }
114116
114118
  if (relationalExpr.bitwiseOrExpression().length > 1) {
@@ -114121,7 +114123,7 @@ var TypeValidator = class _TypeValidator {
114121
114123
  return;
114122
114124
  }
114123
114125
  throw new Error(
114124
- `Error E0701: do-while condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)
114126
+ `Error E0701: ${conditionType} condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)
114125
114127
  help: use explicit comparison: ${text} > 0 or ${text} != 0`
114126
114128
  );
114127
114129
  }
@@ -116790,37 +116792,37 @@ var handleBitRangeSubscript = (ctx, exprs, input, state, orchestrator, effects,
116790
116792
  }
116791
116793
  return output;
116792
116794
  };
116795
+ var getFloatTypeName = (baseType) => {
116796
+ return baseType === "f64" ? "double" : "float";
116797
+ };
116793
116798
  var handleFloatBitRange = (ctx, state, orchestrator, effects) => {
116794
116799
  if (!state.inFunctionBody) {
116795
116800
  throw new Error(
116796
116801
  `Float bit indexing reads (${ctx.rootIdentifier}[${ctx.start}, ${ctx.width}]) cannot be used at global scope.`
116797
116802
  );
116798
116803
  }
116799
- effects.push(
116800
- { type: "include", header: "string" },
116801
- { type: "include", header: "float_static_assert" }
116802
- );
116804
+ effects.push({ type: "include", header: "float_static_assert" });
116803
116805
  const isF64 = ctx.baseType === "f64";
116804
- const shadowType = isF64 ? "uint64_t" : "uint32_t";
116806
+ const floatType = getFloatTypeName(ctx.baseType);
116807
+ const intType = isF64 ? "uint64_t" : "uint32_t";
116805
116808
  const shadowName = `__bits_${ctx.rootIdentifier}`;
116806
116809
  const mask = orchestrator.generateBitMask(ctx.width, isF64);
116807
116810
  const needsDeclaration = !orchestrator.hasFloatBitShadow(shadowName);
116808
116811
  if (needsDeclaration) {
116809
116812
  orchestrator.registerFloatBitShadow(shadowName);
116810
- orchestrator.addPendingTempDeclaration(`${shadowType} ${shadowName};`);
116813
+ orchestrator.addPendingTempDeclaration(
116814
+ `union { ${floatType} f; ${intType} u; } ${shadowName};`
116815
+ );
116811
116816
  }
116812
116817
  const shadowIsCurrent = orchestrator.isFloatShadowCurrent(shadowName);
116813
116818
  orchestrator.markFloatShadowCurrent(shadowName);
116814
- if (shadowIsCurrent) {
116815
- if (ctx.start === "0") {
116816
- return `(${shadowName} & ${mask})`;
116817
- }
116818
- return `((${shadowName} >> ${ctx.start}) & ${mask})`;
116819
+ if (!shadowIsCurrent) {
116820
+ orchestrator.addPendingTempDeclaration(`${shadowName}.f = ${ctx.result};`);
116819
116821
  }
116820
116822
  if (ctx.start === "0") {
116821
- return `(memcpy(&${shadowName}, &${ctx.result}, sizeof(${ctx.result})), (${shadowName} & ${mask}))`;
116823
+ return `(${shadowName}.u & ${mask})`;
116822
116824
  }
116823
- return `(memcpy(&${shadowName}, &${ctx.result}, sizeof(${ctx.result})), ((${shadowName} >> ${ctx.start}) & ${mask}))`;
116825
+ return `((${shadowName}.u >> ${ctx.start}) & ${mask})`;
116824
116826
  };
116825
116827
  var applyAccessEffects = (sourceEffects, targetEffects) => {
116826
116828
  for (const effect of sourceEffects) {
@@ -116940,6 +116942,7 @@ var generateIf = (node, _input, _state, orchestrator) => {
116940
116942
  }
116941
116943
  const cacheDecls = orchestrator.setupLengthCache(lengthCounts);
116942
116944
  orchestrator.validateConditionNoFunctionCall(node.expression(), "if");
116945
+ orchestrator.validateConditionIsBoolean(node.expression(), "if");
116943
116946
  const condition = orchestrator.generateExpression(node.expression());
116944
116947
  const conditionTemps = orchestrator.flushPendingTempDeclarations();
116945
116948
  const thenBranch = orchestrator.generateStatement(thenStmt);
@@ -116960,6 +116963,7 @@ var generateIf = (node, _input, _state, orchestrator) => {
116960
116963
  var generateWhile = (node, _input, _state, orchestrator) => {
116961
116964
  const effects = [];
116962
116965
  orchestrator.validateConditionNoFunctionCall(node.expression(), "while");
116966
+ orchestrator.validateConditionIsBoolean(node.expression(), "while");
116963
116967
  const condition = orchestrator.generateExpression(node.expression());
116964
116968
  const conditionTemps = orchestrator.flushPendingTempDeclarations();
116965
116969
  const body = orchestrator.generateStatement(node.statement());
@@ -116971,8 +116975,8 @@ var generateWhile = (node, _input, _state, orchestrator) => {
116971
116975
  };
116972
116976
  var generateDoWhile = (node, _input, _state, orchestrator) => {
116973
116977
  const effects = [];
116974
- orchestrator.validateDoWhileCondition(node.expression());
116975
116978
  orchestrator.validateConditionNoFunctionCall(node.expression(), "do-while");
116979
+ orchestrator.validateConditionIsBoolean(node.expression(), "do-while");
116976
116980
  const body = orchestrator.generateBlock(node.block());
116977
116981
  const condition = orchestrator.generateExpression(node.expression());
116978
116982
  const conditionTemps = orchestrator.flushPendingTempDeclarations();
@@ -117037,6 +117041,7 @@ var generateFor = (node, input, state, orchestrator) => {
117037
117041
  let condition = "";
117038
117042
  if (node.expression()) {
117039
117043
  orchestrator.validateConditionNoFunctionCall(node.expression(), "for");
117044
+ orchestrator.validateConditionIsBoolean(node.expression(), "for");
117040
117045
  condition = orchestrator.generateExpression(node.expression());
117041
117046
  }
117042
117047
  const conditionTemps = orchestrator.flushPendingTempDeclarations();
@@ -117370,6 +117375,12 @@ var generateSwitch = (node, input, state, orchestrator) => {
117370
117375
  );
117371
117376
  lines.push(defaultResult.code);
117372
117377
  effects.push(...defaultResult.effects);
117378
+ } else {
117379
+ lines.push(
117380
+ orchestrator.indent("default: {"),
117381
+ orchestrator.indent(orchestrator.indent("break;")),
117382
+ orchestrator.indent("}")
117383
+ );
117373
117384
  }
117374
117385
  lines.push("}");
117375
117386
  return { code: lines.join("\n"), effects };
@@ -122737,11 +122748,16 @@ var MemberChainAnalyzer = class _MemberChainAnalyzer {
122737
122748
  var MemberChainAnalyzer_default = MemberChainAnalyzer;
122738
122749
 
122739
122750
  // src/transpiler/output/codegen/helpers/FloatBitHelper.ts
122751
+ var getFloatTypeName2 = (baseType) => {
122752
+ return baseType === "f64" ? "double" : "float";
122753
+ };
122740
122754
  var FloatBitHelper = class {
122741
122755
  /**
122742
- * Generate float bit write using shadow variable + memcpy.
122756
+ * Generate float bit write using union-based type punning.
122743
122757
  * Returns null if typeInfo is not a float type.
122744
122758
  *
122759
+ * Uses union { float f; uint32_t u; } for MISRA 21.15 compliance instead of memcpy.
122760
+ *
122745
122761
  * @param name - Variable name being written
122746
122762
  * @param typeInfo - Type information for the variable
122747
122763
  * @param bitIndex - Bit index expression (start position)
@@ -122755,10 +122771,10 @@ var FloatBitHelper = class {
122755
122771
  if (!isFloatType) {
122756
122772
  return null;
122757
122773
  }
122758
- callbacks.requireInclude("string");
122759
122774
  callbacks.requireInclude("float_static_assert");
122760
122775
  const isF64 = typeInfo.baseType === "f64";
122761
- const shadowType = isF64 ? "uint64_t" : "uint32_t";
122776
+ const floatType = getFloatTypeName2(typeInfo.baseType);
122777
+ const intType = isF64 ? "uint64_t" : "uint32_t";
122762
122778
  const shadowName = `__bits_${name}`;
122763
122779
  const maskSuffix = isF64 ? "ULL" : "U";
122764
122780
  const needsDeclaration = !CodeGenState.floatBitShadows.has(shadowName);
@@ -122766,14 +122782,18 @@ var FloatBitHelper = class {
122766
122782
  CodeGenState.floatBitShadows.add(shadowName);
122767
122783
  }
122768
122784
  const shadowIsCurrent = CodeGenState.floatShadowCurrent.has(shadowName);
122769
- const decl = needsDeclaration ? `${shadowType} ${shadowName}; ` : "";
122770
- const readMemcpy = shadowIsCurrent ? "" : `memcpy(&${shadowName}, &${name}, sizeof(${name})); `;
122785
+ const decl = needsDeclaration ? `union { ${floatType} f; ${intType} u; } ${shadowName};
122786
+ ` : "";
122787
+ const readUnion = shadowIsCurrent ? "" : `${shadowName}.f = ${name};
122788
+ `;
122771
122789
  CodeGenState.floatShadowCurrent.add(shadowName);
122772
122790
  if (width === null) {
122773
- return `${decl}${readMemcpy}${shadowName} = (${shadowName} & ~(1${maskSuffix} << ${bitIndex})) | ((${shadowType})${callbacks.foldBooleanToInt(value)} << ${bitIndex}); memcpy(&${name}, &${shadowName}, sizeof(${name}));`;
122791
+ return `${decl}${readUnion}${shadowName}.u = (${shadowName}.u & ~(1${maskSuffix} << ${bitIndex})) | ((${intType})${callbacks.foldBooleanToInt(value)} << ${bitIndex});
122792
+ ${name} = ${shadowName}.f;`;
122774
122793
  } else {
122775
122794
  const mask = callbacks.generateBitMask(width, isF64);
122776
- return `${decl}${readMemcpy}${shadowName} = (${shadowName} & ~(${mask} << ${bitIndex})) | (((${shadowType})${value} & ${mask}) << ${bitIndex}); memcpy(&${name}, &${shadowName}, sizeof(${name}));`;
122795
+ return `${decl}${readUnion}${shadowName}.u = (${shadowName}.u & ~(${mask} << ${bitIndex})) | (((${intType})${value} & ${mask}) << ${bitIndex});
122796
+ ${name} = ${shadowName}.f;`;
122777
122797
  }
122778
122798
  }
122779
122799
  };
@@ -125511,11 +125531,14 @@ var FunctionContextManager = class _FunctionContextManager {
125511
125531
  typeCtx,
125512
125532
  callbacks
125513
125533
  );
125534
+ const isCallbackCompat = CodeGenState.currentFunctionName !== null && CodeGenState.callbackCompatibleFunctions.has(
125535
+ CodeGenState.currentFunctionName
125536
+ );
125514
125537
  const paramInfo = {
125515
125538
  name,
125516
125539
  baseType: typeInfo.typeName,
125517
125540
  isArray,
125518
- isStruct: typeInfo.isStruct,
125541
+ isStruct: isCallbackCompat ? false : typeInfo.isStruct,
125519
125542
  isConst,
125520
125543
  isCallback: typeInfo.isCallback,
125521
125544
  isString: typeInfo.isString
@@ -127546,11 +127569,11 @@ var CodeGenerator = class _CodeGenerator {
127546
127569
  TypeValidator_default.validateSwitchStatement(ctx, switchExpr);
127547
127570
  }
127548
127571
  /**
127549
- * Validate do-while condition.
127572
+ * Validate condition is a boolean expression (ADR-027, Issue #884).
127550
127573
  * Part of IOrchestrator interface (ADR-053 A3).
127551
127574
  */
127552
- validateDoWhileCondition(ctx) {
127553
- TypeValidator_default.validateDoWhileCondition(ctx);
127575
+ validateConditionIsBoolean(ctx, conditionType) {
127576
+ TypeValidator_default.validateConditionIsBoolean(ctx, conditionType);
127554
127577
  }
127555
127578
  /**
127556
127579
  * Issue #254: Validate no function calls in condition (E0702).
@@ -127936,8 +127959,8 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
127936
127959
  }
127937
127960
  /**
127938
127961
  * Issue #561: Analyze modifications in a parse tree without full code generation.
127939
- * Used by Pipeline.transpileSource() to collect modification info from includes
127940
- * for cross-file const inference (unified with Pipeline.run() behavior).
127962
+ * Used by the transpile() pipeline to collect modification info from includes
127963
+ * for cross-file const inference.
127941
127964
  *
127942
127965
  * Issue #565: Now accepts optional cross-file data for transitive propagation.
127943
127966
  * When a file calls a function from an included file that modifies its param,
@@ -129318,6 +129341,9 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
129318
129341
  return "{}";
129319
129342
  }
129320
129343
  const fieldInits = fields.map((f) => `.${f.fieldName} = ${f.value}`);
129344
+ if (CodeGenState.cppMode && (typeName.startsWith("struct {") || typeName.startsWith("union {"))) {
129345
+ return `{ ${fieldInits.join(", ")} }`;
129346
+ }
129321
129347
  return `(${castType}){ ${fieldInits.join(", ")} }`;
129322
129348
  }
129323
129349
  /**
@@ -129501,6 +129527,11 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
129501
129527
  )) {
129502
129528
  return true;
129503
129529
  }
129530
+ if (CodeGenState.currentFunctionName && CodeGenState.callbackCompatibleFunctions.has(
129531
+ CodeGenState.currentFunctionName
129532
+ ) && this.isKnownStruct(typeName)) {
129533
+ return true;
129534
+ }
129504
129535
  return false;
129505
129536
  }
129506
129537
  // ========================================================================
@@ -130713,8 +130744,11 @@ var HeaderGeneratorUtils = class _HeaderGeneratorUtils {
130713
130744
  lines.push(include);
130714
130745
  }
130715
130746
  }
130747
+ const userIncludeSet = new Set(options.userIncludes ?? []);
130716
130748
  for (const directive of headersToInclude) {
130717
- lines.push(directive);
130749
+ if (!userIncludeSet.has(directive)) {
130750
+ lines.push(directive);
130751
+ }
130718
130752
  }
130719
130753
  const hasIncludes = options.includeSystemHeaders !== false || options.userIncludes && options.userIncludes.length > 0 || headersToInclude.size > 0;
130720
130754
  if (hasIncludes) {
@@ -132808,7 +132842,7 @@ var DeclaratorUtils = class _DeclaratorUtils {
132808
132842
  if (identifier) {
132809
132843
  parts.push(identifier.getText());
132810
132844
  } else {
132811
- parts.push(typeSpec.getText());
132845
+ parts.push(_DeclaratorUtils.reconstructAnonymousStruct(structSpec));
132812
132846
  }
132813
132847
  } else {
132814
132848
  parts.push(typeSpec.getText());
@@ -132822,6 +132856,74 @@ var DeclaratorUtils = class _DeclaratorUtils {
132822
132856
  }
132823
132857
  return parts.join(" ") || "int";
132824
132858
  }
132859
+ /**
132860
+ * Reconstruct an anonymous struct/union type with proper spacing.
132861
+ * For `struct { unsigned int flag_a: 1; }`, returns the properly formatted string
132862
+ * instead of the concatenated tokens from getText().
132863
+ */
132864
+ static reconstructAnonymousStruct(structSpec) {
132865
+ const structOrUnion = structSpec.structOrUnion();
132866
+ const keyword = structOrUnion.Struct() ? "struct" : "union";
132867
+ const declList = structSpec.structDeclarationList();
132868
+ if (!declList) {
132869
+ return `${keyword} { }`;
132870
+ }
132871
+ const fields = _DeclaratorUtils.reconstructStructFields(declList);
132872
+ return `${keyword} { ${fields} }`;
132873
+ }
132874
+ /**
132875
+ * Reconstruct struct fields with proper spacing.
132876
+ */
132877
+ static reconstructStructFields(declList) {
132878
+ const fieldStrings = [];
132879
+ for (const decl of declList.structDeclaration()) {
132880
+ const fieldStr = _DeclaratorUtils.reconstructStructField(decl);
132881
+ if (fieldStr) {
132882
+ fieldStrings.push(fieldStr);
132883
+ }
132884
+ }
132885
+ return fieldStrings.join(" ");
132886
+ }
132887
+ /**
132888
+ * Reconstruct a single struct field declaration.
132889
+ */
132890
+ static reconstructStructField(decl) {
132891
+ const specQualList = decl.specifierQualifierList();
132892
+ if (!specQualList) return null;
132893
+ const baseType = _DeclaratorUtils.extractTypeFromSpecQualList(specQualList);
132894
+ const declaratorList = decl.structDeclaratorList();
132895
+ if (!declaratorList) {
132896
+ return `${baseType};`;
132897
+ }
132898
+ const declarators = [];
132899
+ for (const structDecl of declaratorList.structDeclarator()) {
132900
+ const declStr = _DeclaratorUtils.reconstructStructDeclarator(structDecl);
132901
+ if (declStr) {
132902
+ declarators.push(declStr);
132903
+ }
132904
+ }
132905
+ if (declarators.length === 0) {
132906
+ return `${baseType};`;
132907
+ }
132908
+ return `${baseType} ${declarators.join(", ")};`;
132909
+ }
132910
+ /**
132911
+ * Reconstruct a struct declarator (field name with optional bitfield width).
132912
+ */
132913
+ static reconstructStructDeclarator(structDecl) {
132914
+ const declarator = structDecl.declarator();
132915
+ const hasColon = structDecl.Colon() !== null;
132916
+ const constExpr = structDecl.constantExpression();
132917
+ let name = "";
132918
+ if (declarator) {
132919
+ name = _DeclaratorUtils.extractDeclaratorName(declarator) || "";
132920
+ }
132921
+ if (hasColon && constExpr) {
132922
+ const width = constExpr.getText();
132923
+ return `${name}: ${width}`;
132924
+ }
132925
+ return name || null;
132926
+ }
132825
132927
  /**
132826
132928
  * Extract typedef name from declaration specifiers.
132827
132929
  * For "typedef struct { ... } AppConfig;", this returns "AppConfig".
@@ -133259,8 +133361,9 @@ var CResolver = class _CResolver {
133259
133361
  if (!name) continue;
133260
133362
  const isFunction = DeclaratorUtils_default.declaratorIsFunction(declarator);
133261
133363
  if (ctx.isTypedef) {
133364
+ const typedefType = _CResolver.isFunctionPointerDeclarator(declarator) ? `${baseType} (*)(${_CResolver.extractParamText(declarator)})` : baseType;
133262
133365
  ctx.symbols.push(
133263
- TypedefCollector_default.collect(name, baseType, ctx.sourceFile, ctx.line)
133366
+ TypedefCollector_default.collect(name, typedefType, ctx.sourceFile, ctx.line)
133264
133367
  );
133265
133368
  } else if (isFunction) {
133266
133369
  ctx.symbols.push(
@@ -133363,6 +133466,36 @@ var CResolver = class _CResolver {
133363
133466
  }
133364
133467
  return typeParts.join(" ") || "int";
133365
133468
  }
133469
+ /**
133470
+ * Check if a declarator represents a function pointer.
133471
+ * For `(*PointCallback)(Point p)`, the C grammar parses as:
133472
+ * declarator -> directDeclarator
133473
+ * directDeclarator -> directDeclarator '(' parameterTypeList ')'
133474
+ * inner directDeclarator -> '(' declarator ')'
133475
+ * inner declarator -> pointer directDeclarator -> * PointCallback
133476
+ */
133477
+ static isFunctionPointerDeclarator(declarator) {
133478
+ const directDecl = declarator.directDeclarator?.();
133479
+ if (!directDecl) return false;
133480
+ const hasParams = directDecl.parameterTypeList?.() !== null || Boolean(directDecl.LeftParen?.());
133481
+ if (!hasParams) return false;
133482
+ const innerDirectDecl = directDecl.directDeclarator?.();
133483
+ if (!innerDirectDecl) return false;
133484
+ const nestedDecl = innerDirectDecl.declarator?.();
133485
+ if (!nestedDecl) return false;
133486
+ return Boolean(nestedDecl.pointer?.());
133487
+ }
133488
+ /**
133489
+ * Extract parameter text from a function pointer declarator.
133490
+ * Returns the text of the parameters from a function pointer like "(*Callback)(Point p)".
133491
+ */
133492
+ static extractParamText(declarator) {
133493
+ const directDecl = declarator.directDeclarator?.();
133494
+ if (!directDecl) return "";
133495
+ const paramTypeList = directDecl.parameterTypeList?.();
133496
+ if (!paramTypeList) return "";
133497
+ return paramTypeList.getText();
133498
+ }
133366
133499
  };
133367
133500
  var c_default = CResolver;
133368
133501
 
@@ -134992,6 +135125,9 @@ var IncludeResolver = class _IncludeResolver {
134992
135125
  }
134993
135126
  if (file.type === EFileType_default.CNext) {
134994
135127
  result.cnextIncludes.push(file);
135128
+ const headerPath = includeInfo.path.replace(/\.cnx$|\.cnext$/, ".h");
135129
+ const directive = includeInfo.isLocal ? `#include "${headerPath}"` : `#include <${headerPath}>`;
135130
+ result.headerIncludeDirectives.set(absolutePath, directive);
134995
135131
  }
134996
135132
  }
134997
135133
  /**
@@ -135129,8 +135265,8 @@ var IncludeResolver = class _IncludeResolver {
135129
135265
  /**
135130
135266
  * Build search paths from a source file location
135131
135267
  *
135132
- * Consolidates the search path building logic used by both `run()` and
135133
- * `transpileSource()` code paths.
135268
+ * Consolidates the search path building logic used by the unified
135269
+ * transpile() entry point.
135134
135270
  *
135135
135271
  * Search order (highest to lowest priority):
135136
135272
  * 1. Source file's directory (for relative includes)
@@ -136465,16 +136601,20 @@ var FunctionCallListener = class extends CNextListener {
136465
136601
  // ========================================================================
136466
136602
  // ISR/Callback Variable Tracking (ADR-040)
136467
136603
  // ========================================================================
136604
+ /**
136605
+ * Check if a type name represents a callable type (ISR, callback, or C function pointer typedef).
136606
+ */
136607
+ isCallableType(typeName) {
136608
+ return typeName === "ISR" || this.analyzer.isCallbackType(typeName) || this.analyzer.isCFunctionPointerTypedef(typeName);
136609
+ }
136468
136610
  /**
136469
136611
  * Track ISR-typed variables from variable declarations
136470
136612
  * e.g., `ISR handler <- myFunction;`
136471
136613
  */
136472
136614
  enterVariableDeclaration = (ctx) => {
136473
- const typeCtx = ctx.type();
136474
- const typeName = typeCtx.getText();
136475
- if (typeName === "ISR" || this.analyzer.isCallbackType(typeName)) {
136476
- const varName = ctx.IDENTIFIER().getText();
136477
- this.analyzer.defineCallableVariable(varName);
136615
+ const typeName = ctx.type().getText();
136616
+ if (this.isCallableType(typeName)) {
136617
+ this.analyzer.defineCallableVariable(ctx.IDENTIFIER().getText());
136478
136618
  }
136479
136619
  };
136480
136620
  /**
@@ -136482,11 +136622,9 @@ var FunctionCallListener = class extends CNextListener {
136482
136622
  * e.g., `void execute(ISR handler) { handler(); }`
136483
136623
  */
136484
136624
  enterParameter = (ctx) => {
136485
- const typeCtx = ctx.type();
136486
- const typeName = typeCtx.getText();
136487
- if (typeName === "ISR" || this.analyzer.isCallbackType(typeName)) {
136488
- const paramName = ctx.IDENTIFIER().getText();
136489
- this.analyzer.defineCallableVariable(paramName);
136625
+ const typeName = ctx.type().getText();
136626
+ if (this.isCallableType(typeName)) {
136627
+ this.analyzer.defineCallableVariable(ctx.IDENTIFIER().getText());
136490
136628
  }
136491
136629
  };
136492
136630
  // ========================================================================
@@ -136625,6 +136763,7 @@ var FunctionCallAnalyzer = class {
136625
136763
  this.collectIncludes(tree);
136626
136764
  this.collectCallbackTypes(tree);
136627
136765
  this.collectAllLocalFunctions(tree);
136766
+ this.collectCallbackCompatibleFunctions(tree);
136628
136767
  const listener = new FunctionCallListener(this);
136629
136768
  ParseTreeWalker5.DEFAULT.walk(listener, tree);
136630
136769
  return this.errors;
@@ -136708,6 +136847,120 @@ var FunctionCallAnalyzer = class {
136708
136847
  isCallbackType(name) {
136709
136848
  return this.callbackTypes.has(name);
136710
136849
  }
136850
+ /**
136851
+ * Check if a type name is a C function pointer typedef.
136852
+ * Looks up the type in the symbol table and checks if it's a typedef
136853
+ * whose underlying type contains "(*)" indicating a function pointer.
136854
+ */
136855
+ isCFunctionPointerTypedef(typeName) {
136856
+ if (!this.symbolTable) return false;
136857
+ const sym = this.symbolTable.getCSymbol(typeName);
136858
+ if (sym?.kind !== "type") return false;
136859
+ return "type" in sym && typeof sym.type === "string" && sym.type.includes("(*)");
136860
+ }
136861
+ /**
136862
+ * Detect functions assigned to C function pointer typedefs.
136863
+ * When `PointCallback cb <- my_handler;` is found and PointCallback
136864
+ * is a C function pointer typedef, mark my_handler as callback-compatible.
136865
+ */
136866
+ collectCallbackCompatibleFunctions(tree) {
136867
+ for (const decl of tree.declaration()) {
136868
+ const funcDecl = decl.functionDeclaration();
136869
+ if (!funcDecl) continue;
136870
+ const block = funcDecl.block();
136871
+ if (!block) continue;
136872
+ this.scanBlockForCallbackAssignments(block);
136873
+ }
136874
+ }
136875
+ /**
136876
+ * Recursively scan all statements in a block for callback typedef assignments.
136877
+ */
136878
+ scanBlockForCallbackAssignments(block) {
136879
+ for (const stmt of block.statement()) {
136880
+ this.scanStatementForCallbackAssignments(stmt);
136881
+ }
136882
+ }
136883
+ /**
136884
+ * Scan a single statement for callback typedef assignments,
136885
+ * recursing into nested blocks (if/while/for/do-while/switch/critical).
136886
+ */
136887
+ scanStatementForCallbackAssignments(stmt) {
136888
+ const varDecl = stmt.variableDeclaration();
136889
+ if (varDecl) {
136890
+ this.checkVarDeclForCallbackAssignment(varDecl);
136891
+ return;
136892
+ }
136893
+ const ifStmt = stmt.ifStatement();
136894
+ if (ifStmt) {
136895
+ for (const child of ifStmt.statement()) {
136896
+ this.scanStatementForCallbackAssignments(child);
136897
+ }
136898
+ return;
136899
+ }
136900
+ const whileStmt = stmt.whileStatement();
136901
+ if (whileStmt) {
136902
+ this.scanStatementForCallbackAssignments(whileStmt.statement());
136903
+ return;
136904
+ }
136905
+ const forStmt = stmt.forStatement();
136906
+ if (forStmt) {
136907
+ this.scanStatementForCallbackAssignments(forStmt.statement());
136908
+ return;
136909
+ }
136910
+ const doWhileStmt = stmt.doWhileStatement();
136911
+ if (doWhileStmt) {
136912
+ this.scanBlockForCallbackAssignments(doWhileStmt.block());
136913
+ return;
136914
+ }
136915
+ const switchStmt = stmt.switchStatement();
136916
+ if (switchStmt) {
136917
+ for (const caseCtx of switchStmt.switchCase()) {
136918
+ this.scanBlockForCallbackAssignments(caseCtx.block());
136919
+ }
136920
+ const defaultCtx = switchStmt.defaultCase();
136921
+ if (defaultCtx) {
136922
+ this.scanBlockForCallbackAssignments(defaultCtx.block());
136923
+ }
136924
+ return;
136925
+ }
136926
+ const criticalStmt = stmt.criticalStatement();
136927
+ if (criticalStmt) {
136928
+ this.scanBlockForCallbackAssignments(criticalStmt.block());
136929
+ return;
136930
+ }
136931
+ const nestedBlock = stmt.block();
136932
+ if (nestedBlock) {
136933
+ this.scanBlockForCallbackAssignments(nestedBlock);
136934
+ }
136935
+ }
136936
+ /**
136937
+ * Check if a variable declaration assigns a function to a C callback typedef.
136938
+ */
136939
+ checkVarDeclForCallbackAssignment(varDecl) {
136940
+ const typeName = varDecl.type().getText();
136941
+ if (!this.isCFunctionPointerTypedef(typeName)) return;
136942
+ const expr = varDecl.expression();
136943
+ if (!expr) return;
136944
+ const funcRef = this.extractFunctionReference(expr);
136945
+ if (!funcRef) return;
136946
+ const lookupName = funcRef.includes(".") ? funcRef.replace(".", "_") : funcRef;
136947
+ if (this.allLocalFunctions.has(lookupName)) {
136948
+ CodeGenState.callbackCompatibleFunctions.add(lookupName);
136949
+ }
136950
+ }
136951
+ /**
136952
+ * Extract a function reference from an expression context.
136953
+ * Matches bare identifiers (e.g., "my_handler") and qualified scope
136954
+ * names (e.g., "MyScope.handler").
136955
+ * Returns null if the expression is not a function reference.
136956
+ */
136957
+ extractFunctionReference(expr) {
136958
+ const text = expr.getText();
136959
+ if (/^\w+(\.\w+)?$/.test(text)) {
136960
+ return text;
136961
+ }
136962
+ return null;
136963
+ }
136711
136964
  /**
136712
136965
  * ADR-040: Register a variable that holds a callable (ISR or callback)
136713
136966
  */
@@ -136961,6 +137214,26 @@ var TypeConstants = class {
136961
137214
  "float",
136962
137215
  "double"
136963
137216
  ];
137217
+ /**
137218
+ * Unsigned integer types valid as array/bit subscript indexes.
137219
+ *
137220
+ * Used by:
137221
+ * - ArrayIndexTypeAnalyzer: validating subscript index types
137222
+ */
137223
+ static UNSIGNED_INDEX_TYPES = [
137224
+ "u8",
137225
+ "u16",
137226
+ "u32",
137227
+ "u64",
137228
+ "bool"
137229
+ ];
137230
+ /**
137231
+ * Signed integer types (rejected as subscript indexes).
137232
+ *
137233
+ * Used by:
137234
+ * - ArrayIndexTypeAnalyzer: detecting signed integer subscript indexes
137235
+ */
137236
+ static SIGNED_TYPES = ["i8", "i16", "i32", "i64"];
136964
137237
  };
136965
137238
  var TypeConstants_default = TypeConstants;
136966
137239
 
@@ -137078,6 +137351,212 @@ var FloatModuloAnalyzer = class {
137078
137351
  };
137079
137352
  var FloatModuloAnalyzer_default = FloatModuloAnalyzer;
137080
137353
 
137354
+ // src/transpiler/logic/analysis/ArrayIndexTypeAnalyzer.ts
137355
+ import { ParseTreeWalker as ParseTreeWalker8 } from "antlr4ng";
137356
+ var VariableTypeCollector = class extends CNextListener {
137357
+ varTypes = /* @__PURE__ */ new Map();
137358
+ getVarTypes() {
137359
+ return this.varTypes;
137360
+ }
137361
+ trackType(typeCtx, identifier) {
137362
+ if (!typeCtx || !identifier) return;
137363
+ this.varTypes.set(identifier.getText(), typeCtx.getText());
137364
+ }
137365
+ enterVariableDeclaration = (ctx) => {
137366
+ this.trackType(ctx.type(), ctx.IDENTIFIER());
137367
+ };
137368
+ enterParameter = (ctx) => {
137369
+ this.trackType(ctx.type(), ctx.IDENTIFIER());
137370
+ };
137371
+ enterForVarDecl = (ctx) => {
137372
+ this.trackType(ctx.type(), ctx.IDENTIFIER());
137373
+ };
137374
+ };
137375
+ var IndexTypeListener = class extends CNextListener {
137376
+ analyzer;
137377
+ // eslint-disable-next-line @typescript-eslint/lines-between-class-members
137378
+ varTypes;
137379
+ constructor(analyzer, varTypes) {
137380
+ super();
137381
+ this.analyzer = analyzer;
137382
+ this.varTypes = varTypes;
137383
+ }
137384
+ /**
137385
+ * Check postfix operations in expressions (RHS: arr[idx], flags[bit])
137386
+ */
137387
+ enterPostfixOp = (ctx) => {
137388
+ if (!ctx.LBRACKET()) return;
137389
+ const expressions = ctx.expression();
137390
+ for (const expr of expressions) {
137391
+ this.validateIndexExpression(expr);
137392
+ }
137393
+ };
137394
+ /**
137395
+ * Check postfix target operations in assignments (LHS: arr[idx] <- val)
137396
+ */
137397
+ enterPostfixTargetOp = (ctx) => {
137398
+ if (!ctx.LBRACKET()) return;
137399
+ const expressions = ctx.expression();
137400
+ for (const expr of expressions) {
137401
+ this.validateIndexExpression(expr);
137402
+ }
137403
+ };
137404
+ /**
137405
+ * Validate that a subscript index expression uses an unsigned integer type.
137406
+ * Collects all leaf operands from the expression and checks each one.
137407
+ */
137408
+ validateIndexExpression(ctx) {
137409
+ const operands = this.collectOperands(ctx);
137410
+ for (const operand of operands) {
137411
+ const resolvedType = this.resolveOperandType(operand);
137412
+ if (!resolvedType) continue;
137413
+ if (TypeConstants_default.SIGNED_TYPES.includes(resolvedType)) {
137414
+ const { line: line2, column: column2 } = ParserUtils_default.getPosition(ctx);
137415
+ this.analyzer.addError(line2, column2, "E0850", resolvedType);
137416
+ return;
137417
+ }
137418
+ if (resolvedType === "float literal" || TypeConstants_default.FLOAT_TYPES.includes(resolvedType)) {
137419
+ const { line: line2, column: column2 } = ParserUtils_default.getPosition(ctx);
137420
+ this.analyzer.addError(line2, column2, "E0851", resolvedType);
137421
+ return;
137422
+ }
137423
+ if (TypeConstants_default.UNSIGNED_INDEX_TYPES.includes(resolvedType)) {
137424
+ continue;
137425
+ }
137426
+ const { line, column } = ParserUtils_default.getPosition(ctx);
137427
+ this.analyzer.addError(line, column, "E0852", resolvedType);
137428
+ return;
137429
+ }
137430
+ }
137431
+ /**
137432
+ * Collect all leaf unary expression operands from an expression tree.
137433
+ * Handles binary operators at any level by flatMapping through the grammar hierarchy.
137434
+ */
137435
+ collectOperands(ctx) {
137436
+ const ternary = ctx.ternaryExpression();
137437
+ if (!ternary) return [];
137438
+ const orExpressions = ternary.orExpression();
137439
+ if (orExpressions.length === 0) return [];
137440
+ const valueExpressions = orExpressions.length === 3 ? orExpressions.slice(1) : orExpressions;
137441
+ return valueExpressions.flatMap((o) => o.andExpression()).flatMap((a) => a.equalityExpression()).flatMap((e) => e.relationalExpression()).flatMap((r) => r.bitwiseOrExpression()).flatMap((bo) => bo.bitwiseXorExpression()).flatMap((bx) => bx.bitwiseAndExpression()).flatMap((ba) => ba.shiftExpression()).flatMap((s) => s.additiveExpression()).flatMap((a) => a.multiplicativeExpression()).flatMap((m) => m.unaryExpression());
137442
+ }
137443
+ /**
137444
+ * Resolve the type of a unary expression operand.
137445
+ * Uses local varTypes first, then falls back to CodeGenState for
137446
+ * struct fields, function return types, and enum detection.
137447
+ *
137448
+ * Returns null if the type cannot be resolved (pass-through).
137449
+ */
137450
+ resolveOperandType(operand) {
137451
+ const postfixExpr = operand.postfixExpression();
137452
+ if (!postfixExpr) return null;
137453
+ const primaryExpr = postfixExpr.primaryExpression();
137454
+ if (!primaryExpr) return null;
137455
+ let currentType = this.resolveBaseType(primaryExpr);
137456
+ const postfixOps = postfixExpr.postfixOp();
137457
+ if (!currentType && postfixOps.length > 0) {
137458
+ const identifier = primaryExpr.IDENTIFIER();
137459
+ if (identifier) {
137460
+ currentType = identifier.getText();
137461
+ }
137462
+ }
137463
+ for (const op of postfixOps) {
137464
+ if (!currentType) return null;
137465
+ currentType = this.resolvePostfixOpType(currentType, op);
137466
+ }
137467
+ return currentType;
137468
+ }
137469
+ /**
137470
+ * Resolve the base type of a primary expression.
137471
+ */
137472
+ resolveBaseType(primaryExpr) {
137473
+ const literal = primaryExpr.literal();
137474
+ if (literal) {
137475
+ if (LiteralUtils_default.isFloat(literal)) return "float literal";
137476
+ return null;
137477
+ }
137478
+ const parenExpr = primaryExpr.expression();
137479
+ if (parenExpr) {
137480
+ const innerOperands = this.collectOperands(parenExpr);
137481
+ for (const innerOp of innerOperands) {
137482
+ const innerType = this.resolveOperandType(innerOp);
137483
+ if (innerType) return innerType;
137484
+ }
137485
+ return null;
137486
+ }
137487
+ const identifier = primaryExpr.IDENTIFIER();
137488
+ if (!identifier) return null;
137489
+ const varName = identifier.getText();
137490
+ const localType = this.varTypes.get(varName);
137491
+ if (localType) return localType;
137492
+ const typeInfo = CodeGenState.getVariableTypeInfo(varName);
137493
+ if (typeInfo) return typeInfo.baseType;
137494
+ return null;
137495
+ }
137496
+ /**
137497
+ * Resolve the resulting type after applying a postfix operator.
137498
+ */
137499
+ resolvePostfixOpType(currentType, op) {
137500
+ if (op.DOT()) {
137501
+ const fieldId = op.IDENTIFIER();
137502
+ if (!fieldId) return null;
137503
+ const fieldName = fieldId.getText();
137504
+ if (CodeGenState.isKnownEnum(currentType)) return null;
137505
+ const fieldType = CodeGenState.getStructFieldType(currentType, fieldName);
137506
+ return fieldType ?? null;
137507
+ }
137508
+ if (op.LBRACKET()) {
137509
+ if (TypeConstants_default.UNSIGNED_INDEX_TYPES.includes(currentType)) {
137510
+ return "bool";
137511
+ }
137512
+ if (TypeConstants_default.SIGNED_TYPES.includes(currentType)) {
137513
+ return "bool";
137514
+ }
137515
+ return currentType;
137516
+ }
137517
+ if (op.LPAREN()) {
137518
+ const returnType = CodeGenState.getFunctionReturnType(currentType);
137519
+ return returnType ?? null;
137520
+ }
137521
+ return null;
137522
+ }
137523
+ };
137524
+ var ArrayIndexTypeAnalyzer = class {
137525
+ errors = [];
137526
+ /**
137527
+ * Analyze the parse tree for invalid subscript index types
137528
+ */
137529
+ analyze(tree) {
137530
+ this.errors = [];
137531
+ const collector = new VariableTypeCollector();
137532
+ ParseTreeWalker8.DEFAULT.walk(collector, tree);
137533
+ const varTypes = collector.getVarTypes();
137534
+ const listener = new IndexTypeListener(this, varTypes);
137535
+ ParseTreeWalker8.DEFAULT.walk(listener, tree);
137536
+ return this.errors;
137537
+ }
137538
+ /**
137539
+ * Add an index type error
137540
+ */
137541
+ addError(line, column, code, actualType) {
137542
+ this.errors.push({
137543
+ code,
137544
+ line,
137545
+ column,
137546
+ actualType,
137547
+ message: `Subscript index must be an unsigned integer type; got '${actualType}'`,
137548
+ helpText: "Use an unsigned integer type (u8, u16, u32, u64) for array and bit subscript indexes."
137549
+ });
137550
+ }
137551
+ /**
137552
+ * Get all detected errors
137553
+ */
137554
+ getErrors() {
137555
+ return this.errors;
137556
+ }
137557
+ };
137558
+ var ArrayIndexTypeAnalyzer_default = ArrayIndexTypeAnalyzer;
137559
+
137081
137560
  // src/transpiler/logic/analysis/runAnalyzers.ts
137082
137561
  function collectErrors(analyzerErrors, target, formatMessage) {
137083
137562
  const formatter = formatMessage ?? ((e) => e.message);
@@ -137131,6 +137610,10 @@ function runAnalyzers(tree, tokenStream, options) {
137131
137610
  if (collectErrors(floatModAnalyzer.analyze(tree), errors, formatWithCode)) {
137132
137611
  return errors;
137133
137612
  }
137613
+ const indexTypeAnalyzer = new ArrayIndexTypeAnalyzer_default();
137614
+ if (collectErrors(indexTypeAnalyzer.analyze(tree), errors, formatWithCode)) {
137615
+ return errors;
137616
+ }
137134
137617
  const commentExtractor = new CommentExtractor_default(tokenStream);
137135
137618
  collectErrors(
137136
137619
  commentExtractor.validate(),
@@ -137738,7 +138221,7 @@ var TransitiveEnumCollector = class {
137738
138221
  /**
137739
138222
  * Collect symbol info for standalone mode from resolved includes.
137740
138223
  *
137741
- * Issue #591: Extracted from Transpiler.transpileSource() to unify enum collection.
138224
+ * Issue #591: Extracted to unify enum collection across transpilation modes.
137742
138225
  * Unlike collect() which starts from a file path and parses it, this method
137743
138226
  * starts from already-resolved includes (from IncludeResolver.resolve()).
137744
138227
  *
@@ -137816,83 +138299,53 @@ var Transpiler = class {
137816
138299
  this.cacheManager = projectRoot ? new CacheManager_default(projectRoot, this.fs) : null;
137817
138300
  }
137818
138301
  // ===========================================================================
137819
- // Public API: run() and transpileSource()
138302
+ // Public API
137820
138303
  // ===========================================================================
137821
138304
  /**
137822
- * Execute the unified pipeline from CLI inputs.
138305
+ * Unified entry point for all transpilation.
137823
138306
  *
137824
- * Stage 1 (file discovery) happens here, then delegates to _executePipeline().
138307
+ * @param input - What to transpile:
138308
+ * - { kind: 'files' } — discover from config.inputs, write to disk
138309
+ * - { kind: 'source', source, ... } — transpile in-memory source
138310
+ * @returns ITranspilerResult with per-file results in .files[]
137825
138311
  */
137826
- async run() {
138312
+ async transpile(input) {
137827
138313
  const result = this._initResult();
137828
138314
  try {
137829
138315
  await this._initializeRun();
137830
- const { cnextFiles, headerFiles } = await this.discoverSources();
137831
- if (cnextFiles.length === 0) {
138316
+ const pipelineInput = await this.discoverIncludes(input);
138317
+ if (pipelineInput.cnextFiles.length === 0) {
137832
138318
  return this._finalizeResult(result, "No C-Next source files found");
137833
138319
  }
137834
- this._ensureOutputDirectories();
137835
- const pipelineFiles = cnextFiles.map((f) => ({
137836
- path: f.path,
137837
- discoveredFile: f
137838
- }));
137839
- const input = {
137840
- cnextFiles: pipelineFiles,
137841
- headerFiles,
137842
- writeOutputToDisk: true
137843
- };
137844
- await this._executePipeline(input, result);
138320
+ if (input.kind === "files") {
138321
+ this._ensureOutputDirectories();
138322
+ }
138323
+ await this._executePipeline(pipelineInput, result);
137845
138324
  return await this._finalizeResult(result);
137846
138325
  } catch (err) {
137847
138326
  return this._handleRunError(result, err);
137848
138327
  }
137849
138328
  }
137850
138329
  /**
137851
- * Transpile source code provided as a string.
138330
+ * Stage 1: Discover files and build pipeline input.
137852
138331
  *
137853
- * Discovers includes from the source, builds an IPipelineInput, and
137854
- * delegates to the same _executePipeline() as run().
138332
+ * Branches on input kind:
138333
+ * - 'files': filesystem scan, dependency graph, topological sort
138334
+ * - 'source': parse in-memory string, walk include tree
137855
138335
  *
137856
- * @param source - The C-Next source code as a string
137857
- * @param options - Options for transpilation
137858
- * @returns Promise<IFileResult> with generated code or errors
137859
- */
137860
- async transpileSource(source, options) {
137861
- const workingDir = options?.workingDir ?? process.cwd();
137862
- const additionalIncludeDirs = options?.includeDirs ?? [];
137863
- const sourcePath = options?.sourcePath ?? "<string>";
137864
- try {
137865
- await this._initializeRun();
137866
- const input = this._discoverFromSource(
137867
- source,
137868
- workingDir,
137869
- additionalIncludeDirs,
137870
- sourcePath
137871
- );
137872
- const result = this._initResult();
137873
- await this._executePipeline(input, result);
137874
- const fileResult = result.files.find((f) => f.sourcePath === sourcePath);
137875
- if (fileResult) {
137876
- return fileResult;
137877
- }
137878
- if (result.errors.length > 0) {
137879
- return this.buildErrorResult(sourcePath, result.errors, 0);
137880
- }
137881
- return this.buildErrorResult(
137882
- sourcePath,
137883
- [
137884
- {
137885
- line: 1,
137886
- column: 0,
137887
- message: "Pipeline produced no result for source file",
137888
- severity: "error"
137889
- }
137890
- ],
137891
- 0
137892
- );
137893
- } catch (err) {
137894
- return this.buildCatchResult(sourcePath, err);
137895
- }
138336
+ * Header directive storage happens via IncludeResolver.resolve() for both
138337
+ * C headers and cnext includes (Issue #854).
138338
+ */
138339
+ async discoverIncludes(input) {
138340
+ if (input.kind === "files") {
138341
+ return this._discoverFromFiles();
138342
+ }
138343
+ return this._discoverFromSource(
138344
+ input.source,
138345
+ input.workingDir ?? process.cwd(),
138346
+ input.includeDirs ?? [],
138347
+ input.sourcePath ?? "<string>"
138348
+ );
137896
138349
  }
137897
138350
  // ===========================================================================
137898
138351
  // Unified Pipeline
@@ -137900,7 +138353,7 @@ var Transpiler = class {
137900
138353
  /**
137901
138354
  * The single unified pipeline for all transpilation.
137902
138355
  *
137903
- * Both run() and transpileSource() delegate here after file discovery.
138356
+ * transpile() delegates here after file discovery via discoverIncludes().
137904
138357
  *
137905
138358
  * Stage 2: Collect symbols from C/C++ headers (includes building analyzer context)
137906
138359
  * Stage 3: Collect symbols from C-Next files
@@ -138007,14 +138460,6 @@ var Transpiler = class {
138007
138460
  if (this.config.parseOnly) {
138008
138461
  return this.buildParseOnlyResult(sourcePath, declarationCount);
138009
138462
  }
138010
- const analyzerErrors = runAnalyzers_default(tree, tokenStream);
138011
- if (analyzerErrors.length > 0) {
138012
- return this.buildErrorResult(
138013
- sourcePath,
138014
- analyzerErrors,
138015
- declarationCount
138016
- );
138017
- }
138018
138463
  const tSymbols = cnext_default.resolve(tree, sourcePath);
138019
138464
  let symbolInfo = TSymbolInfoAdapter_default.convert(tSymbols);
138020
138465
  const externalEnumSources = this._collectExternalEnumSources(
@@ -138027,6 +138472,15 @@ var Transpiler = class {
138027
138472
  externalEnumSources
138028
138473
  );
138029
138474
  }
138475
+ CodeGenState.symbols = symbolInfo;
138476
+ const analyzerErrors = runAnalyzers_default(tree, tokenStream);
138477
+ if (analyzerErrors.length > 0) {
138478
+ return this.buildErrorResult(
138479
+ sourcePath,
138480
+ analyzerErrors,
138481
+ declarationCount
138482
+ );
138483
+ }
138030
138484
  this._setupCrossFileModifications();
138031
138485
  const code = this.codeGenerator.generate(tree, tokenStream, {
138032
138486
  debugMode: this.config.debugMode,
@@ -138044,15 +138498,7 @@ var Transpiler = class {
138044
138498
  if (this.cppDetected) {
138045
138499
  this._accumulateFileModifications();
138046
138500
  }
138047
- const fileSymbols = CodeGenState.symbolTable.getTSymbolsByFile(sourcePath);
138048
- const headerCode = this.generateHeaderContent(
138049
- fileSymbols,
138050
- sourcePath,
138051
- this.cppDetected,
138052
- userIncludes,
138053
- passByValueCopy,
138054
- symbolInfo
138055
- );
138501
+ const headerCode = this.generateHeaderForFile(file) ?? void 0;
138056
138502
  return this.buildSuccessResult(
138057
138503
  sourcePath,
138058
138504
  code,
@@ -138117,6 +138563,13 @@ var Transpiler = class {
138117
138563
  this.state.setHeaderDirective(header.path, directive);
138118
138564
  }
138119
138565
  }
138566
+ for (const cnxInclude of resolved.cnextIncludes) {
138567
+ const includePath = resolve12(cnxInclude.path);
138568
+ const directive = resolved.headerIncludeDirectives.get(includePath);
138569
+ if (directive) {
138570
+ this.state.setHeaderDirective(includePath, directive);
138571
+ }
138572
+ }
138120
138573
  const cnextIncludeFiles = [];
138121
138574
  IncludeTreeWalker_default.walk(
138122
138575
  resolved.cnextIncludes,
@@ -138174,6 +138627,7 @@ var Transpiler = class {
138174
138627
  this.state.reset();
138175
138628
  CodeGenState.symbolTable.clear();
138176
138629
  SymbolRegistry_default.reset();
138630
+ CodeGenState.callbackCompatibleFunctions = /* @__PURE__ */ new Set();
138177
138631
  }
138178
138632
  /**
138179
138633
  * Ensure output directories exist
@@ -138252,8 +138706,12 @@ var Transpiler = class {
138252
138706
  if (file.symbolOnly) {
138253
138707
  continue;
138254
138708
  }
138255
- const headerPath = this.generateHeader(file.discoveredFile);
138256
- if (headerPath) {
138709
+ const headerContent = this.generateHeaderForFile(file);
138710
+ if (headerContent) {
138711
+ const headerPath = this.pathResolver.getHeaderOutputPath(
138712
+ file.discoveredFile
138713
+ );
138714
+ this.fs.writeFile(headerPath, headerContent);
138257
138715
  result.outputFiles.push(headerPath);
138258
138716
  }
138259
138717
  }
@@ -138287,7 +138745,7 @@ var Transpiler = class {
138287
138745
  return result;
138288
138746
  }
138289
138747
  // ===========================================================================
138290
- // Source Discovery (Stage 1 for run())
138748
+ // File Discovery (Stage 1 for files mode)
138291
138749
  // ===========================================================================
138292
138750
  /**
138293
138751
  * Discover C-Next files from a single input (file or directory).
@@ -138348,6 +138806,10 @@ var Transpiler = class {
138348
138806
  ""
138349
138807
  );
138350
138808
  depGraph.addDependency(cnxPath, includePath);
138809
+ const directive = resolved.headerIncludeDirectives.get(includePath);
138810
+ if (directive) {
138811
+ this.state.setHeaderDirective(includePath, directive);
138812
+ }
138351
138813
  const alreadyExists = cnextBaseNames.has(includeBaseName) || cnextFiles.some((f) => resolve12(f.path) === includePath);
138352
138814
  if (!alreadyExists) {
138353
138815
  cnextFiles.push(cnxInclude);
@@ -138411,14 +138873,14 @@ var Transpiler = class {
138411
138873
  * This ensures headers are found based on what the source actually
138412
138874
  * includes, not by blindly scanning include directories.
138413
138875
  */
138414
- async discoverSources() {
138876
+ async _discoverFromFiles() {
138415
138877
  const cnextFiles = [];
138416
138878
  const fileByPath = /* @__PURE__ */ new Map();
138417
138879
  for (const input of this.config.inputs) {
138418
138880
  this._discoverCNextFromInput(input, cnextFiles, fileByPath);
138419
138881
  }
138420
138882
  if (cnextFiles.length === 0) {
138421
- return { cnextFiles: [], headerFiles: [] };
138883
+ return { cnextFiles: [], headerFiles: [], writeOutputToDisk: true };
138422
138884
  }
138423
138885
  const headerSet = /* @__PURE__ */ new Map();
138424
138886
  const depGraph = new DependencyGraph_default();
@@ -138446,9 +138908,14 @@ var Transpiler = class {
138446
138908
  }
138447
138909
  );
138448
138910
  this.warnings.push(...headerWarnings);
138911
+ const pipelineFiles = sortedCnextFiles.map((f) => ({
138912
+ path: f.path,
138913
+ discoveredFile: f
138914
+ }));
138449
138915
  return {
138450
- cnextFiles: sortedCnextFiles,
138451
- headerFiles: allHeaders
138916
+ cnextFiles: pipelineFiles,
138917
+ headerFiles: allHeaders,
138918
+ writeOutputToDisk: true
138452
138919
  };
138453
138920
  }
138454
138921
  // ===========================================================================
@@ -138633,17 +139100,22 @@ var Transpiler = class {
138633
139100
  * Stage 6: Generate header file for a C-Next file
138634
139101
  * ADR-055 Phase 7: Uses TSymbol directly, converts to IHeaderSymbol for generation.
138635
139102
  */
138636
- generateHeader(file) {
138637
- const tSymbols = CodeGenState.symbolTable.getTSymbolsByFile(file.path);
139103
+ /**
139104
+ * Generate header content for a single file's exported symbols.
139105
+ * Unified method replacing both generateHeader() and generateHeaderContent().
139106
+ * Reads all needed data from state (populated during Stage 5).
139107
+ */
139108
+ generateHeaderForFile(file) {
139109
+ const sourcePath = file.path;
139110
+ const tSymbols = CodeGenState.symbolTable.getTSymbolsByFile(sourcePath);
138638
139111
  const exportedSymbols = tSymbols.filter((s) => s.isExported);
138639
139112
  if (exportedSymbols.length === 0) {
138640
139113
  return null;
138641
139114
  }
138642
- const headerName = basename5(file.path).replace(/\.cnx$|\.cnext$/, ".h");
138643
- const headerPath = this.pathResolver.getHeaderOutputPath(file);
138644
- const typeInput = this.state.getSymbolInfo(file.path);
138645
- const passByValueParams = this.state.getPassByValueParams(file.path) ?? /* @__PURE__ */ new Map();
138646
- const userIncludes = this.state.getUserIncludes(file.path);
139115
+ const headerName = basename5(sourcePath).replace(/\.cnx$|\.cnext$/, ".h");
139116
+ const typeInput = this.state.getSymbolInfo(sourcePath);
139117
+ const passByValueParams = this.state.getPassByValueParams(sourcePath) ?? /* @__PURE__ */ new Map();
139118
+ const userIncludes = this.state.getUserIncludes(sourcePath);
138647
139119
  const allKnownEnums = TransitiveEnumCollector_default.aggregateKnownEnums(
138648
139120
  this.state.getAllSymbolInfo()
138649
139121
  );
@@ -138658,7 +139130,7 @@ var Transpiler = class {
138658
139130
  unmodifiedParams,
138659
139131
  allKnownEnums
138660
139132
  );
138661
- const headerContent = this.headerGenerator.generate(
139133
+ return this.headerGenerator.generate(
138662
139134
  headerSymbols,
138663
139135
  headerName,
138664
139136
  {
@@ -138671,8 +139143,6 @@ var Transpiler = class {
138671
139143
  passByValueParams,
138672
139144
  allKnownEnums
138673
139145
  );
138674
- this.fs.writeFile(headerPath, headerContent);
138675
- return headerPath;
138676
139146
  }
138677
139147
  /**
138678
139148
  * Collect external enum sources from included C-Next files.
@@ -138705,43 +139175,6 @@ var Transpiler = class {
138705
139175
  );
138706
139176
  }
138707
139177
  }
138708
- /**
138709
- * Generate header content for exported symbols.
138710
- * Issue #591: Extracted from transpileSource() for reduced complexity.
138711
- * ADR-055 Phase 7: Works with TSymbol[], converts to IHeaderSymbol for generation.
138712
- */
138713
- generateHeaderContent(tSymbols, sourcePath, cppMode, userIncludes, passByValueParams, symbolInfo) {
138714
- const exportedSymbols = tSymbols.filter((s) => s.isExported);
138715
- if (exportedSymbols.length === 0) {
138716
- return void 0;
138717
- }
138718
- const unmodifiedParams = this.codeGenerator.getFunctionUnmodifiedParams();
138719
- const headerSymbols = this.convertToHeaderSymbols(
138720
- exportedSymbols,
138721
- unmodifiedParams,
138722
- symbolInfo.knownEnums
138723
- );
138724
- const headerName = basename5(sourcePath).replace(/\.cnx$|\.cnext$/, ".h");
138725
- const typeInput = CodeGenState.symbols;
138726
- const externalTypeHeaders = ExternalTypeHeaderBuilder_default.build(
138727
- this.state.getAllHeaderDirectives(),
138728
- CodeGenState.symbolTable
138729
- );
138730
- const typeInputWithSymbolTable = typeInput ? { ...typeInput, symbolTable: CodeGenState.symbolTable } : void 0;
138731
- return this.headerGenerator.generate(
138732
- headerSymbols,
138733
- headerName,
138734
- {
138735
- exportedOnly: true,
138736
- userIncludes,
138737
- externalTypeHeaders,
138738
- cppMode
138739
- },
138740
- typeInputWithSymbolTable,
138741
- passByValueParams,
138742
- symbolInfo.knownEnums
138743
- );
138744
- }
138745
139178
  /**
138746
139179
  * Convert TSymbols to IHeaderSymbols with auto-const information applied.
138747
139180
  * ADR-055 Phase 7: Replaces mutation-based auto-const updating.
@@ -138949,7 +139382,7 @@ var Runner = class {
138949
139382
  target: config.target,
138950
139383
  debugMode: config.debugMode
138951
139384
  });
138952
- const result = await pipeline.run();
139385
+ const result = await pipeline.transpile({ kind: "files" });
138953
139386
  this._renameOutputIfNeeded(result, explicitOutputFile);
138954
139387
  ResultPrinter_default.print(result);
138955
139388
  process.exit(result.success ? 0 : 1);
@@ -139659,16 +140092,31 @@ var ServeCommand = class _ServeCommand {
139659
140092
  }
139660
140093
  const { source, filePath } = params;
139661
140094
  const options = filePath ? { workingDir: dirname11(filePath), sourcePath: filePath } : void 0;
139662
- const result = await _ServeCommand.transpiler.transpileSource(
140095
+ const transpileResult = await _ServeCommand.transpiler.transpile({
140096
+ kind: "source",
139663
140097
  source,
139664
- options
139665
- );
140098
+ ...options
140099
+ });
140100
+ const fileResult = transpileResult.files.find(
140101
+ (f) => f.sourcePath === (filePath ?? "<string>")
140102
+ ) ?? transpileResult.files[0];
140103
+ if (!fileResult) {
140104
+ return {
140105
+ success: true,
140106
+ result: {
140107
+ success: false,
140108
+ code: "",
140109
+ errors: transpileResult.errors,
140110
+ cppDetected: _ServeCommand.transpiler.isCppDetected()
140111
+ }
140112
+ };
140113
+ }
139666
140114
  return {
139667
140115
  success: true,
139668
140116
  result: {
139669
- success: result.success,
139670
- code: result.code,
139671
- errors: result.errors,
140117
+ success: fileResult.success,
140118
+ code: fileResult.code,
140119
+ errors: fileResult.errors,
139672
140120
  cppDetected: _ServeCommand.transpiler.isCppDetected()
139673
140121
  }
139674
140122
  };
@@ -139682,7 +140130,9 @@ var ServeCommand = class _ServeCommand {
139682
140130
  const { source, filePath } = params;
139683
140131
  if (_ServeCommand.transpiler && filePath) {
139684
140132
  try {
139685
- await _ServeCommand.transpiler.transpileSource(source, {
140133
+ await _ServeCommand.transpiler.transpile({
140134
+ kind: "source",
140135
+ source,
139686
140136
  workingDir: dirname11(filePath),
139687
140137
  sourcePath: filePath
139688
140138
  });