c-next 0.2.3 → 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 (39) hide show
  1. package/README.md +59 -57
  2. package/dist/index.js +537 -170
  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/output/codegen/CodeGenerator.ts +31 -5
  24. package/src/transpiler/output/codegen/TypeValidator.ts +10 -7
  25. package/src/transpiler/output/codegen/__tests__/ExpressionWalker.test.ts +9 -3
  26. package/src/transpiler/output/codegen/__tests__/RequireInclude.test.ts +86 -23
  27. package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +3 -1
  28. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +43 -29
  29. package/src/transpiler/output/codegen/generators/IOrchestrator.ts +5 -2
  30. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +1 -1
  31. package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +12 -3
  32. package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +4 -4
  33. package/src/transpiler/output/codegen/helpers/FunctionContextManager.ts +9 -1
  34. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +5 -2
  35. package/src/transpiler/state/CodeGenState.ts +6 -0
  36. package/src/transpiler/types/IPipelineFile.ts +2 -2
  37. package/src/transpiler/types/IPipelineInput.ts +1 -1
  38. package/src/transpiler/types/TTranspileInput.ts +18 -0
  39. 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.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
  }
@@ -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();
@@ -125526,11 +125531,14 @@ var FunctionContextManager = class _FunctionContextManager {
125526
125531
  typeCtx,
125527
125532
  callbacks
125528
125533
  );
125534
+ const isCallbackCompat = CodeGenState.currentFunctionName !== null && CodeGenState.callbackCompatibleFunctions.has(
125535
+ CodeGenState.currentFunctionName
125536
+ );
125529
125537
  const paramInfo = {
125530
125538
  name,
125531
125539
  baseType: typeInfo.typeName,
125532
125540
  isArray,
125533
- isStruct: typeInfo.isStruct,
125541
+ isStruct: isCallbackCompat ? false : typeInfo.isStruct,
125534
125542
  isConst,
125535
125543
  isCallback: typeInfo.isCallback,
125536
125544
  isString: typeInfo.isString
@@ -127561,11 +127569,11 @@ var CodeGenerator = class _CodeGenerator {
127561
127569
  TypeValidator_default.validateSwitchStatement(ctx, switchExpr);
127562
127570
  }
127563
127571
  /**
127564
- * Validate do-while condition.
127572
+ * Validate condition is a boolean expression (ADR-027, Issue #884).
127565
127573
  * Part of IOrchestrator interface (ADR-053 A3).
127566
127574
  */
127567
- validateDoWhileCondition(ctx) {
127568
- TypeValidator_default.validateDoWhileCondition(ctx);
127575
+ validateConditionIsBoolean(ctx, conditionType) {
127576
+ TypeValidator_default.validateConditionIsBoolean(ctx, conditionType);
127569
127577
  }
127570
127578
  /**
127571
127579
  * Issue #254: Validate no function calls in condition (E0702).
@@ -127951,8 +127959,8 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
127951
127959
  }
127952
127960
  /**
127953
127961
  * 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).
127962
+ * Used by the transpile() pipeline to collect modification info from includes
127963
+ * for cross-file const inference.
127956
127964
  *
127957
127965
  * Issue #565: Now accepts optional cross-file data for transitive propagation.
127958
127966
  * When a file calls a function from an included file that modifies its param,
@@ -129333,6 +129341,9 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
129333
129341
  return "{}";
129334
129342
  }
129335
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
+ }
129336
129347
  return `(${castType}){ ${fieldInits.join(", ")} }`;
129337
129348
  }
129338
129349
  /**
@@ -129516,6 +129527,11 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
129516
129527
  )) {
129517
129528
  return true;
129518
129529
  }
129530
+ if (CodeGenState.currentFunctionName && CodeGenState.callbackCompatibleFunctions.has(
129531
+ CodeGenState.currentFunctionName
129532
+ ) && this.isKnownStruct(typeName)) {
129533
+ return true;
129534
+ }
129519
129535
  return false;
129520
129536
  }
129521
129537
  // ========================================================================
@@ -130728,8 +130744,11 @@ var HeaderGeneratorUtils = class _HeaderGeneratorUtils {
130728
130744
  lines.push(include);
130729
130745
  }
130730
130746
  }
130747
+ const userIncludeSet = new Set(options.userIncludes ?? []);
130731
130748
  for (const directive of headersToInclude) {
130732
- lines.push(directive);
130749
+ if (!userIncludeSet.has(directive)) {
130750
+ lines.push(directive);
130751
+ }
130733
130752
  }
130734
130753
  const hasIncludes = options.includeSystemHeaders !== false || options.userIncludes && options.userIncludes.length > 0 || headersToInclude.size > 0;
130735
130754
  if (hasIncludes) {
@@ -133342,8 +133361,9 @@ var CResolver = class _CResolver {
133342
133361
  if (!name) continue;
133343
133362
  const isFunction = DeclaratorUtils_default.declaratorIsFunction(declarator);
133344
133363
  if (ctx.isTypedef) {
133364
+ const typedefType = _CResolver.isFunctionPointerDeclarator(declarator) ? `${baseType} (*)(${_CResolver.extractParamText(declarator)})` : baseType;
133345
133365
  ctx.symbols.push(
133346
- TypedefCollector_default.collect(name, baseType, ctx.sourceFile, ctx.line)
133366
+ TypedefCollector_default.collect(name, typedefType, ctx.sourceFile, ctx.line)
133347
133367
  );
133348
133368
  } else if (isFunction) {
133349
133369
  ctx.symbols.push(
@@ -133446,6 +133466,36 @@ var CResolver = class _CResolver {
133446
133466
  }
133447
133467
  return typeParts.join(" ") || "int";
133448
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
+ }
133449
133499
  };
133450
133500
  var c_default = CResolver;
133451
133501
 
@@ -135075,6 +135125,9 @@ var IncludeResolver = class _IncludeResolver {
135075
135125
  }
135076
135126
  if (file.type === EFileType_default.CNext) {
135077
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);
135078
135131
  }
135079
135132
  }
135080
135133
  /**
@@ -135212,8 +135265,8 @@ var IncludeResolver = class _IncludeResolver {
135212
135265
  /**
135213
135266
  * Build search paths from a source file location
135214
135267
  *
135215
- * Consolidates the search path building logic used by both `run()` and
135216
- * `transpileSource()` code paths.
135268
+ * Consolidates the search path building logic used by the unified
135269
+ * transpile() entry point.
135217
135270
  *
135218
135271
  * Search order (highest to lowest priority):
135219
135272
  * 1. Source file's directory (for relative includes)
@@ -136548,16 +136601,20 @@ var FunctionCallListener = class extends CNextListener {
136548
136601
  // ========================================================================
136549
136602
  // ISR/Callback Variable Tracking (ADR-040)
136550
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
+ }
136551
136610
  /**
136552
136611
  * Track ISR-typed variables from variable declarations
136553
136612
  * e.g., `ISR handler <- myFunction;`
136554
136613
  */
136555
136614
  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);
136615
+ const typeName = ctx.type().getText();
136616
+ if (this.isCallableType(typeName)) {
136617
+ this.analyzer.defineCallableVariable(ctx.IDENTIFIER().getText());
136561
136618
  }
136562
136619
  };
136563
136620
  /**
@@ -136565,11 +136622,9 @@ var FunctionCallListener = class extends CNextListener {
136565
136622
  * e.g., `void execute(ISR handler) { handler(); }`
136566
136623
  */
136567
136624
  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);
136625
+ const typeName = ctx.type().getText();
136626
+ if (this.isCallableType(typeName)) {
136627
+ this.analyzer.defineCallableVariable(ctx.IDENTIFIER().getText());
136573
136628
  }
136574
136629
  };
136575
136630
  // ========================================================================
@@ -136708,6 +136763,7 @@ var FunctionCallAnalyzer = class {
136708
136763
  this.collectIncludes(tree);
136709
136764
  this.collectCallbackTypes(tree);
136710
136765
  this.collectAllLocalFunctions(tree);
136766
+ this.collectCallbackCompatibleFunctions(tree);
136711
136767
  const listener = new FunctionCallListener(this);
136712
136768
  ParseTreeWalker5.DEFAULT.walk(listener, tree);
136713
136769
  return this.errors;
@@ -136791,6 +136847,120 @@ var FunctionCallAnalyzer = class {
136791
136847
  isCallbackType(name) {
136792
136848
  return this.callbackTypes.has(name);
136793
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
+ }
136794
136964
  /**
136795
136965
  * ADR-040: Register a variable that holds a callable (ISR or callback)
136796
136966
  */
@@ -137044,6 +137214,26 @@ var TypeConstants = class {
137044
137214
  "float",
137045
137215
  "double"
137046
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"];
137047
137237
  };
137048
137238
  var TypeConstants_default = TypeConstants;
137049
137239
 
@@ -137161,6 +137351,212 @@ var FloatModuloAnalyzer = class {
137161
137351
  };
137162
137352
  var FloatModuloAnalyzer_default = FloatModuloAnalyzer;
137163
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
+
137164
137560
  // src/transpiler/logic/analysis/runAnalyzers.ts
137165
137561
  function collectErrors(analyzerErrors, target, formatMessage) {
137166
137562
  const formatter = formatMessage ?? ((e) => e.message);
@@ -137214,6 +137610,10 @@ function runAnalyzers(tree, tokenStream, options) {
137214
137610
  if (collectErrors(floatModAnalyzer.analyze(tree), errors, formatWithCode)) {
137215
137611
  return errors;
137216
137612
  }
137613
+ const indexTypeAnalyzer = new ArrayIndexTypeAnalyzer_default();
137614
+ if (collectErrors(indexTypeAnalyzer.analyze(tree), errors, formatWithCode)) {
137615
+ return errors;
137616
+ }
137217
137617
  const commentExtractor = new CommentExtractor_default(tokenStream);
137218
137618
  collectErrors(
137219
137619
  commentExtractor.validate(),
@@ -137821,7 +138221,7 @@ var TransitiveEnumCollector = class {
137821
138221
  /**
137822
138222
  * Collect symbol info for standalone mode from resolved includes.
137823
138223
  *
137824
- * Issue #591: Extracted from Transpiler.transpileSource() to unify enum collection.
138224
+ * Issue #591: Extracted to unify enum collection across transpilation modes.
137825
138225
  * Unlike collect() which starts from a file path and parses it, this method
137826
138226
  * starts from already-resolved includes (from IncludeResolver.resolve()).
137827
138227
  *
@@ -137899,83 +138299,53 @@ var Transpiler = class {
137899
138299
  this.cacheManager = projectRoot ? new CacheManager_default(projectRoot, this.fs) : null;
137900
138300
  }
137901
138301
  // ===========================================================================
137902
- // Public API: run() and transpileSource()
138302
+ // Public API
137903
138303
  // ===========================================================================
137904
138304
  /**
137905
- * Execute the unified pipeline from CLI inputs.
138305
+ * Unified entry point for all transpilation.
137906
138306
  *
137907
- * 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[]
137908
138311
  */
137909
- async run() {
138312
+ async transpile(input) {
137910
138313
  const result = this._initResult();
137911
138314
  try {
137912
138315
  await this._initializeRun();
137913
- const { cnextFiles, headerFiles } = await this.discoverSources();
137914
- if (cnextFiles.length === 0) {
138316
+ const pipelineInput = await this.discoverIncludes(input);
138317
+ if (pipelineInput.cnextFiles.length === 0) {
137915
138318
  return this._finalizeResult(result, "No C-Next source files found");
137916
138319
  }
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);
138320
+ if (input.kind === "files") {
138321
+ this._ensureOutputDirectories();
138322
+ }
138323
+ await this._executePipeline(pipelineInput, result);
137928
138324
  return await this._finalizeResult(result);
137929
138325
  } catch (err) {
137930
138326
  return this._handleRunError(result, err);
137931
138327
  }
137932
138328
  }
137933
138329
  /**
137934
- * Transpile source code provided as a string.
138330
+ * Stage 1: Discover files and build pipeline input.
137935
138331
  *
137936
- * Discovers includes from the source, builds an IPipelineInput, and
137937
- * 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
137938
138335
  *
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
- }
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
+ );
137979
138349
  }
137980
138350
  // ===========================================================================
137981
138351
  // Unified Pipeline
@@ -137983,7 +138353,7 @@ var Transpiler = class {
137983
138353
  /**
137984
138354
  * The single unified pipeline for all transpilation.
137985
138355
  *
137986
- * Both run() and transpileSource() delegate here after file discovery.
138356
+ * transpile() delegates here after file discovery via discoverIncludes().
137987
138357
  *
137988
138358
  * Stage 2: Collect symbols from C/C++ headers (includes building analyzer context)
137989
138359
  * Stage 3: Collect symbols from C-Next files
@@ -138090,14 +138460,6 @@ var Transpiler = class {
138090
138460
  if (this.config.parseOnly) {
138091
138461
  return this.buildParseOnlyResult(sourcePath, declarationCount);
138092
138462
  }
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
138463
  const tSymbols = cnext_default.resolve(tree, sourcePath);
138102
138464
  let symbolInfo = TSymbolInfoAdapter_default.convert(tSymbols);
138103
138465
  const externalEnumSources = this._collectExternalEnumSources(
@@ -138110,6 +138472,15 @@ var Transpiler = class {
138110
138472
  externalEnumSources
138111
138473
  );
138112
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
+ }
138113
138484
  this._setupCrossFileModifications();
138114
138485
  const code = this.codeGenerator.generate(tree, tokenStream, {
138115
138486
  debugMode: this.config.debugMode,
@@ -138127,15 +138498,7 @@ var Transpiler = class {
138127
138498
  if (this.cppDetected) {
138128
138499
  this._accumulateFileModifications();
138129
138500
  }
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
- );
138501
+ const headerCode = this.generateHeaderForFile(file) ?? void 0;
138139
138502
  return this.buildSuccessResult(
138140
138503
  sourcePath,
138141
138504
  code,
@@ -138200,6 +138563,13 @@ var Transpiler = class {
138200
138563
  this.state.setHeaderDirective(header.path, directive);
138201
138564
  }
138202
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
+ }
138203
138573
  const cnextIncludeFiles = [];
138204
138574
  IncludeTreeWalker_default.walk(
138205
138575
  resolved.cnextIncludes,
@@ -138257,6 +138627,7 @@ var Transpiler = class {
138257
138627
  this.state.reset();
138258
138628
  CodeGenState.symbolTable.clear();
138259
138629
  SymbolRegistry_default.reset();
138630
+ CodeGenState.callbackCompatibleFunctions = /* @__PURE__ */ new Set();
138260
138631
  }
138261
138632
  /**
138262
138633
  * Ensure output directories exist
@@ -138335,8 +138706,12 @@ var Transpiler = class {
138335
138706
  if (file.symbolOnly) {
138336
138707
  continue;
138337
138708
  }
138338
- const headerPath = this.generateHeader(file.discoveredFile);
138339
- 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);
138340
138715
  result.outputFiles.push(headerPath);
138341
138716
  }
138342
138717
  }
@@ -138370,7 +138745,7 @@ var Transpiler = class {
138370
138745
  return result;
138371
138746
  }
138372
138747
  // ===========================================================================
138373
- // Source Discovery (Stage 1 for run())
138748
+ // File Discovery (Stage 1 for files mode)
138374
138749
  // ===========================================================================
138375
138750
  /**
138376
138751
  * Discover C-Next files from a single input (file or directory).
@@ -138431,6 +138806,10 @@ var Transpiler = class {
138431
138806
  ""
138432
138807
  );
138433
138808
  depGraph.addDependency(cnxPath, includePath);
138809
+ const directive = resolved.headerIncludeDirectives.get(includePath);
138810
+ if (directive) {
138811
+ this.state.setHeaderDirective(includePath, directive);
138812
+ }
138434
138813
  const alreadyExists = cnextBaseNames.has(includeBaseName) || cnextFiles.some((f) => resolve12(f.path) === includePath);
138435
138814
  if (!alreadyExists) {
138436
138815
  cnextFiles.push(cnxInclude);
@@ -138494,14 +138873,14 @@ var Transpiler = class {
138494
138873
  * This ensures headers are found based on what the source actually
138495
138874
  * includes, not by blindly scanning include directories.
138496
138875
  */
138497
- async discoverSources() {
138876
+ async _discoverFromFiles() {
138498
138877
  const cnextFiles = [];
138499
138878
  const fileByPath = /* @__PURE__ */ new Map();
138500
138879
  for (const input of this.config.inputs) {
138501
138880
  this._discoverCNextFromInput(input, cnextFiles, fileByPath);
138502
138881
  }
138503
138882
  if (cnextFiles.length === 0) {
138504
- return { cnextFiles: [], headerFiles: [] };
138883
+ return { cnextFiles: [], headerFiles: [], writeOutputToDisk: true };
138505
138884
  }
138506
138885
  const headerSet = /* @__PURE__ */ new Map();
138507
138886
  const depGraph = new DependencyGraph_default();
@@ -138529,9 +138908,14 @@ var Transpiler = class {
138529
138908
  }
138530
138909
  );
138531
138910
  this.warnings.push(...headerWarnings);
138911
+ const pipelineFiles = sortedCnextFiles.map((f) => ({
138912
+ path: f.path,
138913
+ discoveredFile: f
138914
+ }));
138532
138915
  return {
138533
- cnextFiles: sortedCnextFiles,
138534
- headerFiles: allHeaders
138916
+ cnextFiles: pipelineFiles,
138917
+ headerFiles: allHeaders,
138918
+ writeOutputToDisk: true
138535
138919
  };
138536
138920
  }
138537
138921
  // ===========================================================================
@@ -138716,17 +139100,22 @@ var Transpiler = class {
138716
139100
  * Stage 6: Generate header file for a C-Next file
138717
139101
  * ADR-055 Phase 7: Uses TSymbol directly, converts to IHeaderSymbol for generation.
138718
139102
  */
138719
- generateHeader(file) {
138720
- 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);
138721
139111
  const exportedSymbols = tSymbols.filter((s) => s.isExported);
138722
139112
  if (exportedSymbols.length === 0) {
138723
139113
  return null;
138724
139114
  }
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);
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);
138730
139119
  const allKnownEnums = TransitiveEnumCollector_default.aggregateKnownEnums(
138731
139120
  this.state.getAllSymbolInfo()
138732
139121
  );
@@ -138741,7 +139130,7 @@ var Transpiler = class {
138741
139130
  unmodifiedParams,
138742
139131
  allKnownEnums
138743
139132
  );
138744
- const headerContent = this.headerGenerator.generate(
139133
+ return this.headerGenerator.generate(
138745
139134
  headerSymbols,
138746
139135
  headerName,
138747
139136
  {
@@ -138754,8 +139143,6 @@ var Transpiler = class {
138754
139143
  passByValueParams,
138755
139144
  allKnownEnums
138756
139145
  );
138757
- this.fs.writeFile(headerPath, headerContent);
138758
- return headerPath;
138759
139146
  }
138760
139147
  /**
138761
139148
  * Collect external enum sources from included C-Next files.
@@ -138788,43 +139175,6 @@ var Transpiler = class {
138788
139175
  );
138789
139176
  }
138790
139177
  }
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
139178
  /**
138829
139179
  * Convert TSymbols to IHeaderSymbols with auto-const information applied.
138830
139180
  * ADR-055 Phase 7: Replaces mutation-based auto-const updating.
@@ -139032,7 +139382,7 @@ var Runner = class {
139032
139382
  target: config.target,
139033
139383
  debugMode: config.debugMode
139034
139384
  });
139035
- const result = await pipeline.run();
139385
+ const result = await pipeline.transpile({ kind: "files" });
139036
139386
  this._renameOutputIfNeeded(result, explicitOutputFile);
139037
139387
  ResultPrinter_default.print(result);
139038
139388
  process.exit(result.success ? 0 : 1);
@@ -139742,16 +140092,31 @@ var ServeCommand = class _ServeCommand {
139742
140092
  }
139743
140093
  const { source, filePath } = params;
139744
140094
  const options = filePath ? { workingDir: dirname11(filePath), sourcePath: filePath } : void 0;
139745
- const result = await _ServeCommand.transpiler.transpileSource(
140095
+ const transpileResult = await _ServeCommand.transpiler.transpile({
140096
+ kind: "source",
139746
140097
  source,
139747
- options
139748
- );
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
+ }
139749
140114
  return {
139750
140115
  success: true,
139751
140116
  result: {
139752
- success: result.success,
139753
- code: result.code,
139754
- errors: result.errors,
140117
+ success: fileResult.success,
140118
+ code: fileResult.code,
140119
+ errors: fileResult.errors,
139755
140120
  cppDetected: _ServeCommand.transpiler.isCppDetected()
139756
140121
  }
139757
140122
  };
@@ -139765,7 +140130,9 @@ var ServeCommand = class _ServeCommand {
139765
140130
  const { source, filePath } = params;
139766
140131
  if (_ServeCommand.transpiler && filePath) {
139767
140132
  try {
139768
- await _ServeCommand.transpiler.transpileSource(source, {
140133
+ await _ServeCommand.transpiler.transpile({
140134
+ kind: "source",
140135
+ source,
139769
140136
  workingDir: dirname11(filePath),
139770
140137
  sourcePath: filePath
139771
140138
  });