c-next 0.1.20 → 0.1.22

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.20",
3
+ "version": "0.1.22",
4
4
  "description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -3058,9 +3058,10 @@ export default class CodeGenerator implements IOrchestrator {
3058
3058
  }
3059
3059
 
3060
3060
  /**
3061
- * Check if a type name is a user-defined struct
3061
+ * Issue #322: Check if a type name is a user-defined struct
3062
+ * Part of IOrchestrator interface.
3062
3063
  */
3063
- private isStructType(typeName: string): boolean {
3064
+ isStructType(typeName: string): boolean {
3064
3065
  return this.typeResolver!.isStructType(typeName);
3065
3066
  }
3066
3067
 
@@ -7999,10 +8000,12 @@ export default class CodeGenerator implements IOrchestrator {
7999
8000
  currentIdentifier = memberName; // Track for .length lookups
8000
8001
  isGlobalAccess = true; // Mark that we're in a global access chain
8001
8002
  // Issue #304: Check if this is a C++ scope symbol (namespace, class, enum)
8002
- // Issue #314: In C++ mode, global.X.method() should always use :: syntax
8003
- // because the programmer is explicitly requesting C++ static method access,
8004
- // even for undeclared external symbols (e.g., Arduino's Serial class)
8005
- if (this.isCppScopeSymbol(memberName) || this.cppMode) {
8003
+ // These require :: syntax for member access. Variable symbols (including
8004
+ // object instances like Arduino's extern HardwareSerial Serial;) should
8005
+ // use . syntax, not :: syntax.
8006
+ // Issue #321: Removed || this.cppMode override - it was causing all symbols
8007
+ // to use :: in C++ mode, even object instances that need . syntax.
8008
+ if (this.isCppScopeSymbol(memberName)) {
8006
8009
  isCppAccessChain = true;
8007
8010
  }
8008
8011
  // Check if this first identifier is a register
@@ -72,6 +72,9 @@ interface IOrchestrator {
72
72
  /** Check if a function is defined in C-Next (vs C headers) */
73
73
  isCNextFunction(name: string): boolean;
74
74
 
75
+ /** Issue #322: Check if a type is a struct type */
76
+ isStructType(typeName: string): boolean;
77
+
75
78
  /** Get the raw type name without C conversion */
76
79
  getTypeName(ctx: Parser.TypeContext): string;
77
80
 
@@ -147,8 +147,40 @@ const generateFunctionCall = (
147
147
  }
148
148
 
149
149
  if (!isCNextFunc) {
150
- // C function: pass-by-value, just generate the expression
151
- const argCode = orchestrator.generateExpression(e);
150
+ // C/C++ function: pass-by-value, just generate the expression
151
+ let argCode = orchestrator.generateExpression(e);
152
+
153
+ // Issue #322: Check if parameter expects a pointer and argument is a struct
154
+ // If so, automatically add & to pass the address
155
+ if (targetParam?.baseType?.endsWith("*")) {
156
+ // Try getExpressionType first
157
+ let argType = orchestrator.getExpressionType(e);
158
+
159
+ // Issue #322: If getExpressionType returns null (e.g., for this.member),
160
+ // fall back to looking up the generated code in the type registry
161
+ if (!argType && !argCode.startsWith("&")) {
162
+ // The argCode is already resolved (e.g., ConfigManager_config)
163
+ // Look it up directly in the type registry
164
+ const typeInfo = input.typeRegistry.get(argCode);
165
+ if (typeInfo) {
166
+ argType = typeInfo.baseType;
167
+ }
168
+ }
169
+
170
+ // Add & if argument is a struct type (not already a pointer)
171
+ // Don't add & if the expression already has & prefix
172
+ // Don't add & if argument is already a pointer or array
173
+ if (
174
+ argType &&
175
+ !argType.endsWith("*") &&
176
+ !argCode.startsWith("&") &&
177
+ !targetParam.isArray &&
178
+ orchestrator.isStructType(argType)
179
+ ) {
180
+ argCode = `&${argCode}`;
181
+ }
182
+ }
183
+
152
184
  // Issue #304: Wrap with static_cast if C++ enum class → integer
153
185
  return wrapWithCppEnumCast(
154
186
  argCode,
@@ -1,4 +1,5 @@
1
1
  import TSymbolKind from "./TSymbolKind";
2
+ import TLanguage from "./TLanguage";
2
3
 
3
4
  /**
4
5
  * Symbol information for IDE features (autocomplete, hover)
@@ -23,6 +24,10 @@ interface ISymbolInfo {
23
24
  line: number;
24
25
  /** Array size or bit width */
25
26
  size?: number;
27
+ /** Source file path where this symbol is defined */
28
+ sourceFile?: string;
29
+ /** Language of the source file */
30
+ language?: TLanguage;
26
31
  }
27
32
 
28
33
  export default ISymbolInfo;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Language type for source traceability in IDE features
3
+ */
4
+ type TLanguage = "cnext" | "c" | "cpp";
5
+
6
+ export default TLanguage;
@@ -62,6 +62,8 @@ class Pipeline {
62
62
  private cppDetected: boolean;
63
63
  /** Issue #220: Store SymbolCollector per file for header generation */
64
64
  private symbolCollectors: Map<string, SymbolCollector> = new Map();
65
+ /** Issue #321: Track processed headers to avoid cycles during recursive include resolution */
66
+ private processedHeaders: Set<string> = new Set();
65
67
  /** Issue #280: Store pass-by-value params per file for header generation */
66
68
  private passByValueParamsCollectors: Map<
67
69
  string,
@@ -320,8 +322,17 @@ class Pipeline {
320
322
 
321
323
  /**
322
324
  * Stage 2: Collect symbols from C/C++ headers
325
+ * Issue #321: Now recursively processes #include directives to handle
326
+ * nested headers (e.g., Arduino's HardwareSerial.h including Stream.h)
323
327
  */
324
328
  private async collectHeaderSymbols(file: IDiscoveredFile): Promise<void> {
329
+ // Issue #321: Check if already processed to avoid cycles
330
+ const absolutePath = resolve(file.path);
331
+ if (this.processedHeaders.has(absolutePath)) {
332
+ return;
333
+ }
334
+ this.processedHeaders.add(absolutePath);
335
+
325
336
  // Check cache first
326
337
  if (this.cacheManager?.isValid(file.path)) {
327
338
  const cached = this.cacheManager.getSymbols(file.path);
@@ -348,35 +359,84 @@ class Pipeline {
348
359
  }
349
360
  }
350
361
 
351
- let content: string;
362
+ // Issue #321: Read original content FIRST to extract includes before preprocessing
363
+ // The preprocessor expands/removes #include directives, so we need the original
364
+ const originalContent = readFileSync(file.path, "utf-8");
365
+
366
+ // Issue #328: Skip headers generated by C-Next Transpiler
367
+ // During incremental migration, generated .h files may be discovered via #include
368
+ // recursion from C++ files. These headers contain the same symbols as their .cnx
369
+ // source files, so including them would cause false symbol conflicts.
370
+ if (originalContent.includes("Generated by C-Next Transpiler")) {
371
+ if (this.config.debugMode) {
372
+ console.log(`[DEBUG] Skipping C-Next generated header: ${file.path}`);
373
+ }
374
+ return;
375
+ }
352
376
 
353
- // Preprocess if enabled
354
- if (this.config.preprocess && this.preprocessor.isAvailable()) {
355
- const preprocessResult = await this.preprocessor.preprocess(file.path, {
356
- includePaths: this.config.includeDirs,
357
- defines: this.config.defines,
358
- keepLineDirectives: false,
359
- });
377
+ // Issue #321: Recursively process #include directives in headers
378
+ // This ensures symbols from nested headers (like Arduino's extern HardwareSerial Serial)
379
+ // are properly collected even when included transitively
380
+ const includes = IncludeDiscovery.extractIncludes(originalContent);
381
+ const headerDir = dirname(absolutePath);
382
+ const searchPaths = [headerDir, ...this.config.includeDirs];
360
383
 
361
- if (!preprocessResult.success) {
362
- this.warnings.push(
363
- `Preprocessor warning for ${file.path}: ${preprocessResult.error}`,
384
+ if (this.config.debugMode) {
385
+ console.log(`[DEBUG] Processing includes in ${file.path}:`);
386
+ console.log(`[DEBUG] Search paths: ${searchPaths.join(", ")}`);
387
+ }
388
+
389
+ for (const includePath of includes) {
390
+ const resolved = IncludeDiscovery.resolveInclude(
391
+ includePath,
392
+ searchPaths,
393
+ );
394
+ if (this.config.debugMode) {
395
+ console.log(
396
+ `[DEBUG] #include "${includePath}" → ${resolved || "NOT FOUND"}`,
364
397
  );
365
- content = readFileSync(file.path, "utf-8");
366
- } else {
367
- content = preprocessResult.content;
368
398
  }
369
- } else {
370
- content = readFileSync(file.path, "utf-8");
399
+ if (resolved) {
400
+ const includedFile = FileDiscovery.discoverFile(resolved);
401
+ if (
402
+ includedFile &&
403
+ (includedFile.type === EFileType.CHeader ||
404
+ includedFile.type === EFileType.CppHeader)
405
+ ) {
406
+ if (this.config.debugMode) {
407
+ console.log(
408
+ `[DEBUG] → Recursively processing ${includedFile.path}`,
409
+ );
410
+ }
411
+ await this.collectHeaderSymbols(includedFile);
412
+ }
413
+ }
371
414
  }
372
415
 
416
+ // Issue #321: Parse with ORIGINAL content, not preprocessed content
417
+ // The preprocessor expands includes and can lose class definitions.
418
+ // We need the original content to properly detect C++ syntax and parse classes.
419
+ // Note: Preprocessing was previously done here but is no longer needed for symbol collection.
420
+
373
421
  // Parse based on file type
374
422
  if (file.type === EFileType.CHeader) {
375
- this.parseCHeader(content, file.path);
423
+ if (this.config.debugMode) {
424
+ console.log(`[DEBUG] Parsing C header: ${file.path}`);
425
+ }
426
+ this.parseCHeader(originalContent, file.path);
376
427
  } else if (file.type === EFileType.CppHeader) {
377
428
  // Issue #211: .hpp files are always C++
378
429
  this.cppDetected = true;
379
- this.parseCppHeader(content, file.path);
430
+ if (this.config.debugMode) {
431
+ console.log(`[DEBUG] Parsing C++ header: ${file.path}`);
432
+ }
433
+ this.parseCppHeader(originalContent, file.path);
434
+ }
435
+
436
+ // Debug: Show symbols found
437
+ if (this.config.debugMode) {
438
+ const symbols = this.symbolTable.getSymbolsByFile(file.path);
439
+ console.log(`[DEBUG] Found ${symbols.length} symbols in ${file.path}`);
380
440
  }
381
441
 
382
442
  // After parsing, cache the results
@@ -102,6 +102,9 @@ class CppSymbolCollector {
102
102
  ? `${this.currentNamespace}::${name}`
103
103
  : name;
104
104
 
105
+ // Issue #322: Extract function parameters
106
+ const params = this.extractFunctionParameters(declarator);
107
+
105
108
  this.symbols.push({
106
109
  name: fullName,
107
110
  kind: ESymbolKind.Function,
@@ -111,6 +114,7 @@ class CppSymbolCollector {
111
114
  sourceLanguage: ESourceLanguage.Cpp,
112
115
  isExported: true,
113
116
  parent: this.currentNamespace,
117
+ parameters: params.length > 0 ? params : undefined,
114
118
  });
115
119
  }
116
120
 
@@ -214,6 +218,11 @@ class CppSymbolCollector {
214
218
  ? `${this.currentNamespace}::${name}`
215
219
  : name;
216
220
 
221
+ // Issue #322: Extract parameters for function declarations
222
+ const params = isFunction
223
+ ? this.extractFunctionParameters(declarator)
224
+ : [];
225
+
217
226
  this.symbols.push({
218
227
  name: fullName,
219
228
  kind: isFunction ? ESymbolKind.Function : ESymbolKind.Variable,
@@ -224,6 +233,7 @@ class CppSymbolCollector {
224
233
  isExported: true,
225
234
  isDeclaration: isFunction,
226
235
  parent: this.currentNamespace,
236
+ parameters: params.length > 0 ? params : undefined,
227
237
  });
228
238
  }
229
239
  }
@@ -280,10 +290,39 @@ class CppSymbolCollector {
280
290
  * Collect a single member declaration
281
291
  */
282
292
  private collectMemberDeclaration(className: string, memberDecl: any): void {
283
- // Skip function definitions, access specifiers, etc.
284
- // We only want data members
293
+ const line = memberDecl.start?.line ?? 0;
294
+
295
+ // Issue #322: Check for inline function definition within the class
296
+ const funcDef = memberDecl.functionDefinition?.();
297
+ if (funcDef) {
298
+ const declarator = funcDef.declarator?.();
299
+ if (declarator) {
300
+ const funcName = this.extractDeclaratorName(declarator);
301
+ if (funcName) {
302
+ const declSpecSeq = funcDef.declSpecifierSeq?.();
303
+ const returnType = declSpecSeq
304
+ ? this.extractTypeFromDeclSpecSeq(declSpecSeq)
305
+ : "void";
306
+ const params = this.extractFunctionParameters(declarator);
307
+ const fullName = `${className}::${funcName}`;
308
+
309
+ this.symbols.push({
310
+ name: fullName,
311
+ kind: ESymbolKind.Function,
312
+ type: returnType,
313
+ sourceFile: this.sourceFile,
314
+ sourceLine: line,
315
+ sourceLanguage: ESourceLanguage.Cpp,
316
+ isExported: true,
317
+ parent: className,
318
+ parameters: params.length > 0 ? params : undefined,
319
+ });
320
+ }
321
+ }
322
+ return;
323
+ }
285
324
 
286
- // Get member declaration list
325
+ // Get member declaration list (for data members and function declarations)
287
326
  const declSpecSeq = memberDecl.declSpecifierSeq?.();
288
327
  if (!declSpecSeq) return;
289
328
 
@@ -300,8 +339,23 @@ class CppSymbolCollector {
300
339
  const fieldName = this.extractDeclaratorName(declarator);
301
340
  if (!fieldName) continue;
302
341
 
303
- // Check if this is a function (has parameters) - skip functions
342
+ // Issue #322: Collect member functions with their parameters
304
343
  if (this.declaratorIsFunction(declarator)) {
344
+ const params = this.extractFunctionParameters(declarator);
345
+ const fullName = `${className}::${fieldName}`;
346
+
347
+ this.symbols.push({
348
+ name: fullName,
349
+ kind: ESymbolKind.Function,
350
+ type: fieldType,
351
+ sourceFile: this.sourceFile,
352
+ sourceLine: line,
353
+ sourceLanguage: ESourceLanguage.Cpp,
354
+ isExported: true,
355
+ isDeclaration: true,
356
+ parent: className,
357
+ parameters: params.length > 0 ? params : undefined,
358
+ });
305
359
  continue;
306
360
  }
307
361
 
@@ -508,6 +562,166 @@ class CppSymbolCollector {
508
562
  return false;
509
563
  }
510
564
 
565
+ /**
566
+ * Issue #322: Extract function parameters from a declarator.
567
+ * Returns an array of parameter info objects with name, type, and pointer flag.
568
+ */
569
+ private extractFunctionParameters(declarator: any): Array<{
570
+ name: string;
571
+ type: string;
572
+ isConst: boolean;
573
+ isArray: boolean;
574
+ }> {
575
+ const params: Array<{
576
+ name: string;
577
+ type: string;
578
+ isConst: boolean;
579
+ isArray: boolean;
580
+ }> = [];
581
+
582
+ // Find parametersAndQualifiers from the declarator
583
+ let paramsAndQuals: any = null;
584
+ const ptrDecl = declarator.pointerDeclarator?.();
585
+ if (ptrDecl) {
586
+ const noPtr = ptrDecl.noPointerDeclarator?.();
587
+ paramsAndQuals = noPtr?.parametersAndQualifiers?.();
588
+ }
589
+ if (!paramsAndQuals) {
590
+ const noPtr = declarator.noPointerDeclarator?.();
591
+ paramsAndQuals = noPtr?.parametersAndQualifiers?.();
592
+ }
593
+ if (!paramsAndQuals) {
594
+ return params;
595
+ }
596
+
597
+ // Get parameterDeclarationClause
598
+ const paramClause = paramsAndQuals.parameterDeclarationClause?.();
599
+ if (!paramClause) {
600
+ return params;
601
+ }
602
+
603
+ // Get parameterDeclarationList
604
+ const paramList = paramClause.parameterDeclarationList?.();
605
+ if (!paramList) {
606
+ return params;
607
+ }
608
+
609
+ // Iterate over parameterDeclaration entries
610
+ for (const paramDecl of paramList.parameterDeclaration?.() ?? []) {
611
+ const paramInfo = this.extractParameterInfo(paramDecl);
612
+ if (paramInfo) {
613
+ params.push(paramInfo);
614
+ }
615
+ }
616
+
617
+ return params;
618
+ }
619
+
620
+ /**
621
+ * Issue #322: Extract type and name info from a single parameter declaration.
622
+ */
623
+ private extractParameterInfo(paramDecl: any): {
624
+ name: string;
625
+ type: string;
626
+ isConst: boolean;
627
+ isArray: boolean;
628
+ } | null {
629
+ // Get the type from declSpecifierSeq
630
+ const declSpecSeq = paramDecl.declSpecifierSeq?.();
631
+ if (!declSpecSeq) {
632
+ return null;
633
+ }
634
+
635
+ let baseType = this.extractTypeFromDeclSpecSeq(declSpecSeq);
636
+ let isConst = false;
637
+ let isPointer = false;
638
+
639
+ // Check for const qualifier
640
+ const declText = declSpecSeq.getText();
641
+ if (declText.includes("const")) {
642
+ isConst = true;
643
+ }
644
+
645
+ // Check for pointer in declarator or abstractDeclarator
646
+ const declarator = paramDecl.declarator?.();
647
+ const abstractDecl = paramDecl.abstractDeclarator?.();
648
+
649
+ if (declarator) {
650
+ // Check if declarator has pointer operator
651
+ if (this.declaratorHasPointer(declarator)) {
652
+ isPointer = true;
653
+ }
654
+ }
655
+ if (abstractDecl) {
656
+ // Abstract declarator (no name) - check for pointer
657
+ if (this.abstractDeclaratorHasPointer(abstractDecl)) {
658
+ isPointer = true;
659
+ }
660
+ }
661
+
662
+ // If pointer, append * to the type
663
+ if (isPointer) {
664
+ baseType = baseType + "*";
665
+ }
666
+
667
+ // Get parameter name (may be empty for abstract declarators)
668
+ let paramName = "";
669
+ if (declarator) {
670
+ const name = this.extractDeclaratorName(declarator);
671
+ if (name) {
672
+ paramName = name;
673
+ }
674
+ }
675
+
676
+ return {
677
+ name: paramName,
678
+ type: baseType,
679
+ isConst,
680
+ isArray: false, // Could be enhanced to detect arrays
681
+ };
682
+ }
683
+
684
+ /**
685
+ * Issue #322: Check if a declarator contains a pointer operator.
686
+ */
687
+ private declaratorHasPointer(declarator: any): boolean {
688
+ const ptrDecl = declarator.pointerDeclarator?.();
689
+ if (ptrDecl) {
690
+ // Check for pointerOperator children
691
+ const ptrOps = ptrDecl.pointerOperator?.();
692
+ if (ptrOps && ptrOps.length > 0) {
693
+ return true;
694
+ }
695
+ // Also check getText for *
696
+ if (ptrDecl.getText().includes("*")) {
697
+ return true;
698
+ }
699
+ }
700
+ return false;
701
+ }
702
+
703
+ /**
704
+ * Issue #322: Check if an abstract declarator (pointer without name) contains a pointer.
705
+ */
706
+ private abstractDeclaratorHasPointer(abstractDecl: any): boolean {
707
+ // Check for pointerAbstractDeclarator
708
+ const ptrAbstract = abstractDecl.pointerAbstractDeclarator?.();
709
+ if (ptrAbstract) {
710
+ const ptrOps = ptrAbstract.pointerOperator?.();
711
+ if (ptrOps && ptrOps.length > 0) {
712
+ return true;
713
+ }
714
+ if (ptrAbstract.getText().includes("*")) {
715
+ return true;
716
+ }
717
+ }
718
+ // Simple check for * in the text
719
+ if (abstractDecl.getText().includes("*")) {
720
+ return true;
721
+ }
722
+ return false;
723
+ }
724
+
511
725
  private extractTypeFromDeclSpecSeq(declSpecSeq: any): string {
512
726
  const parts: string[] = [];
513
727