c-next 0.2.3 → 0.2.5

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 (61) hide show
  1. package/README.md +59 -57
  2. package/dist/index.js +859 -198
  3. package/dist/index.js.map +4 -4
  4. package/package.json +3 -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 +281 -14
  17. package/src/transpiler/logic/analysis/__tests__/ArrayIndexTypeAnalyzer.test.ts +545 -0
  18. package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +375 -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/__tests__/CResolver.integration.test.ts +18 -0
  23. package/src/transpiler/logic/symbols/c/index.ts +50 -1
  24. package/src/transpiler/output/codegen/CodeGenerator.ts +69 -15
  25. package/src/transpiler/output/codegen/TypeResolver.ts +1 -1
  26. package/src/transpiler/output/codegen/TypeValidator.ts +10 -7
  27. package/src/transpiler/output/codegen/__tests__/ExpressionWalker.test.ts +9 -3
  28. package/src/transpiler/output/codegen/__tests__/RequireInclude.test.ts +86 -23
  29. package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +3 -1
  30. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +43 -29
  31. package/src/transpiler/output/codegen/generators/IOrchestrator.ts +5 -2
  32. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +15 -3
  33. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +1 -1
  34. package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +12 -3
  35. package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +4 -4
  36. package/src/transpiler/output/codegen/helpers/FunctionContextManager.ts +64 -3
  37. package/src/transpiler/output/codegen/helpers/MemberSeparatorResolver.ts +28 -6
  38. package/src/transpiler/output/codegen/helpers/ParameterDereferenceResolver.ts +12 -0
  39. package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +30 -2
  40. package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +15 -7
  41. package/src/transpiler/output/codegen/helpers/StringOperationsHelper.ts +1 -1
  42. package/src/transpiler/output/codegen/helpers/TypedefParamParser.ts +220 -0
  43. package/src/transpiler/output/codegen/helpers/__tests__/FunctionContextManager.test.ts +5 -5
  44. package/src/transpiler/output/codegen/helpers/__tests__/MemberSeparatorResolver.test.ts +48 -36
  45. package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +37 -0
  46. package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +63 -0
  47. package/src/transpiler/output/codegen/helpers/__tests__/TypedefParamParser.test.ts +209 -0
  48. package/src/transpiler/output/codegen/resolution/EnumTypeResolver.ts +1 -1
  49. package/src/transpiler/output/codegen/resolution/SizeofResolver.ts +1 -1
  50. package/src/transpiler/output/codegen/types/IParameterInput.ts +13 -0
  51. package/src/transpiler/output/codegen/types/ISeparatorContext.ts +7 -0
  52. package/src/transpiler/output/codegen/types/TParameterInfo.ts +12 -0
  53. package/src/transpiler/output/codegen/utils/CodegenParserUtils.ts +1 -1
  54. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +5 -2
  55. package/src/transpiler/state/CodeGenState.ts +25 -0
  56. package/src/transpiler/types/IPipelineFile.ts +2 -2
  57. package/src/transpiler/types/IPipelineInput.ts +1 -1
  58. package/src/transpiler/types/TTranspileInput.ts +18 -0
  59. package/src/{transpiler/output/codegen/utils → utils}/ExpressionUnwrapper.ts +1 -1
  60. package/src/{transpiler/output/codegen/utils → utils}/__tests__/ExpressionUnwrapper.test.ts +2 -2
  61. 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.3",
13
+ version: "0.2.5",
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",
@@ -32,6 +32,8 @@ var package_default = {
32
32
  "test:cli": "node scripts/test-cli.js",
33
33
  "test:q": "tsx scripts/test.ts -q",
34
34
  "test:update": "tsx scripts/test.ts --update",
35
+ "integration:transpile": "tsx scripts/test.ts --transpile-only",
36
+ "integration:execute": "tsx scripts/test.ts --execute-only",
35
37
  analyze: "./scripts/static-analysis.sh",
36
38
  clean: "rm -rf dist src/transpiler/logic/parser/grammar src/transpiler/logic/parser/c/grammar src/transpiler/logic/parser/cpp/grammar",
37
39
  "prettier:check": "prettier --check .",
@@ -110764,6 +110766,12 @@ var CodeGenState = class {
110764
110766
  static callbackTypes = /* @__PURE__ */ new Map();
110765
110767
  /** Callback field types: "Struct.field" -> callbackTypeName */
110766
110768
  static callbackFieldTypes = /* @__PURE__ */ new Map();
110769
+ /**
110770
+ * Functions that are assigned to C callback typedefs.
110771
+ * Maps function name -> typedef name (e.g., "my_flush" -> "flush_cb_t")
110772
+ * Issue #895: We need the typedef name to look up parameter types.
110773
+ */
110774
+ static callbackCompatibleFunctions = /* @__PURE__ */ new Map();
110767
110775
  // ===========================================================================
110768
110776
  // PASS-BY-VALUE ANALYSIS (Issue #269)
110769
110777
  // ===========================================================================
@@ -111131,6 +111139,20 @@ var CodeGenState = class {
111131
111139
  static getCallbackType(name) {
111132
111140
  return this.callbackTypes.get(name);
111133
111141
  }
111142
+ /**
111143
+ * Issue #895: Get the typedef type string for a C typedef by name.
111144
+ * Used to look up function pointer typedef signatures for callback-compatible functions.
111145
+ *
111146
+ * @param typedefName - Name of the typedef (e.g., "flush_cb_t")
111147
+ * @returns The type string (e.g., "void (*)(widget_t *, const rect_t *, uint8_t *)") or undefined
111148
+ */
111149
+ static getTypedefType(typedefName) {
111150
+ const symbol = this.symbolTable.getCSymbol(typedefName);
111151
+ if (symbol?.kind === "type") {
111152
+ return symbol.type;
111153
+ }
111154
+ return void 0;
111155
+ }
111134
111156
  /**
111135
111157
  * Check if a type name is a known C-Next function.
111136
111158
  */
@@ -112904,7 +112926,7 @@ var TYPE_RANGES = {
112904
112926
  };
112905
112927
  var TYPE_RANGES_default = TYPE_RANGES;
112906
112928
 
112907
- // src/transpiler/output/codegen/utils/ExpressionUnwrapper.ts
112929
+ // src/utils/ExpressionUnwrapper.ts
112908
112930
  var ExpressionUnwrapper = class {
112909
112931
  /**
112910
112932
  * Navigate from ExpressionContext to ShiftExpressionContext.
@@ -114074,14 +114096,14 @@ var TypeValidator = class _TypeValidator {
114074
114096
  }
114075
114097
  }
114076
114098
  // ========================================================================
114077
- // Do-While Validation (ADR-027)
114099
+ // Condition Boolean Validation (ADR-027, Issue #884)
114078
114100
  // ========================================================================
114079
- static validateDoWhileCondition(ctx) {
114101
+ static validateConditionIsBoolean(ctx, conditionType) {
114080
114102
  const ternaryExpr = ctx.ternaryExpression();
114081
114103
  const orExprs = ternaryExpr.orExpression();
114082
114104
  if (orExprs.length !== 1) {
114083
114105
  throw new Error(
114084
- `Error E0701: do-while condition must be a boolean expression, not a ternary (MISRA C:2012 Rule 14.4)`
114106
+ `Error E0701: ${conditionType} condition must be a boolean expression, not a ternary (MISRA C:2012 Rule 14.4)`
114085
114107
  );
114086
114108
  }
114087
114109
  const orExpr = orExprs[0];
@@ -114092,7 +114114,7 @@ var TypeValidator = class _TypeValidator {
114092
114114
  const andExpr = orExpr.andExpression(0);
114093
114115
  if (!andExpr) {
114094
114116
  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)`
114117
+ `Error E0701: ${conditionType} condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)`
114096
114118
  );
114097
114119
  }
114098
114120
  if (andExpr.equalityExpression().length > 1) {
@@ -114101,7 +114123,7 @@ var TypeValidator = class _TypeValidator {
114101
114123
  const equalityExpr = andExpr.equalityExpression(0);
114102
114124
  if (!equalityExpr) {
114103
114125
  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)`
114126
+ `Error E0701: ${conditionType} condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)`
114105
114127
  );
114106
114128
  }
114107
114129
  if (equalityExpr.relationalExpression().length > 1) {
@@ -114110,7 +114132,7 @@ var TypeValidator = class _TypeValidator {
114110
114132
  const relationalExpr = equalityExpr.relationalExpression(0);
114111
114133
  if (!relationalExpr) {
114112
114134
  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)`
114135
+ `Error E0701: ${conditionType} condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)`
114114
114136
  );
114115
114137
  }
114116
114138
  if (relationalExpr.bitwiseOrExpression().length > 1) {
@@ -114121,7 +114143,7 @@ var TypeValidator = class _TypeValidator {
114121
114143
  return;
114122
114144
  }
114123
114145
  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)
114146
+ `Error E0701: ${conditionType} condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)
114125
114147
  help: use explicit comparison: ${text} > 0 or ${text} != 0`
114126
114148
  );
114127
114149
  }
@@ -115742,6 +115764,7 @@ var generatePostfixExpression = (ctx, input, state, orchestrator) => {
115742
115764
  const rootIdentifier = primary.IDENTIFIER()?.getText();
115743
115765
  const paramInfo = rootIdentifier ? state.currentParameters.get(rootIdentifier) : null;
115744
115766
  const isStructParam = paramInfo?.isStruct ?? false;
115767
+ const forcePointerSemantics = paramInfo?.forcePointerSemantics ?? false;
115745
115768
  const hasSubscriptOps = ops.some((op) => op.expression().length > 0);
115746
115769
  const isNonArrayParamWithSubscript = paramInfo && !paramInfo.isArray && !paramInfo.isStruct && hasSubscriptOps;
115747
115770
  let result;
@@ -115762,6 +115785,7 @@ var generatePostfixExpression = (ctx, input, state, orchestrator) => {
115762
115785
  const postfixCtx = {
115763
115786
  rootIdentifier,
115764
115787
  isStructParam,
115788
+ forcePointerSemantics,
115765
115789
  input,
115766
115790
  state,
115767
115791
  orchestrator,
@@ -115853,6 +115877,7 @@ var handleMemberOp = (memberName, tracking, ctx) => {
115853
115877
  memberName,
115854
115878
  rootIdentifier: ctx.rootIdentifier,
115855
115879
  isStructParam: ctx.isStructParam,
115880
+ forcePointerSemantics: ctx.forcePointerSemantics,
115856
115881
  isGlobalAccess: tracking.isGlobalAccess,
115857
115882
  isCppAccessChain: tracking.isCppAccessChain,
115858
115883
  currentStructType: tracking.currentStructType,
@@ -116587,7 +116612,7 @@ var tryStructParamAccess = (ctx, orchestrator) => {
116587
116612
  if (!ctx.isStructParam || ctx.result !== ctx.rootIdentifier) {
116588
116613
  return null;
116589
116614
  }
116590
- const structParamSep = memberAccessChain_default.getStructParamSeparator({
116615
+ const structParamSep = ctx.forcePointerSemantics ? "->" : memberAccessChain_default.getStructParamSeparator({
116591
116616
  cppMode: orchestrator.isCppMode()
116592
116617
  });
116593
116618
  const output = initializeMemberOutput(ctx);
@@ -116940,6 +116965,7 @@ var generateIf = (node, _input, _state, orchestrator) => {
116940
116965
  }
116941
116966
  const cacheDecls = orchestrator.setupLengthCache(lengthCounts);
116942
116967
  orchestrator.validateConditionNoFunctionCall(node.expression(), "if");
116968
+ orchestrator.validateConditionIsBoolean(node.expression(), "if");
116943
116969
  const condition = orchestrator.generateExpression(node.expression());
116944
116970
  const conditionTemps = orchestrator.flushPendingTempDeclarations();
116945
116971
  const thenBranch = orchestrator.generateStatement(thenStmt);
@@ -116960,6 +116986,7 @@ var generateIf = (node, _input, _state, orchestrator) => {
116960
116986
  var generateWhile = (node, _input, _state, orchestrator) => {
116961
116987
  const effects = [];
116962
116988
  orchestrator.validateConditionNoFunctionCall(node.expression(), "while");
116989
+ orchestrator.validateConditionIsBoolean(node.expression(), "while");
116963
116990
  const condition = orchestrator.generateExpression(node.expression());
116964
116991
  const conditionTemps = orchestrator.flushPendingTempDeclarations();
116965
116992
  const body = orchestrator.generateStatement(node.statement());
@@ -116971,8 +116998,8 @@ var generateWhile = (node, _input, _state, orchestrator) => {
116971
116998
  };
116972
116999
  var generateDoWhile = (node, _input, _state, orchestrator) => {
116973
117000
  const effects = [];
116974
- orchestrator.validateDoWhileCondition(node.expression());
116975
117001
  orchestrator.validateConditionNoFunctionCall(node.expression(), "do-while");
117002
+ orchestrator.validateConditionIsBoolean(node.expression(), "do-while");
116976
117003
  const body = orchestrator.generateBlock(node.block());
116977
117004
  const condition = orchestrator.generateExpression(node.expression());
116978
117005
  const conditionTemps = orchestrator.flushPendingTempDeclarations();
@@ -117037,6 +117064,7 @@ var generateFor = (node, input, state, orchestrator) => {
117037
117064
  let condition = "";
117038
117065
  if (node.expression()) {
117039
117066
  orchestrator.validateConditionNoFunctionCall(node.expression(), "for");
117067
+ orchestrator.validateConditionIsBoolean(node.expression(), "for");
117040
117068
  condition = orchestrator.generateExpression(node.expression());
117041
117069
  }
117042
117070
  const conditionTemps = orchestrator.flushPendingTempDeclarations();
@@ -124789,7 +124817,16 @@ var MemberSeparatorResolver = class _MemberSeparatorResolver {
124789
124817
  /**
124790
124818
  * Build the separator context for a member access chain
124791
124819
  */
124792
- static buildContext(firstId, hasGlobal, hasThis, currentScope, isStructParam, deps, isCppAccess) {
124820
+ static buildContext(input, deps) {
124821
+ const {
124822
+ firstId,
124823
+ hasGlobal,
124824
+ hasThis,
124825
+ currentScope,
124826
+ isStructParam,
124827
+ isCppAccess,
124828
+ forcePointerSemantics
124829
+ } = input;
124793
124830
  const isCrossScope = hasGlobal && (deps.isKnownScope(firstId) || deps.isKnownRegister(firstId));
124794
124831
  const scopedRegName = hasThis && currentScope ? `${currentScope}_${firstId}` : null;
124795
124832
  const isScopedRegister = scopedRegName !== null && deps.isKnownRegister(scopedRegName);
@@ -124799,7 +124836,8 @@ var MemberSeparatorResolver = class _MemberSeparatorResolver {
124799
124836
  isStructParam,
124800
124837
  isCppAccess,
124801
124838
  scopedRegName,
124802
- isScopedRegister
124839
+ isScopedRegister,
124840
+ forcePointerSemantics
124803
124841
  };
124804
124842
  }
124805
124843
  /**
@@ -124810,6 +124848,9 @@ var MemberSeparatorResolver = class _MemberSeparatorResolver {
124810
124848
  return "::";
124811
124849
  }
124812
124850
  if (ctx.isStructParam) {
124851
+ if (ctx.forcePointerSemantics) {
124852
+ return "->";
124853
+ }
124813
124854
  return deps.getStructParamSeparator();
124814
124855
  }
124815
124856
  if (ctx.isCrossScope) {
@@ -124870,6 +124911,9 @@ var ParameterDereferenceResolver = class _ParameterDereferenceResolver {
124870
124911
  * Determine if a parameter should be passed by value (no dereference needed)
124871
124912
  */
124872
124913
  static isPassByValue(paramInfo, deps) {
124914
+ if (paramInfo.isCallbackPointerPrimitive) {
124915
+ return false;
124916
+ }
124873
124917
  if (paramInfo.isCallback) {
124874
124918
  return true;
124875
124919
  }
@@ -124908,6 +124952,9 @@ var ParameterDereferenceResolver = class _ParameterDereferenceResolver {
124908
124952
  if (_ParameterDereferenceResolver.isPassByValue(paramInfo, deps)) {
124909
124953
  return id;
124910
124954
  }
124955
+ if (paramInfo.forcePointerSemantics) {
124956
+ return `(*${id})`;
124957
+ }
124911
124958
  return deps.maybeDereference(id);
124912
124959
  }
124913
124960
  };
@@ -125450,6 +125497,137 @@ var CastValidator = class _CastValidator {
125450
125497
  };
125451
125498
  var CastValidator_default = CastValidator;
125452
125499
 
125500
+ // src/transpiler/output/codegen/helpers/TypedefParamParser.ts
125501
+ var TypedefParamParser = class _TypedefParamParser {
125502
+ /**
125503
+ * Parse a function pointer typedef type string.
125504
+ *
125505
+ * @param typedefType - The type string, e.g., "void (*)(widget_t *, const rect_t *, uint8_t *)"
125506
+ * @returns Parsed result with return type and parameters, or null if parsing fails
125507
+ */
125508
+ static parse(typedefType) {
125509
+ const funcPtrIndex = typedefType.indexOf("(*)");
125510
+ if (funcPtrIndex === -1) {
125511
+ return null;
125512
+ }
125513
+ const returnType = typedefType.substring(0, funcPtrIndex).trim();
125514
+ const afterFuncPtr = typedefType.substring(funcPtrIndex + 3).trim();
125515
+ if (!afterFuncPtr.startsWith("(")) {
125516
+ return null;
125517
+ }
125518
+ const paramsStr = _TypedefParamParser.extractParenContent(afterFuncPtr);
125519
+ if (paramsStr === null) {
125520
+ return null;
125521
+ }
125522
+ if (!paramsStr || paramsStr === "void") {
125523
+ return { returnType, params: [] };
125524
+ }
125525
+ const paramStrings = _TypedefParamParser.splitParams(paramsStr);
125526
+ const params = paramStrings.map((p) => _TypedefParamParser.parseParam(p));
125527
+ return { returnType, params };
125528
+ }
125529
+ /**
125530
+ * Extract content between matching parentheses, handling arbitrary nesting.
125531
+ * @param str - String starting with '('
125532
+ * @returns Content between outer parens, or null if no match
125533
+ */
125534
+ static extractParenContent(str) {
125535
+ if (!str.startsWith("(")) {
125536
+ return null;
125537
+ }
125538
+ let depth = 0;
125539
+ for (let i = 0; i < str.length; i++) {
125540
+ if (str[i] === "(") {
125541
+ depth++;
125542
+ } else if (str[i] === ")") {
125543
+ depth--;
125544
+ if (depth === 0) {
125545
+ return str.substring(1, i);
125546
+ }
125547
+ }
125548
+ }
125549
+ return null;
125550
+ }
125551
+ /**
125552
+ * Split parameter string by commas, respecting nested parentheses.
125553
+ */
125554
+ static splitParams(paramsStr) {
125555
+ const params = [];
125556
+ let current = "";
125557
+ let depth = 0;
125558
+ for (const char of paramsStr) {
125559
+ if (char === "(") {
125560
+ depth++;
125561
+ current += char;
125562
+ } else if (char === ")") {
125563
+ depth--;
125564
+ current += char;
125565
+ } else if (char === "," && depth === 0) {
125566
+ params.push(current.trim());
125567
+ current = "";
125568
+ } else {
125569
+ current += char;
125570
+ }
125571
+ }
125572
+ if (current.trim()) {
125573
+ params.push(current.trim());
125574
+ }
125575
+ return params;
125576
+ }
125577
+ /**
125578
+ * Parse a single parameter type string.
125579
+ */
125580
+ static parseParam(paramStr) {
125581
+ const trimmed = paramStr.trim();
125582
+ const isPointer = trimmed.includes("*");
125583
+ const isConst = /\bconst\b/.test(trimmed) || trimmed.startsWith("const");
125584
+ let baseType = trimmed.replaceAll(/\bconst\b/g, "").replace(/^const/, "").replaceAll("*", "").replaceAll(/\s+/g, " ").trim();
125585
+ if (baseType.includes(" ")) {
125586
+ baseType = baseType.replace(/\s+\w+$/, "");
125587
+ }
125588
+ if (baseType.startsWith("struct ")) {
125589
+ baseType = baseType.substring(7);
125590
+ }
125591
+ return {
125592
+ type: trimmed,
125593
+ isPointer,
125594
+ isConst,
125595
+ baseType
125596
+ };
125597
+ }
125598
+ /**
125599
+ * Get the parameter info at a given index, or null if not found.
125600
+ */
125601
+ static getParamAt(typedefType, paramIndex) {
125602
+ const parsed = _TypedefParamParser.parse(typedefType);
125603
+ if (!parsed || paramIndex >= parsed.params.length) {
125604
+ return null;
125605
+ }
125606
+ return parsed.params[paramIndex];
125607
+ }
125608
+ /**
125609
+ * Check if a parameter at a given index should be a pointer based on the typedef.
125610
+ *
125611
+ * @param typedefType - The typedef type string
125612
+ * @param paramIndex - The parameter index (0-based)
125613
+ * @returns true if the param should be a pointer, false for value, null if unknown
125614
+ */
125615
+ static shouldBePointer(typedefType, paramIndex) {
125616
+ return _TypedefParamParser.getParamAt(typedefType, paramIndex)?.isPointer ?? null;
125617
+ }
125618
+ /**
125619
+ * Check if a parameter at a given index should be const based on the typedef.
125620
+ *
125621
+ * @param typedefType - The typedef type string
125622
+ * @param paramIndex - The parameter index (0-based)
125623
+ * @returns true if the param should be const, false otherwise, null if unknown
125624
+ */
125625
+ static shouldBeConst(typedefType, paramIndex) {
125626
+ return _TypedefParamParser.getParamAt(typedefType, paramIndex)?.isConst ?? null;
125627
+ }
125628
+ };
125629
+ var TypedefParamParser_default = TypedefParamParser;
125630
+
125453
125631
  // src/transpiler/output/codegen/helpers/FunctionContextManager.ts
125454
125632
  var FunctionContextManager = class _FunctionContextManager {
125455
125633
  /**
@@ -125510,14 +125688,15 @@ var FunctionContextManager = class _FunctionContextManager {
125510
125688
  static processParameterList(params, callbacks) {
125511
125689
  CodeGenState.currentParameters.clear();
125512
125690
  if (!params) return;
125513
- for (const param of params.parameter()) {
125514
- _FunctionContextManager.processParameter(param, callbacks);
125691
+ const paramList = params.parameter();
125692
+ for (let i = 0; i < paramList.length; i++) {
125693
+ _FunctionContextManager.processParameter(paramList[i], callbacks, i);
125515
125694
  }
125516
125695
  }
125517
125696
  /**
125518
125697
  * Process a single parameter declaration.
125519
125698
  */
125520
- static processParameter(param, callbacks) {
125699
+ static processParameter(param, callbacks, paramIndex) {
125521
125700
  const name = param.IDENTIFIER().getText();
125522
125701
  const isArray = param.arrayDimension().length > 0 || param.type().arrayType() !== null;
125523
125702
  const isConst = param.constModifier() !== null;
@@ -125526,14 +125705,21 @@ var FunctionContextManager = class _FunctionContextManager {
125526
125705
  typeCtx,
125527
125706
  callbacks
125528
125707
  );
125708
+ const callbackTypedefInfo = _FunctionContextManager.getCallbackTypedefParamInfo(paramIndex);
125709
+ const isCallbackPointerParam = callbackTypedefInfo?.shouldBePointer ?? false;
125710
+ const isStruct = callbackTypedefInfo ? isCallbackPointerParam && typeInfo.isStruct : typeInfo.isStruct;
125711
+ const isCallbackPointerPrimitive = isCallbackPointerParam && !typeInfo.isStruct && !isArray;
125529
125712
  const paramInfo = {
125530
125713
  name,
125531
125714
  baseType: typeInfo.typeName,
125532
125715
  isArray,
125533
- isStruct: typeInfo.isStruct,
125716
+ isStruct,
125534
125717
  isConst,
125535
125718
  isCallback: typeInfo.isCallback,
125536
- isString: typeInfo.isString
125719
+ isString: typeInfo.isString,
125720
+ isCallbackPointerPrimitive,
125721
+ // Issue #895: Callback-compatible params need pointer semantics even in C++ mode
125722
+ forcePointerSemantics: isCallbackPointerParam
125537
125723
  };
125538
125724
  CodeGenState.currentParameters.set(name, paramInfo);
125539
125725
  _FunctionContextManager.registerParameterType(
@@ -125696,6 +125882,32 @@ var FunctionContextManager = class _FunctionContextManager {
125696
125882
  }
125697
125883
  return dimensions;
125698
125884
  }
125885
+ /**
125886
+ * Issue #895: Get callback typedef parameter info from the C header.
125887
+ * Returns null if not callback-compatible or index is invalid.
125888
+ */
125889
+ static getCallbackTypedefParamInfo(paramIndex) {
125890
+ if (CodeGenState.currentFunctionName === null) return null;
125891
+ const typedefName = CodeGenState.callbackCompatibleFunctions.get(
125892
+ CodeGenState.currentFunctionName
125893
+ );
125894
+ if (!typedefName) return null;
125895
+ const typedefType = CodeGenState.getTypedefType(typedefName);
125896
+ if (!typedefType) return null;
125897
+ const shouldBePointer = TypedefParamParser_default.shouldBePointer(
125898
+ typedefType,
125899
+ paramIndex
125900
+ );
125901
+ const shouldBeConst = TypedefParamParser_default.shouldBeConst(
125902
+ typedefType,
125903
+ paramIndex
125904
+ );
125905
+ if (shouldBePointer === null) return null;
125906
+ return {
125907
+ shouldBePointer,
125908
+ shouldBeConst: shouldBeConst ?? false
125909
+ };
125910
+ }
125699
125911
  /**
125700
125912
  * Extract string capacity from a string type context.
125701
125913
  */
@@ -126488,7 +126700,8 @@ var ParameterInputAdapter = class {
126488
126700
  }
126489
126701
  const isKnownStruct = deps.isKnownStruct(typeName);
126490
126702
  const isKnownPrimitive = !!deps.typeMap[typeName];
126491
- const isAutoConst = !deps.isModified && !isConst;
126703
+ const isAutoConst = !deps.isCallbackCompatible && !deps.isModified && !isConst;
126704
+ const isPassByReference = deps.forcePassByReference || isKnownStruct || isKnownPrimitive;
126492
126705
  return {
126493
126706
  name,
126494
126707
  baseType: typeName,
@@ -126499,7 +126712,12 @@ var ParameterInputAdapter = class {
126499
126712
  isCallback: false,
126500
126713
  isString: false,
126501
126714
  isPassByValue: deps.isPassByValue,
126502
- isPassByReference: isKnownStruct || isKnownPrimitive
126715
+ isPassByReference,
126716
+ // Issue #895: Force pointer syntax in C++ mode for callback-compatible functions
126717
+ // because C callback typedefs expect pointers, not C++ references
126718
+ forcePointerSyntax: deps.forcePassByReference,
126719
+ // Issue #895: Preserve const from callback typedef signature
126720
+ forceConst: deps.forceConst
126503
126721
  };
126504
126722
  }
126505
126723
  /**
@@ -126713,11 +126931,15 @@ var ParameterSignatureBuilder = class {
126713
126931
  /**
126714
126932
  * Build pass-by-reference parameter signature.
126715
126933
  * C mode: const Point* p
126716
- * C++ mode: const Point& p
126934
+ * C++ mode: const Point& p (unless forcePointerSyntax)
126935
+ *
126936
+ * Issue #895: When forcePointerSyntax is set, always use pointer syntax
126937
+ * because C callback typedefs expect pointers, not C++ references.
126717
126938
  */
126718
126939
  static _buildRefParam(param, refSuffix) {
126719
126940
  const constPrefix = this._getConstPrefix(param);
126720
- return `${constPrefix}${param.mappedType}${refSuffix} ${param.name}`;
126941
+ const actualSuffix = param.forcePointerSyntax ? "*" : refSuffix;
126942
+ return `${constPrefix}${param.mappedType}${actualSuffix} ${param.name}`;
126721
126943
  }
126722
126944
  /**
126723
126945
  * Build unknown type parameter (pass by value, standard C semantics).
@@ -126727,13 +126949,15 @@ var ParameterSignatureBuilder = class {
126727
126949
  return `${constMod}${param.mappedType} ${param.name}`;
126728
126950
  }
126729
126951
  /**
126730
- * Get const prefix combining explicit const and auto-const.
126731
- * Auto-const is applied to unmodified parameters.
126952
+ * Get const prefix combining explicit const, auto-const, and forced const.
126953
+ * Priority: forceConst > isConst > isAutoConst
126954
+ * Issue #895: forceConst preserves const from callback typedef signature.
126732
126955
  */
126733
126956
  static _getConstPrefix(param) {
126734
- const autoConst = param.isAutoConst && !param.isConst ? "const " : "";
126735
- const explicitConst = param.isConst ? "const " : "";
126736
- return `${autoConst}${explicitConst}`;
126957
+ if (param.forceConst || param.isConst || param.isAutoConst) {
126958
+ return "const ";
126959
+ }
126960
+ return "";
126737
126961
  }
126738
126962
  };
126739
126963
  var ParameterSignatureBuilder_default = ParameterSignatureBuilder;
@@ -127561,11 +127785,11 @@ var CodeGenerator = class _CodeGenerator {
127561
127785
  TypeValidator_default.validateSwitchStatement(ctx, switchExpr);
127562
127786
  }
127563
127787
  /**
127564
- * Validate do-while condition.
127788
+ * Validate condition is a boolean expression (ADR-027, Issue #884).
127565
127789
  * Part of IOrchestrator interface (ADR-053 A3).
127566
127790
  */
127567
- validateDoWhileCondition(ctx) {
127568
- TypeValidator_default.validateDoWhileCondition(ctx);
127791
+ validateConditionIsBoolean(ctx, conditionType) {
127792
+ TypeValidator_default.validateConditionIsBoolean(ctx, conditionType);
127569
127793
  }
127570
127794
  /**
127571
127795
  * Issue #254: Validate no function calls in condition (E0702).
@@ -127709,7 +127933,7 @@ var CodeGenerator = class _CodeGenerator {
127709
127933
  }
127710
127934
  /** Generate parameter list for function signature */
127711
127935
  generateParameterList(ctx) {
127712
- return ctx.parameter().map((p) => this.generateParameter(p)).join(", ");
127936
+ return ctx.parameter().map((p, index) => this.generateParameter(p, index)).join(", ");
127713
127937
  }
127714
127938
  /** Get the raw type name without C conversion */
127715
127939
  getTypeName(ctx) {
@@ -127951,8 +128175,8 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
127951
128175
  }
127952
128176
  /**
127953
128177
  * Issue #561: Analyze modifications in a parse tree without full code generation.
127954
- * Used by Pipeline.transpileSource() to collect modification info from includes
127955
- * for cross-file const inference (unified with Pipeline.run() behavior).
128178
+ * Used by the transpile() pipeline to collect modification info from includes
128179
+ * for cross-file const inference.
127956
128180
  *
127957
128181
  * Issue #565: Now accepts optional cross-file data for transitive propagation.
127958
128182
  * When a file calls a function from an included file that modifies its param,
@@ -129333,6 +129557,9 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
129333
129557
  return "{}";
129334
129558
  }
129335
129559
  const fieldInits = fields.map((f) => `.${f.fieldName} = ${f.value}`);
129560
+ if (CodeGenState.cppMode && (typeName.startsWith("struct {") || typeName.startsWith("union {"))) {
129561
+ return `{ ${fieldInits.join(", ")} }`;
129562
+ }
129336
129563
  return `(${castType}){ ${fieldInits.join(", ")} }`;
129337
129564
  }
129338
129565
  /**
@@ -129452,13 +129679,17 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
129452
129679
  const typedef = this.generateCallbackTypedef(name);
129453
129680
  return typedef ? functionCode + typedef : functionCode;
129454
129681
  }
129455
- generateParameter(ctx) {
129682
+ generateParameter(ctx, paramIndex) {
129456
129683
  const typeName = this.getTypeName(ctx.type());
129457
129684
  const name = ctx.IDENTIFIER().getText();
129458
129685
  this._validateCStyleArrayParam(ctx, typeName, name);
129459
129686
  this._validateUnboundedArrayParam(ctx);
129460
129687
  const isModified = this._isCurrentParameterModified(name);
129461
- const isPassByValue = this._isPassByValueType(typeName, name);
129688
+ const callbackInfo = paramIndex === void 0 ? null : FunctionContextManager_default.getCallbackTypedefParamInfo(paramIndex);
129689
+ const isPassByValue = callbackInfo ? !callbackInfo.shouldBePointer : this._isPassByValueType(typeName, name);
129690
+ const isCallbackCompatible = callbackInfo !== null;
129691
+ const forcePassByReference = callbackInfo?.shouldBePointer ?? false;
129692
+ const forceConst = callbackInfo?.shouldBeConst ?? false;
129462
129693
  const input = ParameterInputAdapter_default.fromAST(ctx, {
129463
129694
  getTypeName: (t) => this.getTypeName(t),
129464
129695
  generateType: (t) => this.generateType(t),
@@ -129467,7 +129698,10 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
129467
129698
  isKnownStruct: (t) => this.isKnownStruct(t),
129468
129699
  typeMap: TYPE_MAP_default,
129469
129700
  isModified,
129470
- isPassByValue
129701
+ isPassByValue,
129702
+ isCallbackCompatible,
129703
+ forcePassByReference,
129704
+ forceConst
129471
129705
  });
129472
129706
  return ParameterSignatureBuilder_default.build(input, CppModeHelper_default.refOrPtr());
129473
129707
  }
@@ -129516,6 +129750,11 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
129516
129750
  )) {
129517
129751
  return true;
129518
129752
  }
129753
+ if (CodeGenState.currentFunctionName && CodeGenState.callbackCompatibleFunctions.has(
129754
+ CodeGenState.currentFunctionName
129755
+ ) && this.isKnownStruct(typeName)) {
129756
+ return true;
129757
+ }
129519
129758
  return false;
129520
129759
  }
129521
129760
  // ========================================================================
@@ -129800,14 +130039,18 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
129800
130039
  const isStructParam = paramInfo?.isStruct ?? false;
129801
130040
  const isCppAccess = hasGlobal && this.isCppScopeSymbol(firstId);
129802
130041
  const separatorDeps = this._buildMemberSeparatorDeps();
130042
+ const forcePointerSemantics = paramInfo?.forcePointerSemantics ?? false;
129803
130043
  const separatorCtx = MemberSeparatorResolver_default.buildContext(
129804
- firstId,
129805
- hasGlobal,
129806
- hasThis,
129807
- CodeGenState.currentScope,
129808
- isStructParam,
129809
- separatorDeps,
129810
- isCppAccess
130044
+ {
130045
+ firstId,
130046
+ hasGlobal,
130047
+ hasThis,
130048
+ currentScope: CodeGenState.currentScope,
130049
+ isStructParam,
130050
+ isCppAccess,
130051
+ forcePointerSemantics
130052
+ },
130053
+ separatorDeps
129811
130054
  );
129812
130055
  return {
129813
130056
  generateExpression: (expr) => this.generateExpression(expr),
@@ -130728,8 +130971,11 @@ var HeaderGeneratorUtils = class _HeaderGeneratorUtils {
130728
130971
  lines.push(include);
130729
130972
  }
130730
130973
  }
130974
+ const userIncludeSet = new Set(options.userIncludes ?? []);
130731
130975
  for (const directive of headersToInclude) {
130732
- lines.push(directive);
130976
+ if (!userIncludeSet.has(directive)) {
130977
+ lines.push(directive);
130978
+ }
130733
130979
  }
130734
130980
  const hasIncludes = options.includeSystemHeaders !== false || options.userIncludes && options.userIncludes.length > 0 || headersToInclude.size > 0;
130735
130981
  if (hasIncludes) {
@@ -133342,8 +133588,9 @@ var CResolver = class _CResolver {
133342
133588
  if (!name) continue;
133343
133589
  const isFunction = DeclaratorUtils_default.declaratorIsFunction(declarator);
133344
133590
  if (ctx.isTypedef) {
133591
+ const typedefType = _CResolver.isFunctionPointerDeclarator(declarator) ? `${baseType} (*)(${_CResolver.extractParamText(declarator)})` : baseType;
133345
133592
  ctx.symbols.push(
133346
- TypedefCollector_default.collect(name, baseType, ctx.sourceFile, ctx.line)
133593
+ TypedefCollector_default.collect(name, typedefType, ctx.sourceFile, ctx.line)
133347
133594
  );
133348
133595
  } else if (isFunction) {
133349
133596
  ctx.symbols.push(
@@ -133446,6 +133693,36 @@ var CResolver = class _CResolver {
133446
133693
  }
133447
133694
  return typeParts.join(" ") || "int";
133448
133695
  }
133696
+ /**
133697
+ * Check if a declarator represents a function pointer.
133698
+ * For `(*PointCallback)(Point p)`, the C grammar parses as:
133699
+ * declarator -> directDeclarator
133700
+ * directDeclarator -> directDeclarator '(' parameterTypeList ')'
133701
+ * inner directDeclarator -> '(' declarator ')'
133702
+ * inner declarator -> pointer directDeclarator -> * PointCallback
133703
+ */
133704
+ static isFunctionPointerDeclarator(declarator) {
133705
+ const directDecl = declarator.directDeclarator?.();
133706
+ if (!directDecl) return false;
133707
+ const hasParams = directDecl.parameterTypeList?.() !== null || Boolean(directDecl.LeftParen?.());
133708
+ if (!hasParams) return false;
133709
+ const innerDirectDecl = directDecl.directDeclarator?.();
133710
+ if (!innerDirectDecl) return false;
133711
+ const nestedDecl = innerDirectDecl.declarator?.();
133712
+ if (!nestedDecl) return false;
133713
+ return Boolean(nestedDecl.pointer?.());
133714
+ }
133715
+ /**
133716
+ * Extract parameter text from a function pointer declarator.
133717
+ * Returns the text of the parameters from a function pointer like "(*Callback)(Point p)".
133718
+ */
133719
+ static extractParamText(declarator) {
133720
+ const directDecl = declarator.directDeclarator?.();
133721
+ if (!directDecl) return "";
133722
+ const paramTypeList = directDecl.parameterTypeList?.();
133723
+ if (!paramTypeList) return "";
133724
+ return paramTypeList.getText();
133725
+ }
133449
133726
  };
133450
133727
  var c_default = CResolver;
133451
133728
 
@@ -135075,6 +135352,9 @@ var IncludeResolver = class _IncludeResolver {
135075
135352
  }
135076
135353
  if (file.type === EFileType_default.CNext) {
135077
135354
  result.cnextIncludes.push(file);
135355
+ const headerPath = includeInfo.path.replace(/\.cnx$|\.cnext$/, ".h");
135356
+ const directive = includeInfo.isLocal ? `#include "${headerPath}"` : `#include <${headerPath}>`;
135357
+ result.headerIncludeDirectives.set(absolutePath, directive);
135078
135358
  }
135079
135359
  }
135080
135360
  /**
@@ -135212,8 +135492,8 @@ var IncludeResolver = class _IncludeResolver {
135212
135492
  /**
135213
135493
  * Build search paths from a source file location
135214
135494
  *
135215
- * Consolidates the search path building logic used by both `run()` and
135216
- * `transpileSource()` code paths.
135495
+ * Consolidates the search path building logic used by the unified
135496
+ * transpile() entry point.
135217
135497
  *
135218
135498
  * Search order (highest to lowest priority):
135219
135499
  * 1. Source file's directory (for relative includes)
@@ -136548,16 +136828,20 @@ var FunctionCallListener = class extends CNextListener {
136548
136828
  // ========================================================================
136549
136829
  // ISR/Callback Variable Tracking (ADR-040)
136550
136830
  // ========================================================================
136831
+ /**
136832
+ * Check if a type name represents a callable type (ISR, callback, or C function pointer typedef).
136833
+ */
136834
+ isCallableType(typeName) {
136835
+ return typeName === "ISR" || this.analyzer.isCallbackType(typeName) || this.analyzer.isCFunctionPointerTypedef(typeName);
136836
+ }
136551
136837
  /**
136552
136838
  * Track ISR-typed variables from variable declarations
136553
136839
  * e.g., `ISR handler <- myFunction;`
136554
136840
  */
136555
136841
  enterVariableDeclaration = (ctx) => {
136556
- const typeCtx = ctx.type();
136557
- const typeName = typeCtx.getText();
136558
- if (typeName === "ISR" || this.analyzer.isCallbackType(typeName)) {
136559
- const varName = ctx.IDENTIFIER().getText();
136560
- this.analyzer.defineCallableVariable(varName);
136842
+ const typeName = ctx.type().getText();
136843
+ if (this.isCallableType(typeName)) {
136844
+ this.analyzer.defineCallableVariable(ctx.IDENTIFIER().getText());
136561
136845
  }
136562
136846
  };
136563
136847
  /**
@@ -136565,11 +136849,9 @@ var FunctionCallListener = class extends CNextListener {
136565
136849
  * e.g., `void execute(ISR handler) { handler(); }`
136566
136850
  */
136567
136851
  enterParameter = (ctx) => {
136568
- const typeCtx = ctx.type();
136569
- const typeName = typeCtx.getText();
136570
- if (typeName === "ISR" || this.analyzer.isCallbackType(typeName)) {
136571
- const paramName = ctx.IDENTIFIER().getText();
136572
- this.analyzer.defineCallableVariable(paramName);
136852
+ const typeName = ctx.type().getText();
136853
+ if (this.isCallableType(typeName)) {
136854
+ this.analyzer.defineCallableVariable(ctx.IDENTIFIER().getText());
136573
136855
  }
136574
136856
  };
136575
136857
  // ========================================================================
@@ -136708,6 +136990,7 @@ var FunctionCallAnalyzer = class {
136708
136990
  this.collectIncludes(tree);
136709
136991
  this.collectCallbackTypes(tree);
136710
136992
  this.collectAllLocalFunctions(tree);
136993
+ this.collectCallbackCompatibleFunctions(tree);
136711
136994
  const listener = new FunctionCallListener(this);
136712
136995
  ParseTreeWalker5.DEFAULT.walk(listener, tree);
136713
136996
  return this.errors;
@@ -136791,6 +137074,187 @@ var FunctionCallAnalyzer = class {
136791
137074
  isCallbackType(name) {
136792
137075
  return this.callbackTypes.has(name);
136793
137076
  }
137077
+ /**
137078
+ * Check if a type name is a C function pointer typedef.
137079
+ * Looks up the type in the symbol table and checks if it's a typedef
137080
+ * whose underlying type contains "(*)" indicating a function pointer.
137081
+ */
137082
+ isCFunctionPointerTypedef(typeName) {
137083
+ if (!this.symbolTable) return false;
137084
+ const sym = this.symbolTable.getCSymbol(typeName);
137085
+ if (sym?.kind !== "type") return false;
137086
+ return "type" in sym && typeof sym.type === "string" && sym.type.includes("(*)");
137087
+ }
137088
+ /**
137089
+ * Detect functions assigned to C function pointer typedefs.
137090
+ * When `PointCallback cb <- my_handler;` is found and PointCallback
137091
+ * is a C function pointer typedef, mark my_handler as callback-compatible.
137092
+ */
137093
+ collectCallbackCompatibleFunctions(tree) {
137094
+ for (const decl of tree.declaration()) {
137095
+ const funcDecl = decl.functionDeclaration();
137096
+ if (!funcDecl) continue;
137097
+ const block = funcDecl.block();
137098
+ if (!block) continue;
137099
+ this.scanBlockForCallbackAssignments(block);
137100
+ }
137101
+ }
137102
+ /**
137103
+ * Recursively scan all statements in a block for callback typedef assignments.
137104
+ */
137105
+ scanBlockForCallbackAssignments(block) {
137106
+ for (const stmt of block.statement()) {
137107
+ this.scanStatementForCallbackAssignments(stmt);
137108
+ }
137109
+ }
137110
+ /**
137111
+ * Scan a single statement for callback typedef assignments,
137112
+ * recursing into nested blocks (if/while/for/do-while/switch/critical).
137113
+ */
137114
+ scanStatementForCallbackAssignments(stmt) {
137115
+ const varDecl = stmt.variableDeclaration();
137116
+ if (varDecl) {
137117
+ this.checkVarDeclForCallbackAssignment(varDecl);
137118
+ return;
137119
+ }
137120
+ const exprStmt = stmt.expressionStatement();
137121
+ if (exprStmt) {
137122
+ this.checkExpressionForCallbackArgs(exprStmt.expression());
137123
+ return;
137124
+ }
137125
+ const ifStmt = stmt.ifStatement();
137126
+ if (ifStmt) {
137127
+ for (const child of ifStmt.statement()) {
137128
+ this.scanStatementForCallbackAssignments(child);
137129
+ }
137130
+ return;
137131
+ }
137132
+ const whileStmt = stmt.whileStatement();
137133
+ if (whileStmt) {
137134
+ this.scanStatementForCallbackAssignments(whileStmt.statement());
137135
+ return;
137136
+ }
137137
+ const forStmt = stmt.forStatement();
137138
+ if (forStmt) {
137139
+ this.scanStatementForCallbackAssignments(forStmt.statement());
137140
+ return;
137141
+ }
137142
+ const doWhileStmt = stmt.doWhileStatement();
137143
+ if (doWhileStmt) {
137144
+ this.scanBlockForCallbackAssignments(doWhileStmt.block());
137145
+ return;
137146
+ }
137147
+ const switchStmt = stmt.switchStatement();
137148
+ if (switchStmt) {
137149
+ for (const caseCtx of switchStmt.switchCase()) {
137150
+ this.scanBlockForCallbackAssignments(caseCtx.block());
137151
+ }
137152
+ const defaultCtx = switchStmt.defaultCase();
137153
+ if (defaultCtx) {
137154
+ this.scanBlockForCallbackAssignments(defaultCtx.block());
137155
+ }
137156
+ return;
137157
+ }
137158
+ const criticalStmt = stmt.criticalStatement();
137159
+ if (criticalStmt) {
137160
+ this.scanBlockForCallbackAssignments(criticalStmt.block());
137161
+ return;
137162
+ }
137163
+ const nestedBlock = stmt.block();
137164
+ if (nestedBlock) {
137165
+ this.scanBlockForCallbackAssignments(nestedBlock);
137166
+ }
137167
+ }
137168
+ /**
137169
+ * Check if a variable declaration assigns a function to a C callback typedef.
137170
+ */
137171
+ checkVarDeclForCallbackAssignment(varDecl) {
137172
+ const typeName = varDecl.type().getText();
137173
+ if (!this.isCFunctionPointerTypedef(typeName)) return;
137174
+ const expr = varDecl.expression();
137175
+ if (!expr) return;
137176
+ const funcRef = this.extractFunctionReference(expr);
137177
+ if (!funcRef) return;
137178
+ const lookupName = funcRef.includes(".") ? funcRef.replace(".", "_") : funcRef;
137179
+ if (this.allLocalFunctions.has(lookupName)) {
137180
+ CodeGenState.callbackCompatibleFunctions.set(lookupName, typeName);
137181
+ }
137182
+ }
137183
+ /**
137184
+ * Extract a function reference from an expression context.
137185
+ * Matches bare identifiers (e.g., "my_handler") and qualified scope
137186
+ * names (e.g., "MyScope.handler").
137187
+ * Returns null if the expression is not a function reference.
137188
+ */
137189
+ extractFunctionReference(expr) {
137190
+ const text = expr.getText();
137191
+ if (/^\w+(\.\w+)?$/.test(text)) {
137192
+ return text;
137193
+ }
137194
+ return null;
137195
+ }
137196
+ /**
137197
+ * Issue #895: Check expression for function calls that pass C-Next functions
137198
+ * to C function pointer parameters.
137199
+ *
137200
+ * Pattern: `global.widget_set_flush_cb(w, my_flush)`
137201
+ * Where widget_set_flush_cb's 2nd param is a C function pointer typedef.
137202
+ */
137203
+ checkExpressionForCallbackArgs(expr) {
137204
+ const postfix = this.findPostfixExpression(expr);
137205
+ if (!postfix) return;
137206
+ const callInfo = this.extractCallInfo(postfix);
137207
+ if (!callInfo) return;
137208
+ const cFunc = this.symbolTable?.getCSymbol(callInfo.funcName);
137209
+ if (cFunc?.kind !== "function" || !cFunc.parameters) return;
137210
+ for (let i = 0; i < callInfo.args.length; i++) {
137211
+ const param = cFunc.parameters[i];
137212
+ if (!param) continue;
137213
+ if (!this.isCFunctionPointerTypedef(param.type)) continue;
137214
+ const funcRef = this.extractFunctionReference(callInfo.args[i]);
137215
+ if (!funcRef) continue;
137216
+ const lookupName = funcRef.includes(".") ? funcRef.replace(".", "_") : funcRef;
137217
+ if (this.allLocalFunctions.has(lookupName)) {
137218
+ CodeGenState.callbackCompatibleFunctions.set(lookupName, param.type);
137219
+ }
137220
+ }
137221
+ }
137222
+ /**
137223
+ * Find the postfix expression within an expression tree.
137224
+ * Uses ExpressionUnwrapper which validates that expression is "simple"
137225
+ * (single term at each level), returning null for complex expressions.
137226
+ */
137227
+ findPostfixExpression(expr) {
137228
+ return ExpressionUnwrapper_default.getPostfixExpression(expr);
137229
+ }
137230
+ /**
137231
+ * Extract function name and arguments from a postfix expression.
137232
+ * Returns null if not a function call.
137233
+ */
137234
+ extractCallInfo(postfix) {
137235
+ const primary = postfix.primaryExpression();
137236
+ const ops = postfix.postfixOp();
137237
+ const ident = primary.IDENTIFIER();
137238
+ const globalKw = primary.GLOBAL();
137239
+ if (!ident && !globalKw) {
137240
+ return null;
137241
+ }
137242
+ let funcName = ident ? ident.getText() : "";
137243
+ let argListOp = null;
137244
+ for (const op of ops) {
137245
+ if (op.IDENTIFIER()) {
137246
+ const member = op.IDENTIFIER().getText();
137247
+ funcName = funcName ? `${funcName}_${member}` : member;
137248
+ } else if (op.argumentList() || op.getText().startsWith("(")) {
137249
+ argListOp = op;
137250
+ break;
137251
+ }
137252
+ }
137253
+ if (!argListOp || !funcName) return null;
137254
+ const argList = argListOp.argumentList();
137255
+ const args = argList?.expression() ?? [];
137256
+ return { funcName, args };
137257
+ }
136794
137258
  /**
136795
137259
  * ADR-040: Register a variable that holds a callable (ISR or callback)
136796
137260
  */
@@ -137044,6 +137508,26 @@ var TypeConstants = class {
137044
137508
  "float",
137045
137509
  "double"
137046
137510
  ];
137511
+ /**
137512
+ * Unsigned integer types valid as array/bit subscript indexes.
137513
+ *
137514
+ * Used by:
137515
+ * - ArrayIndexTypeAnalyzer: validating subscript index types
137516
+ */
137517
+ static UNSIGNED_INDEX_TYPES = [
137518
+ "u8",
137519
+ "u16",
137520
+ "u32",
137521
+ "u64",
137522
+ "bool"
137523
+ ];
137524
+ /**
137525
+ * Signed integer types (rejected as subscript indexes).
137526
+ *
137527
+ * Used by:
137528
+ * - ArrayIndexTypeAnalyzer: detecting signed integer subscript indexes
137529
+ */
137530
+ static SIGNED_TYPES = ["i8", "i16", "i32", "i64"];
137047
137531
  };
137048
137532
  var TypeConstants_default = TypeConstants;
137049
137533
 
@@ -137161,6 +137645,212 @@ var FloatModuloAnalyzer = class {
137161
137645
  };
137162
137646
  var FloatModuloAnalyzer_default = FloatModuloAnalyzer;
137163
137647
 
137648
+ // src/transpiler/logic/analysis/ArrayIndexTypeAnalyzer.ts
137649
+ import { ParseTreeWalker as ParseTreeWalker8 } from "antlr4ng";
137650
+ var VariableTypeCollector = class extends CNextListener {
137651
+ varTypes = /* @__PURE__ */ new Map();
137652
+ getVarTypes() {
137653
+ return this.varTypes;
137654
+ }
137655
+ trackType(typeCtx, identifier) {
137656
+ if (!typeCtx || !identifier) return;
137657
+ this.varTypes.set(identifier.getText(), typeCtx.getText());
137658
+ }
137659
+ enterVariableDeclaration = (ctx) => {
137660
+ this.trackType(ctx.type(), ctx.IDENTIFIER());
137661
+ };
137662
+ enterParameter = (ctx) => {
137663
+ this.trackType(ctx.type(), ctx.IDENTIFIER());
137664
+ };
137665
+ enterForVarDecl = (ctx) => {
137666
+ this.trackType(ctx.type(), ctx.IDENTIFIER());
137667
+ };
137668
+ };
137669
+ var IndexTypeListener = class extends CNextListener {
137670
+ analyzer;
137671
+ // eslint-disable-next-line @typescript-eslint/lines-between-class-members
137672
+ varTypes;
137673
+ constructor(analyzer, varTypes) {
137674
+ super();
137675
+ this.analyzer = analyzer;
137676
+ this.varTypes = varTypes;
137677
+ }
137678
+ /**
137679
+ * Check postfix operations in expressions (RHS: arr[idx], flags[bit])
137680
+ */
137681
+ enterPostfixOp = (ctx) => {
137682
+ if (!ctx.LBRACKET()) return;
137683
+ const expressions = ctx.expression();
137684
+ for (const expr of expressions) {
137685
+ this.validateIndexExpression(expr);
137686
+ }
137687
+ };
137688
+ /**
137689
+ * Check postfix target operations in assignments (LHS: arr[idx] <- val)
137690
+ */
137691
+ enterPostfixTargetOp = (ctx) => {
137692
+ if (!ctx.LBRACKET()) return;
137693
+ const expressions = ctx.expression();
137694
+ for (const expr of expressions) {
137695
+ this.validateIndexExpression(expr);
137696
+ }
137697
+ };
137698
+ /**
137699
+ * Validate that a subscript index expression uses an unsigned integer type.
137700
+ * Collects all leaf operands from the expression and checks each one.
137701
+ */
137702
+ validateIndexExpression(ctx) {
137703
+ const operands = this.collectOperands(ctx);
137704
+ for (const operand of operands) {
137705
+ const resolvedType = this.resolveOperandType(operand);
137706
+ if (!resolvedType) continue;
137707
+ if (TypeConstants_default.SIGNED_TYPES.includes(resolvedType)) {
137708
+ const { line: line2, column: column2 } = ParserUtils_default.getPosition(ctx);
137709
+ this.analyzer.addError(line2, column2, "E0850", resolvedType);
137710
+ return;
137711
+ }
137712
+ if (resolvedType === "float literal" || TypeConstants_default.FLOAT_TYPES.includes(resolvedType)) {
137713
+ const { line: line2, column: column2 } = ParserUtils_default.getPosition(ctx);
137714
+ this.analyzer.addError(line2, column2, "E0851", resolvedType);
137715
+ return;
137716
+ }
137717
+ if (TypeConstants_default.UNSIGNED_INDEX_TYPES.includes(resolvedType)) {
137718
+ continue;
137719
+ }
137720
+ const { line, column } = ParserUtils_default.getPosition(ctx);
137721
+ this.analyzer.addError(line, column, "E0852", resolvedType);
137722
+ return;
137723
+ }
137724
+ }
137725
+ /**
137726
+ * Collect all leaf unary expression operands from an expression tree.
137727
+ * Handles binary operators at any level by flatMapping through the grammar hierarchy.
137728
+ */
137729
+ collectOperands(ctx) {
137730
+ const ternary = ctx.ternaryExpression();
137731
+ if (!ternary) return [];
137732
+ const orExpressions = ternary.orExpression();
137733
+ if (orExpressions.length === 0) return [];
137734
+ const valueExpressions = orExpressions.length === 3 ? orExpressions.slice(1) : orExpressions;
137735
+ 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());
137736
+ }
137737
+ /**
137738
+ * Resolve the type of a unary expression operand.
137739
+ * Uses local varTypes first, then falls back to CodeGenState for
137740
+ * struct fields, function return types, and enum detection.
137741
+ *
137742
+ * Returns null if the type cannot be resolved (pass-through).
137743
+ */
137744
+ resolveOperandType(operand) {
137745
+ const postfixExpr = operand.postfixExpression();
137746
+ if (!postfixExpr) return null;
137747
+ const primaryExpr = postfixExpr.primaryExpression();
137748
+ if (!primaryExpr) return null;
137749
+ let currentType = this.resolveBaseType(primaryExpr);
137750
+ const postfixOps = postfixExpr.postfixOp();
137751
+ if (!currentType && postfixOps.length > 0) {
137752
+ const identifier = primaryExpr.IDENTIFIER();
137753
+ if (identifier) {
137754
+ currentType = identifier.getText();
137755
+ }
137756
+ }
137757
+ for (const op of postfixOps) {
137758
+ if (!currentType) return null;
137759
+ currentType = this.resolvePostfixOpType(currentType, op);
137760
+ }
137761
+ return currentType;
137762
+ }
137763
+ /**
137764
+ * Resolve the base type of a primary expression.
137765
+ */
137766
+ resolveBaseType(primaryExpr) {
137767
+ const literal = primaryExpr.literal();
137768
+ if (literal) {
137769
+ if (LiteralUtils_default.isFloat(literal)) return "float literal";
137770
+ return null;
137771
+ }
137772
+ const parenExpr = primaryExpr.expression();
137773
+ if (parenExpr) {
137774
+ const innerOperands = this.collectOperands(parenExpr);
137775
+ for (const innerOp of innerOperands) {
137776
+ const innerType = this.resolveOperandType(innerOp);
137777
+ if (innerType) return innerType;
137778
+ }
137779
+ return null;
137780
+ }
137781
+ const identifier = primaryExpr.IDENTIFIER();
137782
+ if (!identifier) return null;
137783
+ const varName = identifier.getText();
137784
+ const localType = this.varTypes.get(varName);
137785
+ if (localType) return localType;
137786
+ const typeInfo = CodeGenState.getVariableTypeInfo(varName);
137787
+ if (typeInfo) return typeInfo.baseType;
137788
+ return null;
137789
+ }
137790
+ /**
137791
+ * Resolve the resulting type after applying a postfix operator.
137792
+ */
137793
+ resolvePostfixOpType(currentType, op) {
137794
+ if (op.DOT()) {
137795
+ const fieldId = op.IDENTIFIER();
137796
+ if (!fieldId) return null;
137797
+ const fieldName = fieldId.getText();
137798
+ if (CodeGenState.isKnownEnum(currentType)) return null;
137799
+ const fieldType = CodeGenState.getStructFieldType(currentType, fieldName);
137800
+ return fieldType ?? null;
137801
+ }
137802
+ if (op.LBRACKET()) {
137803
+ if (TypeConstants_default.UNSIGNED_INDEX_TYPES.includes(currentType)) {
137804
+ return "bool";
137805
+ }
137806
+ if (TypeConstants_default.SIGNED_TYPES.includes(currentType)) {
137807
+ return "bool";
137808
+ }
137809
+ return currentType;
137810
+ }
137811
+ if (op.LPAREN()) {
137812
+ const returnType = CodeGenState.getFunctionReturnType(currentType);
137813
+ return returnType ?? null;
137814
+ }
137815
+ return null;
137816
+ }
137817
+ };
137818
+ var ArrayIndexTypeAnalyzer = class {
137819
+ errors = [];
137820
+ /**
137821
+ * Analyze the parse tree for invalid subscript index types
137822
+ */
137823
+ analyze(tree) {
137824
+ this.errors = [];
137825
+ const collector = new VariableTypeCollector();
137826
+ ParseTreeWalker8.DEFAULT.walk(collector, tree);
137827
+ const varTypes = collector.getVarTypes();
137828
+ const listener = new IndexTypeListener(this, varTypes);
137829
+ ParseTreeWalker8.DEFAULT.walk(listener, tree);
137830
+ return this.errors;
137831
+ }
137832
+ /**
137833
+ * Add an index type error
137834
+ */
137835
+ addError(line, column, code, actualType) {
137836
+ this.errors.push({
137837
+ code,
137838
+ line,
137839
+ column,
137840
+ actualType,
137841
+ message: `Subscript index must be an unsigned integer type; got '${actualType}'`,
137842
+ helpText: "Use an unsigned integer type (u8, u16, u32, u64) for array and bit subscript indexes."
137843
+ });
137844
+ }
137845
+ /**
137846
+ * Get all detected errors
137847
+ */
137848
+ getErrors() {
137849
+ return this.errors;
137850
+ }
137851
+ };
137852
+ var ArrayIndexTypeAnalyzer_default = ArrayIndexTypeAnalyzer;
137853
+
137164
137854
  // src/transpiler/logic/analysis/runAnalyzers.ts
137165
137855
  function collectErrors(analyzerErrors, target, formatMessage) {
137166
137856
  const formatter = formatMessage ?? ((e) => e.message);
@@ -137214,6 +137904,10 @@ function runAnalyzers(tree, tokenStream, options) {
137214
137904
  if (collectErrors(floatModAnalyzer.analyze(tree), errors, formatWithCode)) {
137215
137905
  return errors;
137216
137906
  }
137907
+ const indexTypeAnalyzer = new ArrayIndexTypeAnalyzer_default();
137908
+ if (collectErrors(indexTypeAnalyzer.analyze(tree), errors, formatWithCode)) {
137909
+ return errors;
137910
+ }
137217
137911
  const commentExtractor = new CommentExtractor_default(tokenStream);
137218
137912
  collectErrors(
137219
137913
  commentExtractor.validate(),
@@ -137821,7 +138515,7 @@ var TransitiveEnumCollector = class {
137821
138515
  /**
137822
138516
  * Collect symbol info for standalone mode from resolved includes.
137823
138517
  *
137824
- * Issue #591: Extracted from Transpiler.transpileSource() to unify enum collection.
138518
+ * Issue #591: Extracted to unify enum collection across transpilation modes.
137825
138519
  * Unlike collect() which starts from a file path and parses it, this method
137826
138520
  * starts from already-resolved includes (from IncludeResolver.resolve()).
137827
138521
  *
@@ -137899,83 +138593,53 @@ var Transpiler = class {
137899
138593
  this.cacheManager = projectRoot ? new CacheManager_default(projectRoot, this.fs) : null;
137900
138594
  }
137901
138595
  // ===========================================================================
137902
- // Public API: run() and transpileSource()
138596
+ // Public API
137903
138597
  // ===========================================================================
137904
138598
  /**
137905
- * Execute the unified pipeline from CLI inputs.
138599
+ * Unified entry point for all transpilation.
137906
138600
  *
137907
- * Stage 1 (file discovery) happens here, then delegates to _executePipeline().
138601
+ * @param input - What to transpile:
138602
+ * - { kind: 'files' } — discover from config.inputs, write to disk
138603
+ * - { kind: 'source', source, ... } — transpile in-memory source
138604
+ * @returns ITranspilerResult with per-file results in .files[]
137908
138605
  */
137909
- async run() {
138606
+ async transpile(input) {
137910
138607
  const result = this._initResult();
137911
138608
  try {
137912
138609
  await this._initializeRun();
137913
- const { cnextFiles, headerFiles } = await this.discoverSources();
137914
- if (cnextFiles.length === 0) {
138610
+ const pipelineInput = await this.discoverIncludes(input);
138611
+ if (pipelineInput.cnextFiles.length === 0) {
137915
138612
  return this._finalizeResult(result, "No C-Next source files found");
137916
138613
  }
137917
- this._ensureOutputDirectories();
137918
- const pipelineFiles = cnextFiles.map((f) => ({
137919
- path: f.path,
137920
- discoveredFile: f
137921
- }));
137922
- const input = {
137923
- cnextFiles: pipelineFiles,
137924
- headerFiles,
137925
- writeOutputToDisk: true
137926
- };
137927
- await this._executePipeline(input, result);
138614
+ if (input.kind === "files") {
138615
+ this._ensureOutputDirectories();
138616
+ }
138617
+ await this._executePipeline(pipelineInput, result);
137928
138618
  return await this._finalizeResult(result);
137929
138619
  } catch (err) {
137930
138620
  return this._handleRunError(result, err);
137931
138621
  }
137932
138622
  }
137933
138623
  /**
137934
- * Transpile source code provided as a string.
138624
+ * Stage 1: Discover files and build pipeline input.
137935
138625
  *
137936
- * Discovers includes from the source, builds an IPipelineInput, and
137937
- * delegates to the same _executePipeline() as run().
138626
+ * Branches on input kind:
138627
+ * - 'files': filesystem scan, dependency graph, topological sort
138628
+ * - 'source': parse in-memory string, walk include tree
137938
138629
  *
137939
- * @param source - The C-Next source code as a string
137940
- * @param options - Options for transpilation
137941
- * @returns Promise<IFileResult> with generated code or errors
137942
- */
137943
- async transpileSource(source, options) {
137944
- const workingDir = options?.workingDir ?? process.cwd();
137945
- const additionalIncludeDirs = options?.includeDirs ?? [];
137946
- const sourcePath = options?.sourcePath ?? "<string>";
137947
- try {
137948
- await this._initializeRun();
137949
- const input = this._discoverFromSource(
137950
- source,
137951
- workingDir,
137952
- additionalIncludeDirs,
137953
- sourcePath
137954
- );
137955
- const result = this._initResult();
137956
- await this._executePipeline(input, result);
137957
- const fileResult = result.files.find((f) => f.sourcePath === sourcePath);
137958
- if (fileResult) {
137959
- return fileResult;
137960
- }
137961
- if (result.errors.length > 0) {
137962
- return this.buildErrorResult(sourcePath, result.errors, 0);
137963
- }
137964
- return this.buildErrorResult(
137965
- sourcePath,
137966
- [
137967
- {
137968
- line: 1,
137969
- column: 0,
137970
- message: "Pipeline produced no result for source file",
137971
- severity: "error"
137972
- }
137973
- ],
137974
- 0
137975
- );
137976
- } catch (err) {
137977
- return this.buildCatchResult(sourcePath, err);
137978
- }
138630
+ * Header directive storage happens via IncludeResolver.resolve() for both
138631
+ * C headers and cnext includes (Issue #854).
138632
+ */
138633
+ async discoverIncludes(input) {
138634
+ if (input.kind === "files") {
138635
+ return this._discoverFromFiles();
138636
+ }
138637
+ return this._discoverFromSource(
138638
+ input.source,
138639
+ input.workingDir ?? process.cwd(),
138640
+ input.includeDirs ?? [],
138641
+ input.sourcePath ?? "<string>"
138642
+ );
137979
138643
  }
137980
138644
  // ===========================================================================
137981
138645
  // Unified Pipeline
@@ -137983,7 +138647,7 @@ var Transpiler = class {
137983
138647
  /**
137984
138648
  * The single unified pipeline for all transpilation.
137985
138649
  *
137986
- * Both run() and transpileSource() delegate here after file discovery.
138650
+ * transpile() delegates here after file discovery via discoverIncludes().
137987
138651
  *
137988
138652
  * Stage 2: Collect symbols from C/C++ headers (includes building analyzer context)
137989
138653
  * Stage 3: Collect symbols from C-Next files
@@ -138090,14 +138754,6 @@ var Transpiler = class {
138090
138754
  if (this.config.parseOnly) {
138091
138755
  return this.buildParseOnlyResult(sourcePath, declarationCount);
138092
138756
  }
138093
- const analyzerErrors = runAnalyzers_default(tree, tokenStream);
138094
- if (analyzerErrors.length > 0) {
138095
- return this.buildErrorResult(
138096
- sourcePath,
138097
- analyzerErrors,
138098
- declarationCount
138099
- );
138100
- }
138101
138757
  const tSymbols = cnext_default.resolve(tree, sourcePath);
138102
138758
  let symbolInfo = TSymbolInfoAdapter_default.convert(tSymbols);
138103
138759
  const externalEnumSources = this._collectExternalEnumSources(
@@ -138110,6 +138766,15 @@ var Transpiler = class {
138110
138766
  externalEnumSources
138111
138767
  );
138112
138768
  }
138769
+ CodeGenState.symbols = symbolInfo;
138770
+ const analyzerErrors = runAnalyzers_default(tree, tokenStream);
138771
+ if (analyzerErrors.length > 0) {
138772
+ return this.buildErrorResult(
138773
+ sourcePath,
138774
+ analyzerErrors,
138775
+ declarationCount
138776
+ );
138777
+ }
138113
138778
  this._setupCrossFileModifications();
138114
138779
  const code = this.codeGenerator.generate(tree, tokenStream, {
138115
138780
  debugMode: this.config.debugMode,
@@ -138127,15 +138792,7 @@ var Transpiler = class {
138127
138792
  if (this.cppDetected) {
138128
138793
  this._accumulateFileModifications();
138129
138794
  }
138130
- const fileSymbols = CodeGenState.symbolTable.getTSymbolsByFile(sourcePath);
138131
- const headerCode = this.generateHeaderContent(
138132
- fileSymbols,
138133
- sourcePath,
138134
- this.cppDetected,
138135
- userIncludes,
138136
- passByValueCopy,
138137
- symbolInfo
138138
- );
138795
+ const headerCode = this.generateHeaderForFile(file) ?? void 0;
138139
138796
  return this.buildSuccessResult(
138140
138797
  sourcePath,
138141
138798
  code,
@@ -138200,6 +138857,13 @@ var Transpiler = class {
138200
138857
  this.state.setHeaderDirective(header.path, directive);
138201
138858
  }
138202
138859
  }
138860
+ for (const cnxInclude of resolved.cnextIncludes) {
138861
+ const includePath = resolve12(cnxInclude.path);
138862
+ const directive = resolved.headerIncludeDirectives.get(includePath);
138863
+ if (directive) {
138864
+ this.state.setHeaderDirective(includePath, directive);
138865
+ }
138866
+ }
138203
138867
  const cnextIncludeFiles = [];
138204
138868
  IncludeTreeWalker_default.walk(
138205
138869
  resolved.cnextIncludes,
@@ -138257,6 +138921,7 @@ var Transpiler = class {
138257
138921
  this.state.reset();
138258
138922
  CodeGenState.symbolTable.clear();
138259
138923
  SymbolRegistry_default.reset();
138924
+ CodeGenState.callbackCompatibleFunctions = /* @__PURE__ */ new Map();
138260
138925
  }
138261
138926
  /**
138262
138927
  * Ensure output directories exist
@@ -138335,8 +139000,12 @@ var Transpiler = class {
138335
139000
  if (file.symbolOnly) {
138336
139001
  continue;
138337
139002
  }
138338
- const headerPath = this.generateHeader(file.discoveredFile);
138339
- if (headerPath) {
139003
+ const headerContent = this.generateHeaderForFile(file);
139004
+ if (headerContent) {
139005
+ const headerPath = this.pathResolver.getHeaderOutputPath(
139006
+ file.discoveredFile
139007
+ );
139008
+ this.fs.writeFile(headerPath, headerContent);
138340
139009
  result.outputFiles.push(headerPath);
138341
139010
  }
138342
139011
  }
@@ -138370,7 +139039,7 @@ var Transpiler = class {
138370
139039
  return result;
138371
139040
  }
138372
139041
  // ===========================================================================
138373
- // Source Discovery (Stage 1 for run())
139042
+ // File Discovery (Stage 1 for files mode)
138374
139043
  // ===========================================================================
138375
139044
  /**
138376
139045
  * Discover C-Next files from a single input (file or directory).
@@ -138431,6 +139100,10 @@ var Transpiler = class {
138431
139100
  ""
138432
139101
  );
138433
139102
  depGraph.addDependency(cnxPath, includePath);
139103
+ const directive = resolved.headerIncludeDirectives.get(includePath);
139104
+ if (directive) {
139105
+ this.state.setHeaderDirective(includePath, directive);
139106
+ }
138434
139107
  const alreadyExists = cnextBaseNames.has(includeBaseName) || cnextFiles.some((f) => resolve12(f.path) === includePath);
138435
139108
  if (!alreadyExists) {
138436
139109
  cnextFiles.push(cnxInclude);
@@ -138494,14 +139167,14 @@ var Transpiler = class {
138494
139167
  * This ensures headers are found based on what the source actually
138495
139168
  * includes, not by blindly scanning include directories.
138496
139169
  */
138497
- async discoverSources() {
139170
+ async _discoverFromFiles() {
138498
139171
  const cnextFiles = [];
138499
139172
  const fileByPath = /* @__PURE__ */ new Map();
138500
139173
  for (const input of this.config.inputs) {
138501
139174
  this._discoverCNextFromInput(input, cnextFiles, fileByPath);
138502
139175
  }
138503
139176
  if (cnextFiles.length === 0) {
138504
- return { cnextFiles: [], headerFiles: [] };
139177
+ return { cnextFiles: [], headerFiles: [], writeOutputToDisk: true };
138505
139178
  }
138506
139179
  const headerSet = /* @__PURE__ */ new Map();
138507
139180
  const depGraph = new DependencyGraph_default();
@@ -138529,9 +139202,14 @@ var Transpiler = class {
138529
139202
  }
138530
139203
  );
138531
139204
  this.warnings.push(...headerWarnings);
139205
+ const pipelineFiles = sortedCnextFiles.map((f) => ({
139206
+ path: f.path,
139207
+ discoveredFile: f
139208
+ }));
138532
139209
  return {
138533
- cnextFiles: sortedCnextFiles,
138534
- headerFiles: allHeaders
139210
+ cnextFiles: pipelineFiles,
139211
+ headerFiles: allHeaders,
139212
+ writeOutputToDisk: true
138535
139213
  };
138536
139214
  }
138537
139215
  // ===========================================================================
@@ -138716,17 +139394,22 @@ var Transpiler = class {
138716
139394
  * Stage 6: Generate header file for a C-Next file
138717
139395
  * ADR-055 Phase 7: Uses TSymbol directly, converts to IHeaderSymbol for generation.
138718
139396
  */
138719
- generateHeader(file) {
138720
- const tSymbols = CodeGenState.symbolTable.getTSymbolsByFile(file.path);
139397
+ /**
139398
+ * Generate header content for a single file's exported symbols.
139399
+ * Unified method replacing both generateHeader() and generateHeaderContent().
139400
+ * Reads all needed data from state (populated during Stage 5).
139401
+ */
139402
+ generateHeaderForFile(file) {
139403
+ const sourcePath = file.path;
139404
+ const tSymbols = CodeGenState.symbolTable.getTSymbolsByFile(sourcePath);
138721
139405
  const exportedSymbols = tSymbols.filter((s) => s.isExported);
138722
139406
  if (exportedSymbols.length === 0) {
138723
139407
  return null;
138724
139408
  }
138725
- const headerName = basename5(file.path).replace(/\.cnx$|\.cnext$/, ".h");
138726
- const headerPath = this.pathResolver.getHeaderOutputPath(file);
138727
- const typeInput = this.state.getSymbolInfo(file.path);
138728
- const passByValueParams = this.state.getPassByValueParams(file.path) ?? /* @__PURE__ */ new Map();
138729
- const userIncludes = this.state.getUserIncludes(file.path);
139409
+ const headerName = basename5(sourcePath).replace(/\.cnx$|\.cnext$/, ".h");
139410
+ const typeInput = this.state.getSymbolInfo(sourcePath);
139411
+ const passByValueParams = this.state.getPassByValueParams(sourcePath) ?? /* @__PURE__ */ new Map();
139412
+ const userIncludes = this.state.getUserIncludes(sourcePath);
138730
139413
  const allKnownEnums = TransitiveEnumCollector_default.aggregateKnownEnums(
138731
139414
  this.state.getAllSymbolInfo()
138732
139415
  );
@@ -138741,7 +139424,7 @@ var Transpiler = class {
138741
139424
  unmodifiedParams,
138742
139425
  allKnownEnums
138743
139426
  );
138744
- const headerContent = this.headerGenerator.generate(
139427
+ return this.headerGenerator.generate(
138745
139428
  headerSymbols,
138746
139429
  headerName,
138747
139430
  {
@@ -138754,8 +139437,6 @@ var Transpiler = class {
138754
139437
  passByValueParams,
138755
139438
  allKnownEnums
138756
139439
  );
138757
- this.fs.writeFile(headerPath, headerContent);
138758
- return headerPath;
138759
139440
  }
138760
139441
  /**
138761
139442
  * Collect external enum sources from included C-Next files.
@@ -138788,43 +139469,6 @@ var Transpiler = class {
138788
139469
  );
138789
139470
  }
138790
139471
  }
138791
- /**
138792
- * Generate header content for exported symbols.
138793
- * Issue #591: Extracted from transpileSource() for reduced complexity.
138794
- * ADR-055 Phase 7: Works with TSymbol[], converts to IHeaderSymbol for generation.
138795
- */
138796
- generateHeaderContent(tSymbols, sourcePath, cppMode, userIncludes, passByValueParams, symbolInfo) {
138797
- const exportedSymbols = tSymbols.filter((s) => s.isExported);
138798
- if (exportedSymbols.length === 0) {
138799
- return void 0;
138800
- }
138801
- const unmodifiedParams = this.codeGenerator.getFunctionUnmodifiedParams();
138802
- const headerSymbols = this.convertToHeaderSymbols(
138803
- exportedSymbols,
138804
- unmodifiedParams,
138805
- symbolInfo.knownEnums
138806
- );
138807
- const headerName = basename5(sourcePath).replace(/\.cnx$|\.cnext$/, ".h");
138808
- const typeInput = CodeGenState.symbols;
138809
- const externalTypeHeaders = ExternalTypeHeaderBuilder_default.build(
138810
- this.state.getAllHeaderDirectives(),
138811
- CodeGenState.symbolTable
138812
- );
138813
- const typeInputWithSymbolTable = typeInput ? { ...typeInput, symbolTable: CodeGenState.symbolTable } : void 0;
138814
- return this.headerGenerator.generate(
138815
- headerSymbols,
138816
- headerName,
138817
- {
138818
- exportedOnly: true,
138819
- userIncludes,
138820
- externalTypeHeaders,
138821
- cppMode
138822
- },
138823
- typeInputWithSymbolTable,
138824
- passByValueParams,
138825
- symbolInfo.knownEnums
138826
- );
138827
- }
138828
139472
  /**
138829
139473
  * Convert TSymbols to IHeaderSymbols with auto-const information applied.
138830
139474
  * ADR-055 Phase 7: Replaces mutation-based auto-const updating.
@@ -139032,7 +139676,7 @@ var Runner = class {
139032
139676
  target: config.target,
139033
139677
  debugMode: config.debugMode
139034
139678
  });
139035
- const result = await pipeline.run();
139679
+ const result = await pipeline.transpile({ kind: "files" });
139036
139680
  this._renameOutputIfNeeded(result, explicitOutputFile);
139037
139681
  ResultPrinter_default.print(result);
139038
139682
  process.exit(result.success ? 0 : 1);
@@ -139742,16 +140386,31 @@ var ServeCommand = class _ServeCommand {
139742
140386
  }
139743
140387
  const { source, filePath } = params;
139744
140388
  const options = filePath ? { workingDir: dirname11(filePath), sourcePath: filePath } : void 0;
139745
- const result = await _ServeCommand.transpiler.transpileSource(
140389
+ const transpileResult = await _ServeCommand.transpiler.transpile({
140390
+ kind: "source",
139746
140391
  source,
139747
- options
139748
- );
140392
+ ...options
140393
+ });
140394
+ const fileResult = transpileResult.files.find(
140395
+ (f) => f.sourcePath === (filePath ?? "<string>")
140396
+ ) ?? transpileResult.files[0];
140397
+ if (!fileResult) {
140398
+ return {
140399
+ success: true,
140400
+ result: {
140401
+ success: false,
140402
+ code: "",
140403
+ errors: transpileResult.errors,
140404
+ cppDetected: _ServeCommand.transpiler.isCppDetected()
140405
+ }
140406
+ };
140407
+ }
139749
140408
  return {
139750
140409
  success: true,
139751
140410
  result: {
139752
- success: result.success,
139753
- code: result.code,
139754
- errors: result.errors,
140411
+ success: fileResult.success,
140412
+ code: fileResult.code,
140413
+ errors: fileResult.errors,
139755
140414
  cppDetected: _ServeCommand.transpiler.isCppDetected()
139756
140415
  }
139757
140416
  };
@@ -139765,7 +140424,9 @@ var ServeCommand = class _ServeCommand {
139765
140424
  const { source, filePath } = params;
139766
140425
  if (_ServeCommand.transpiler && filePath) {
139767
140426
  try {
139768
- await _ServeCommand.transpiler.transpileSource(source, {
140427
+ await _ServeCommand.transpiler.transpile({
140428
+ kind: "source",
140429
+ source,
139769
140430
  workingDir: dirname11(filePath),
139770
140431
  sourcePath: filePath
139771
140432
  });