c-next 0.1.48 → 0.1.49

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c-next",
3
- "version": "0.1.48",
3
+ "version": "0.1.49",
4
4
  "description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -189,7 +189,7 @@ interface GeneratorContext {
189
189
  indentLevel: number;
190
190
  scopeMembers: Map<string, Set<string>>; // scope -> member names (ADR-016)
191
191
  currentParameters: Map<string, TParameterInfo>; // ADR-006: track params for pointer semantics
192
- modifiedParameters: Set<string>; // Issue #268: track modified params for auto-const inference
192
+ // Issue #558: modifiedParameters removed - now uses analysis-phase results from this.modifiedParameters
193
193
  localArrays: Set<string>; // ADR-006: track local array variables (no & needed)
194
194
  localVariables: Set<string>; // ADR-016: track local variables (allowed as bare identifiers)
195
195
  floatBitShadows: Set<string>; // Track declared shadow variables for float bit indexing
@@ -221,7 +221,7 @@ export default class CodeGenerator implements IOrchestrator {
221
221
  indentLevel: 0,
222
222
  scopeMembers: new Map(), // ADR-016: renamed from namespaceMembers
223
223
  currentParameters: new Map(),
224
- modifiedParameters: new Set(),
224
+ // Issue #558: modifiedParameters removed - now uses analysis-phase results
225
225
  localArrays: new Set(),
226
226
  localVariables: new Set(), // ADR-016: track local variables
227
227
  floatBitShadows: new Set(), // Track declared shadow variables for float bit indexing
@@ -327,6 +327,17 @@ export default class CodeGenerator implements IOrchestrator {
327
327
  */
328
328
  private readonly modifiedParameters: Map<string, Set<string>> = new Map();
329
329
 
330
+ /**
331
+ * Issue #558: Pending cross-file modifications to inject after analyzePassByValue clears.
332
+ * Set by Pipeline before generate() to share modifications from previously processed files.
333
+ */
334
+ private pendingCrossFileModifications: Map<string, Set<string>> | null = null;
335
+
336
+ /**
337
+ * Issue #558: Pending cross-file parameter lists to inject for transitive propagation.
338
+ */
339
+ private pendingCrossFileParamLists: Map<string, string[]> | null = null;
340
+
330
341
  /**
331
342
  * Issue #269: Tracks which parameters should pass by value
332
343
  * Map of functionName -> Set of passByValue parameter names
@@ -1061,7 +1072,7 @@ export default class CodeGenerator implements IOrchestrator {
1061
1072
  this.context.localVariables.clear();
1062
1073
  this.context.floatBitShadows.clear();
1063
1074
  this.context.floatShadowCurrent.clear();
1064
- this.context.modifiedParameters.clear(); // Issue #268: Clear for new function
1075
+ // Issue #558: modifiedParameters tracking removed - uses analysis-phase results
1065
1076
  this.context.inFunctionBody = true;
1066
1077
  }
1067
1078
 
@@ -1106,13 +1117,14 @@ export default class CodeGenerator implements IOrchestrator {
1106
1117
 
1107
1118
  /**
1108
1119
  * Issue #268: Update symbol parameters with auto-const info.
1109
- * Call after generating function body to track unmodified parameters.
1120
+ * Issue #558: Now uses analysis-phase results for modification tracking.
1110
1121
  */
1111
1122
  updateFunctionParamsAutoConst(functionName: string): void {
1112
- // Collect unmodified parameters for this function
1123
+ // Collect unmodified parameters for this function using analysis results
1113
1124
  const unmodifiedParams = new Set<string>();
1125
+ const modifiedSet = this.modifiedParameters.get(functionName);
1114
1126
  for (const [paramName] of this.context.currentParameters) {
1115
- if (!this.context.modifiedParameters.has(paramName)) {
1127
+ if (!modifiedSet?.has(paramName)) {
1116
1128
  unmodifiedParams.add(paramName);
1117
1129
  }
1118
1130
  }
@@ -1121,11 +1133,49 @@ export default class CodeGenerator implements IOrchestrator {
1121
1133
 
1122
1134
  /**
1123
1135
  * Issue #268: Mark a parameter as modified for auto-const tracking.
1136
+ * Issue #558: Now a no-op - analysis phase handles all modification tracking
1137
+ * including transitive propagation across function calls and files.
1124
1138
  */
1125
- markParameterModified(paramName: string): void {
1126
- if (this.context.currentParameters.has(paramName)) {
1127
- this.context.modifiedParameters.add(paramName);
1128
- }
1139
+ markParameterModified(_paramName: string): void {
1140
+ // No-op: Analysis phase (analyzePassByValue) now handles all modification
1141
+ // tracking including cross-file and transitive propagation.
1142
+ }
1143
+
1144
+ /**
1145
+ * Issue #558: Check if a parameter is modified using analysis-phase results.
1146
+ * This is the unified source of truth for modification tracking.
1147
+ */
1148
+ private _isCurrentParameterModified(paramName: string): boolean {
1149
+ const funcName = this.context.currentFunctionName;
1150
+ if (!funcName) return false;
1151
+ return this.modifiedParameters.get(funcName)?.has(paramName) ?? false;
1152
+ }
1153
+
1154
+ /**
1155
+ * Issue #558: Get the modified parameters map for cross-file propagation.
1156
+ * Returns function name -> set of modified parameter names.
1157
+ */
1158
+ getModifiedParameters(): ReadonlyMap<string, Set<string>> {
1159
+ return this.modifiedParameters;
1160
+ }
1161
+
1162
+ /**
1163
+ * Issue #558: Set cross-file modification data to inject during analyzePassByValue.
1164
+ * Called by Pipeline before generate() to share modifications from previously processed files.
1165
+ */
1166
+ setCrossFileModifications(
1167
+ modifications: Map<string, Set<string>>,
1168
+ paramLists: Map<string, string[]>,
1169
+ ): void {
1170
+ this.pendingCrossFileModifications = modifications;
1171
+ this.pendingCrossFileParamLists = paramLists;
1172
+ }
1173
+
1174
+ /**
1175
+ * Issue #558: Get the function parameter lists for cross-file propagation.
1176
+ */
1177
+ getFunctionParamLists(): ReadonlyMap<string, string[]> {
1178
+ return this.functionParamLists;
1129
1179
  }
1130
1180
 
1131
1181
  /**
@@ -1539,7 +1589,7 @@ export default class CodeGenerator implements IOrchestrator {
1539
1589
  indentLevel: 0,
1540
1590
  scopeMembers: new Map(), // ADR-016
1541
1591
  currentParameters: new Map(),
1542
- modifiedParameters: new Set(),
1592
+ // Issue #558: modifiedParameters removed - uses analysis-phase results
1543
1593
  localArrays: new Set(),
1544
1594
  localVariables: new Set(), // ADR-016
1545
1595
  floatBitShadows: new Set(), // Track declared shadow variables for float bit indexing
@@ -2025,6 +2075,29 @@ export default class CodeGenerator implements IOrchestrator {
2025
2075
  // Phase 1: Collect function parameter lists and direct modifications
2026
2076
  this.collectFunctionParametersAndModifications(tree);
2027
2077
 
2078
+ // Issue #558: Inject cross-file data before transitive propagation
2079
+ if (this.pendingCrossFileModifications) {
2080
+ for (const [funcName, params] of this.pendingCrossFileModifications) {
2081
+ const existing = this.modifiedParameters.get(funcName);
2082
+ if (existing) {
2083
+ for (const param of params) {
2084
+ existing.add(param);
2085
+ }
2086
+ } else {
2087
+ this.modifiedParameters.set(funcName, new Set(params));
2088
+ }
2089
+ }
2090
+ this.pendingCrossFileModifications = null; // Clear after use
2091
+ }
2092
+ if (this.pendingCrossFileParamLists) {
2093
+ for (const [funcName, params] of this.pendingCrossFileParamLists) {
2094
+ if (!this.functionParamLists.has(funcName)) {
2095
+ this.functionParamLists.set(funcName, params);
2096
+ }
2097
+ }
2098
+ this.pendingCrossFileParamLists = null; // Clear after use
2099
+ }
2100
+
2028
2101
  // Phase 2: Fixed-point iteration for transitive modifications
2029
2102
  this.propagateTransitiveModifications();
2030
2103
 
@@ -2120,17 +2193,31 @@ export default class CodeGenerator implements IOrchestrator {
2120
2193
  // Check for assignment statements
2121
2194
  if (stmt.assignmentStatement()) {
2122
2195
  const assign = stmt.assignmentStatement()!;
2123
- // Get the target - use assignmentTarget() which has IDENTIFIER()
2124
2196
  const target = assign.assignmentTarget();
2197
+
2198
+ // Issue #558: Extract base identifier from assignment target
2199
+ // - Simple identifier: x <- value
2200
+ // - Member access: x.field <- value (first IDENTIFIER is the base)
2201
+ // - Array access: x[i] <- value
2202
+ let baseIdentifier: string | null = null;
2203
+
2125
2204
  if (target?.IDENTIFIER()) {
2126
- // Simple identifier assignment (not array/member access)
2127
- if (!target.arrayAccess() && !target.memberAccess()) {
2128
- const targetName = target.IDENTIFIER()!.getText();
2129
- if (paramSet.has(targetName)) {
2130
- // Direct assignment to parameter
2131
- this.modifiedParameters.get(funcName)!.add(targetName);
2132
- }
2205
+ // Simple identifier assignment
2206
+ baseIdentifier = target.IDENTIFIER()!.getText();
2207
+ } else if (target?.memberAccess()) {
2208
+ // Member access: first IDENTIFIER is the base (e.g., cfg.value -> cfg)
2209
+ const identifiers = target.memberAccess()!.IDENTIFIER();
2210
+ if (identifiers.length > 0) {
2211
+ baseIdentifier = identifiers[0].getText();
2133
2212
  }
2213
+ } else if (target?.arrayAccess()) {
2214
+ // Array access: IDENTIFIER is the base (e.g., arr[0] -> arr)
2215
+ baseIdentifier = target.arrayAccess()!.IDENTIFIER()?.getText() ?? null;
2216
+ }
2217
+
2218
+ if (baseIdentifier && paramSet.has(baseIdentifier)) {
2219
+ // Direct or member/array assignment modifies the parameter
2220
+ this.modifiedParameters.get(funcName)!.add(baseIdentifier);
2134
2221
  }
2135
2222
  }
2136
2223
 
@@ -5358,8 +5445,7 @@ export default class CodeGenerator implements IOrchestrator {
5358
5445
  // Track parameters for ADR-006 pointer semantics
5359
5446
  this._setParameters(ctx.parameterList() ?? null);
5360
5447
 
5361
- // Issue #268: Clear modified parameters tracking for this function
5362
- this.context.modifiedParameters.clear();
5448
+ // Issue #558: modifiedParameters tracking removed - uses analysis-phase results
5363
5449
 
5364
5450
  // ADR-016: Clear local variables and mark that we're in a function body
5365
5451
  this.context.localVariables.clear();
@@ -5525,8 +5611,8 @@ export default class CodeGenerator implements IOrchestrator {
5525
5611
  // Arrays pass naturally as pointers
5526
5612
  if (dims.length > 0) {
5527
5613
  const dimStr = dims.map((d) => this._generateArrayDimension(d)).join("");
5528
- // Issue #268: Add const for unmodified array parameters
5529
- const wasModified = this.context.modifiedParameters.has(name);
5614
+ // Issue #268/#558: Add const for unmodified array parameters (uses analysis results)
5615
+ const wasModified = this._isCurrentParameterModified(name);
5530
5616
  const autoConst = !wasModified && !constMod ? "const " : "";
5531
5617
  return `${autoConst}${constMod}${type} ${name}${dimStr}`;
5532
5618
  }
@@ -5557,8 +5643,8 @@ export default class CodeGenerator implements IOrchestrator {
5557
5643
  // ADR-045: String parameters (non-array) are passed as char*
5558
5644
  // Issue #551: Handle before unknown type check
5559
5645
  if (ctx.type().stringType() && dims.length === 0) {
5560
- // Issue #268: Add const for unmodified string parameters
5561
- const wasModified = this.context.modifiedParameters.has(name);
5646
+ // Issue #268/#558: Add const for unmodified string parameters (uses analysis results)
5647
+ const wasModified = this._isCurrentParameterModified(name);
5562
5648
  const autoConst = !wasModified && !constMod ? "const " : "";
5563
5649
  return `${autoConst}${constMod}char* ${name}`;
5564
5650
  }
@@ -5566,8 +5652,8 @@ export default class CodeGenerator implements IOrchestrator {
5566
5652
  // ADR-006: Pass by reference for known struct types and known primitives
5567
5653
  // Issue #551: Unknown types (external enums, typedefs) use pass-by-value
5568
5654
  if (this._isKnownStruct(typeName) || this._isKnownPrimitive(typeName)) {
5569
- // Issue #268: Add const for unmodified pointer parameters
5570
- const wasModified = this.context.modifiedParameters.has(name);
5655
+ // Issue #268/#558: Add const for unmodified pointer parameters (uses analysis results)
5656
+ const wasModified = this._isCurrentParameterModified(name);
5571
5657
  const autoConst = !wasModified && !constMod ? "const " : "";
5572
5658
  // Issue #409: In C++ mode, use references (&) instead of pointers (*)
5573
5659
  // This allows C-Next callbacks to match C++ function pointer signatures
@@ -6833,10 +6919,7 @@ export default class CodeGenerator implements IOrchestrator {
6833
6919
  throw new Error(constError);
6834
6920
  }
6835
6921
 
6836
- // Issue #268: Track parameter modification for auto-const inference
6837
- if (this.context.currentParameters.has(id)) {
6838
- this.context.modifiedParameters.add(id);
6839
- }
6922
+ // Issue #558: Parameter modification tracking removed - uses analysis-phase results
6840
6923
 
6841
6924
  // Invalidate float shadow when variable is assigned directly
6842
6925
  const shadowName = `__bits_${id}`;
@@ -6962,10 +7045,7 @@ export default class CodeGenerator implements IOrchestrator {
6962
7045
  );
6963
7046
  }
6964
7047
 
6965
- // Issue #268: Track parameter modification for auto-const inference (array element)
6966
- if (this.context.currentParameters.has(arrayName)) {
6967
- this.context.modifiedParameters.add(arrayName);
6968
- }
7048
+ // Issue #558: Parameter modification tracking removed - uses analysis-phase results
6969
7049
  }
6970
7050
 
6971
7051
  // Check member access on const struct - validate the root is not const
@@ -6978,10 +7058,7 @@ export default class CodeGenerator implements IOrchestrator {
6978
7058
  throw new Error(`${constError} (member access)`);
6979
7059
  }
6980
7060
 
6981
- // Issue #268: Track parameter modification for auto-const inference (member access)
6982
- if (this.context.currentParameters.has(rootName)) {
6983
- this.context.modifiedParameters.add(rootName);
6984
- }
7061
+ // Issue #558: Parameter modification tracking removed - uses analysis-phase results
6985
7062
 
6986
7063
  // ADR-013: Check for read-only register members (ro = implicitly const)
6987
7064
  if (identifiers.length >= 2) {
@@ -7124,7 +7201,8 @@ export default class CodeGenerator implements IOrchestrator {
7124
7201
  !paramInfo.isStruct &&
7125
7202
  this._isKnownPrimitive(paramInfo.baseType)
7126
7203
  ) {
7127
- return `(*${id})`;
7204
+ // Issue #558: In C++ mode, primitives that become references don't need dereferencing
7205
+ return this.cppMode ? id : `(*${id})`;
7128
7206
  }
7129
7207
  return id;
7130
7208
  }
@@ -8657,7 +8735,8 @@ export default class CodeGenerator implements IOrchestrator {
8657
8735
  !paramInfo.isStruct &&
8658
8736
  this._isKnownPrimitive(paramInfo.baseType)
8659
8737
  ) {
8660
- return `(*${id})`;
8738
+ // Issue #558: In C++ mode, primitives that become references don't need dereferencing
8739
+ return this.cppMode ? id : `(*${id})`;
8661
8740
  }
8662
8741
  return id;
8663
8742
  }
@@ -82,6 +82,17 @@ class Pipeline {
82
82
  * Used to include C headers in generated .h files instead of forward-declaring types.
83
83
  */
84
84
  private readonly headerIncludeDirectives: Map<string, string> = new Map();
85
+ /**
86
+ * Issue #558: Accumulated parameter modifications across all processed files.
87
+ * Used for cross-file const inference in C++ mode.
88
+ */
89
+ private readonly accumulatedModifications: Map<string, Set<string>> =
90
+ new Map();
91
+ /**
92
+ * Issue #558: Accumulated function parameter lists across all processed files.
93
+ * Used for cross-file const inference transitive propagation.
94
+ */
95
+ private readonly accumulatedParamLists: Map<string, string[]> = new Map();
85
96
 
86
97
  constructor(config: IPipelineConfig) {
87
98
  // Apply defaults
@@ -137,6 +148,10 @@ class Pipeline {
137
148
  await this.cacheManager.initialize();
138
149
  }
139
150
 
151
+ // Issue #558: Reset cross-file modification tracking for new run
152
+ this.accumulatedModifications.clear();
153
+ this.accumulatedParamLists.clear();
154
+
140
155
  // Stage 1: Discover source files
141
156
  const { cnextFiles, headerFiles } = await this.discoverSources();
142
157
 
@@ -146,6 +161,15 @@ class Pipeline {
146
161
  return result;
147
162
  }
148
163
 
164
+ // Issue #558: Sort files by dependency order for correct cross-file const inference.
165
+ // Files that are included by others should be processed first so their
166
+ // parameter modifications are available during transitive propagation.
167
+ // Simple heuristic: reverse the discovery order since includes are added
168
+ // after the files that include them.
169
+ if (this.cppDetected) {
170
+ cnextFiles.reverse();
171
+ }
172
+
149
173
  // Ensure output directory exists if specified
150
174
  if (this.config.outDir && !existsSync(this.config.outDir)) {
151
175
  mkdirSync(this.config.outDir, { recursive: true });
@@ -788,6 +812,14 @@ class Pipeline {
788
812
  );
789
813
  }
790
814
 
815
+ // Issue #558: Inject cross-file data for const inference
816
+ if (this.cppDetected && this.accumulatedModifications.size > 0) {
817
+ this.codeGenerator.setCrossFileModifications(
818
+ this.accumulatedModifications,
819
+ this.accumulatedParamLists,
820
+ );
821
+ }
822
+
791
823
  const code = this.codeGenerator.generate(
792
824
  tree,
793
825
  this.symbolTable,
@@ -822,6 +854,27 @@ class Pipeline {
822
854
  }
823
855
  this.passByValueParamsCollectors.set(file.path, passByValueCopy);
824
856
 
857
+ // Issue #558: Collect modifications and param lists for cross-file const inference
858
+ if (this.cppDetected) {
859
+ const fileModifications = this.codeGenerator.getModifiedParameters();
860
+ for (const [funcName, params] of fileModifications) {
861
+ const existing = this.accumulatedModifications.get(funcName);
862
+ if (existing) {
863
+ for (const param of params) {
864
+ existing.add(param);
865
+ }
866
+ } else {
867
+ this.accumulatedModifications.set(funcName, new Set(params));
868
+ }
869
+ }
870
+ const fileParamLists = this.codeGenerator.getFunctionParamLists();
871
+ for (const [funcName, params] of fileParamLists) {
872
+ if (!this.accumulatedParamLists.has(funcName)) {
873
+ this.accumulatedParamLists.set(funcName, [...params]);
874
+ }
875
+ }
876
+ }
877
+
825
878
  // Issue #424: Store user includes for header generation
826
879
  // These may define macros used in array dimensions
827
880
  // Issue #461: Transform .cnx includes to .h for header generation
@@ -63,6 +63,13 @@ class Project {
63
63
  * Compile the project
64
64
  */
65
65
  async compile(): Promise<IProjectResult> {
66
+ // Issue #558: For C++ mode, use Pipeline.run() to enable cross-file const inference.
67
+ // This is needed because struct parameters passed to modifying functions in other
68
+ // files must not be marked const.
69
+ if (this.config.cppRequired) {
70
+ return this.compileWithPipeline();
71
+ }
72
+
66
73
  // Compile each input file individually to avoid cross‑file symbol leakage.
67
74
  const results: any[] = [];
68
75
  // Build the full list of .cnx/.cnext files from srcDirs and explicit files
@@ -175,6 +182,30 @@ class Project {
175
182
 
176
183
  return aggregate;
177
184
  }
185
+
186
+ /**
187
+ * Issue #558: Compile using Pipeline.run() for cross-file const inference in C++ mode.
188
+ */
189
+ private async compileWithPipeline(): Promise<IProjectResult> {
190
+ const pipelineResult = await this.pipeline.run();
191
+
192
+ // Convert Pipeline result to Project result format
193
+ return {
194
+ success: pipelineResult.success,
195
+ filesProcessed: pipelineResult.filesProcessed,
196
+ symbolsCollected: pipelineResult.symbolsCollected,
197
+ conflicts: pipelineResult.conflicts,
198
+ errors: pipelineResult.errors.map((e) => {
199
+ if (typeof e === "string") return e;
200
+ const path = (e as any).sourcePath ?? "";
201
+ return path
202
+ ? `${path}:${e.line}:${e.column} ${e.message}`
203
+ : `${e.line}:${e.column} ${e.message}`;
204
+ }),
205
+ warnings: pipelineResult.warnings,
206
+ outputFiles: pipelineResult.outputFiles,
207
+ };
208
+ }
178
209
  /**
179
210
  * Get the symbol table (for testing/inspection)
180
211
  */