c-next 0.1.16 → 0.1.18

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.16",
3
+ "version": "0.1.18",
4
4
  "description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -681,6 +681,45 @@ export default class CodeGenerator implements IOrchestrator {
681
681
  return this.symbols!.knownEnums;
682
682
  }
683
683
 
684
+ /**
685
+ * Issue #304: Check if we're generating C++ output.
686
+ * Part of IOrchestrator interface.
687
+ */
688
+ isCppMode(): boolean {
689
+ return this.cppMode;
690
+ }
691
+
692
+ /**
693
+ * Issue #304: Check if a type is a C++ enum class (scoped enum).
694
+ * These require explicit casts to integer types in C++.
695
+ * Part of IOrchestrator interface.
696
+ */
697
+ isCppEnumClass(typeName: string): boolean {
698
+ if (!this.symbolTable) {
699
+ return false;
700
+ }
701
+
702
+ const symbols = this.symbolTable.getOverloads(typeName);
703
+ for (const sym of symbols) {
704
+ if (
705
+ sym.sourceLanguage === ESourceLanguage.Cpp &&
706
+ sym.kind === ESymbolKind.Enum
707
+ ) {
708
+ return true;
709
+ }
710
+ }
711
+
712
+ return false;
713
+ }
714
+
715
+ /**
716
+ * Issue #304: Get the type of an expression.
717
+ * Part of IOrchestrator interface.
718
+ */
719
+ getExpressionType(ctx: Parser.ExpressionContext): string | null {
720
+ return this.typeResolver!.getExpressionType(ctx);
721
+ }
722
+
684
723
  /**
685
724
  * Generate a block (curly braces with statements).
686
725
  * Part of IOrchestrator interface (ADR-053 A3).
@@ -1159,6 +1198,94 @@ export default class CodeGenerator implements IOrchestrator {
1159
1198
  return false;
1160
1199
  }
1161
1200
 
1201
+ /**
1202
+ * Issue #294: Check if an identifier is a known scope
1203
+ * Checks both the local SymbolCollector (current file) and the global SymbolTable
1204
+ * (all files including includes). This ensures cross-file scope references are
1205
+ * properly validated.
1206
+ */
1207
+ private isKnownScope(name: string): boolean {
1208
+ // Check local file's symbol collector first
1209
+ if (this.symbols?.knownScopes.has(name)) {
1210
+ return true;
1211
+ }
1212
+
1213
+ // Check global symbol table for scopes from included files
1214
+ if (this.symbolTable) {
1215
+ const symbols = this.symbolTable.getOverloads(name);
1216
+ if (symbols.some((sym) => sym.kind === ESymbolKind.Namespace)) {
1217
+ return true;
1218
+ }
1219
+ }
1220
+
1221
+ return false;
1222
+ }
1223
+
1224
+ /**
1225
+ * Issue #304: Check if a name is a C++ scope-like symbol that requires :: syntax
1226
+ * This includes C++ namespaces, classes, and enum classes (scoped enums).
1227
+ * Returns true if the symbol comes from C++ and needs :: for member access.
1228
+ */
1229
+ private isCppScopeSymbol(name: string): boolean {
1230
+ if (!this.symbolTable) {
1231
+ return false;
1232
+ }
1233
+
1234
+ const symbols = this.symbolTable.getOverloads(name);
1235
+ for (const sym of symbols) {
1236
+ // Only consider C++ symbols
1237
+ if (sym.sourceLanguage !== ESourceLanguage.Cpp) {
1238
+ continue;
1239
+ }
1240
+
1241
+ // C++ namespaces, classes, and enums (enum class) need :: syntax
1242
+ if (
1243
+ sym.kind === ESymbolKind.Namespace ||
1244
+ sym.kind === ESymbolKind.Class ||
1245
+ sym.kind === ESymbolKind.Enum
1246
+ ) {
1247
+ return true;
1248
+ }
1249
+ }
1250
+
1251
+ return false;
1252
+ }
1253
+
1254
+ /**
1255
+ * Issue #304: Get the appropriate scope separator for C++ vs C/C-Next.
1256
+ * C++ uses :: for scope resolution, C/C-Next uses _ (underscore).
1257
+ */
1258
+ private getScopeSeparator(isCppContext: boolean): string {
1259
+ return isCppContext ? "::" : "_";
1260
+ }
1261
+
1262
+ /**
1263
+ * Issue #304: Check if a type name is from a C++ header
1264
+ * Used to determine whether to use {} or {0} for initialization.
1265
+ * C++ types with constructors may fail with {0} but work with {}.
1266
+ */
1267
+ private isCppType(typeName: string): boolean {
1268
+ if (!this.symbolTable) {
1269
+ return false;
1270
+ }
1271
+
1272
+ const symbols = this.symbolTable.getOverloads(typeName);
1273
+ for (const sym of symbols) {
1274
+ if (sym.sourceLanguage === ESourceLanguage.Cpp) {
1275
+ // Any C++ struct, class, or user-defined type
1276
+ if (
1277
+ sym.kind === ESymbolKind.Struct ||
1278
+ sym.kind === ESymbolKind.Class ||
1279
+ sym.kind === ESymbolKind.Type
1280
+ ) {
1281
+ return true;
1282
+ }
1283
+ }
1284
+ }
1285
+
1286
+ return false;
1287
+ }
1288
+
1162
1289
  /**
1163
1290
  * Generate C code from a C-Next program
1164
1291
  * @param tree The parsed C-Next program
@@ -3569,14 +3696,6 @@ export default class CodeGenerator implements IOrchestrator {
3569
3696
  this.typeResolver!.validateLiteralFitsType(literalText, targetType);
3570
3697
  }
3571
3698
 
3572
- /**
3573
- * ADR-024: Get the type of an expression for type checking.
3574
- * Returns the inferred type or null if type cannot be determined.
3575
- */
3576
- private getExpressionType(ctx: Parser.ExpressionContext): string | null {
3577
- return this.typeResolver!.getExpressionType(ctx);
3578
- }
3579
-
3580
3699
  /**
3581
3700
  * ADR-024: Get the type of a postfix expression.
3582
3701
  */
@@ -5372,9 +5491,22 @@ export default class CodeGenerator implements IOrchestrator {
5372
5491
  return `(${typeName})0`;
5373
5492
  }
5374
5493
 
5494
+ // Issue #304: C++ types with constructors may fail with {0}
5495
+ // Use {} for C++ types, {0} for C types
5496
+ if (this.isCppType(typeName)) {
5497
+ return "{}";
5498
+ }
5499
+
5375
5500
  return "{0}";
5376
5501
  }
5377
5502
 
5503
+ // Issue #295: C++ template types use value initialization {}
5504
+ // Template types like FlexCAN_T4<CAN1, RX_SIZE_256, TX_SIZE_16> are non-trivial
5505
+ // class types that cannot be initialized with = 0
5506
+ if (typeCtx.templateType()) {
5507
+ return "{}";
5508
+ }
5509
+
5378
5510
  // Primitive types
5379
5511
  if (typeCtx.primitiveType()) {
5380
5512
  const primType = typeCtx.primitiveType()!.getText();
@@ -5860,7 +5992,7 @@ export default class CodeGenerator implements IOrchestrator {
5860
5992
  const fieldName = identifiers[3].getText();
5861
5993
 
5862
5994
  // Check if first identifier is a scope
5863
- if (this.symbols!.knownScopes.has(scopeName)) {
5995
+ if (this.isKnownScope(scopeName)) {
5864
5996
  // ADR-016: Validate visibility before allowing cross-scope access
5865
5997
  this.validateCrossScopeVisibility(scopeName, regName);
5866
5998
  const fullRegName = `${scopeName}_${regName}`;
@@ -5997,7 +6129,7 @@ export default class CodeGenerator implements IOrchestrator {
5997
6129
  const firstId = identifiers[0].getText();
5998
6130
  // Check if this is a scoped register: Scope.Register.Member[bit]
5999
6131
  const scopedRegName =
6000
- identifiers.length >= 3 && this.symbols!.knownScopes.has(firstId)
6132
+ identifiers.length >= 3 && this.isKnownScope(firstId)
6001
6133
  ? `${firstId}_${identifiers[1].getText()}`
6002
6134
  : null;
6003
6135
  const isScopedRegister =
@@ -6022,7 +6154,7 @@ export default class CodeGenerator implements IOrchestrator {
6022
6154
  let exprIndex = 0;
6023
6155
 
6024
6156
  // Check if first identifier is a scope for special handling
6025
- const isCrossScope = this.symbols!.knownScopes.has(firstId);
6157
+ const isCrossScope = this.isKnownScope(firstId);
6026
6158
 
6027
6159
  // Bug #8: Track struct types to detect bit access through chains
6028
6160
  // e.g., items[0].byte[7] where byte is u8 - final [7] is bit access
@@ -6238,10 +6370,7 @@ export default class CodeGenerator implements IOrchestrator {
6238
6370
  // Pattern 2: Scope.GPIO7.DR_SET[bit] - 3 identifiers, first is scope
6239
6371
  let fullName: string;
6240
6372
  const leadingId = identifiers[0].getText();
6241
- if (
6242
- this.symbols!.knownScopes.has(leadingId) &&
6243
- identifiers.length >= 3
6244
- ) {
6373
+ if (this.isKnownScope(leadingId) && identifiers.length >= 3) {
6245
6374
  // Scoped register: Scope.Register.Member
6246
6375
  const scopeName = leadingId;
6247
6376
  const regName = identifiers[1].getText();
@@ -6302,10 +6431,7 @@ export default class CodeGenerator implements IOrchestrator {
6302
6431
  // Issue #187: Generate width-appropriate memory access
6303
6432
  // Determine register name for base address lookup
6304
6433
  let regName: string;
6305
- if (
6306
- this.symbols!.knownScopes.has(leadingId) &&
6307
- identifiers.length >= 3
6308
- ) {
6434
+ if (this.isKnownScope(leadingId) && identifiers.length >= 3) {
6309
6435
  regName = `${leadingId}_${identifiers[1].getText()}`;
6310
6436
  } else {
6311
6437
  regName = leadingId;
@@ -6386,7 +6512,7 @@ export default class CodeGenerator implements IOrchestrator {
6386
6512
  // Read-write: need read-modify-write
6387
6513
  return `${regName} = (${regName} & ~(1 << ${bitIndex})) | (${this.foldBooleanToInt(value)} << ${bitIndex});`;
6388
6514
  }
6389
- } else if (this.symbols!.knownScopes.has(firstId)) {
6515
+ } else if (this.isKnownScope(firstId)) {
6390
6516
  // ADR-016: Validate visibility before allowing cross-scope access
6391
6517
  const memberName = parts[1];
6392
6518
  this.validateCrossScopeVisibility(firstId, memberName);
@@ -7175,18 +7301,24 @@ export default class CodeGenerator implements IOrchestrator {
7175
7301
  const identifiers = ctx.IDENTIFIER();
7176
7302
  const parts = identifiers.map((id) => id.getText());
7177
7303
  const firstId = parts[0];
7304
+ // Issue #304: Check if first identifier is a C++ scope symbol
7305
+ const isCppAccess = this.isCppScopeSymbol(firstId);
7178
7306
  // Check if first identifier is a register
7179
7307
  if (this.symbols!.knownRegisters.has(firstId)) {
7180
7308
  // Register member access: GPIO7.DR_SET -> GPIO7_DR_SET
7181
7309
  return parts.join("_");
7182
7310
  }
7183
7311
  // Check if first identifier is a scope
7184
- if (this.symbols!.knownScopes.has(firstId)) {
7312
+ if (this.isKnownScope(firstId)) {
7185
7313
  // ADR-016: Validate visibility before allowing cross-scope access
7186
7314
  const memberName = parts[1];
7187
7315
  this.validateCrossScopeVisibility(firstId, memberName);
7188
- // Scope member access: global.Counter.value -> Counter_value
7189
- return parts.join("_");
7316
+ // Issue #304: Use :: for C++ namespaces, _ for C-Next scopes
7317
+ return parts.join(this.getScopeSeparator(isCppAccess));
7318
+ }
7319
+ // Issue #304: C++ class/enum access uses ::
7320
+ if (isCppAccess) {
7321
+ return parts.join("::");
7190
7322
  }
7191
7323
  // Non-register, non-scope member access: obj.field
7192
7324
  return parts.join(".");
@@ -7200,6 +7332,8 @@ export default class CodeGenerator implements IOrchestrator {
7200
7332
  const parts = identifiers.map((id) => id.getText());
7201
7333
  const expressions = ctx.expression();
7202
7334
  const firstId = parts[0];
7335
+ // Issue #304: Check if first identifier is a C++ scope symbol
7336
+ const isCppAccess = this.isCppScopeSymbol(firstId);
7203
7337
 
7204
7338
  // Handle single vs multi-expression (bit range) syntax
7205
7339
  let indexExpr: string;
@@ -7220,15 +7354,21 @@ export default class CodeGenerator implements IOrchestrator {
7220
7354
  }
7221
7355
 
7222
7356
  // Check if first identifier is a scope
7223
- if (this.symbols!.knownScopes.has(firstId)) {
7357
+ if (this.isKnownScope(firstId)) {
7224
7358
  // ADR-016: Validate visibility before allowing cross-scope access
7225
7359
  const memberName = parts[1];
7226
7360
  this.validateCrossScopeVisibility(firstId, memberName);
7227
- // Scope array access: global.Counter.data[0] -> Counter_data[0]
7228
- const scopedName = parts.join("_");
7361
+ // Issue #304: Use :: for C++ namespaces, _ for C-Next scopes
7362
+ const scopedName = parts.join(this.getScopeSeparator(isCppAccess));
7229
7363
  return `${scopedName}[${indexExpr}]`;
7230
7364
  }
7231
7365
 
7366
+ // Issue #304: C++ class/enum access uses ::
7367
+ if (isCppAccess) {
7368
+ const baseName = parts.join("::");
7369
+ return `${baseName}[${indexExpr}]`;
7370
+ }
7371
+
7232
7372
  // Non-register, non-scope array access
7233
7373
  const baseName = parts.join(".");
7234
7374
  return `${baseName}[${indexExpr}]`;
@@ -7757,6 +7897,8 @@ export default class CodeGenerator implements IOrchestrator {
7757
7897
  let subscriptDepth = 0;
7758
7898
  // ADR-016: Track if we're in a global. access chain (skips scope/enum/register validation)
7759
7899
  let isGlobalAccess = false;
7900
+ // Issue #304: Track if we're accessing C++ symbols that need :: syntax
7901
+ let isCppAccessChain = false;
7760
7902
 
7761
7903
  for (let i = 0; i < ops.length; i++) {
7762
7904
  const op = ops[i];
@@ -7770,6 +7912,10 @@ export default class CodeGenerator implements IOrchestrator {
7770
7912
  result = memberName;
7771
7913
  currentIdentifier = memberName; // Track for .length lookups
7772
7914
  isGlobalAccess = true; // Mark that we're in a global access chain
7915
+ // Issue #304: Check if this is a C++ scope symbol (namespace, class, enum)
7916
+ if (this.isCppScopeSymbol(memberName)) {
7917
+ isCppAccessChain = true;
7918
+ }
7773
7919
  // Check if this first identifier is a register
7774
7920
  if (this.symbols!.knownRegisters.has(memberName)) {
7775
7921
  isRegisterChain = true;
@@ -8046,7 +8192,7 @@ export default class CodeGenerator implements IOrchestrator {
8046
8192
  }
8047
8193
  }
8048
8194
  // Check if this is a scope member access: Scope.member (ADR-016)
8049
- else if (this.symbols!.knownScopes.has(result)) {
8195
+ else if (this.isKnownScope(result)) {
8050
8196
  // ADR-016: Skip validation if we're already in a global. access chain
8051
8197
  if (!isGlobalAccess) {
8052
8198
  // ADR-016: Prevent self-referential scope access - must use 'this.' inside own scope
@@ -8064,8 +8210,8 @@ export default class CodeGenerator implements IOrchestrator {
8064
8210
  }
8065
8211
  // ADR-016: Validate visibility before allowing cross-scope access
8066
8212
  this.validateCrossScopeVisibility(result, memberName);
8067
- // Transform Scope.member to Scope_member
8068
- result = `${result}_${memberName}`;
8213
+ // Issue #304: Use :: for C++ namespaces, _ for C-Next scopes
8214
+ result = `${result}${this.getScopeSeparator(isCppAccessChain)}${memberName}`;
8069
8215
  currentIdentifier = result; // Track for .length lookups
8070
8216
 
8071
8217
  // Check if this resolved identifier is a struct type for chained access
@@ -8093,8 +8239,8 @@ export default class CodeGenerator implements IOrchestrator {
8093
8239
  );
8094
8240
  }
8095
8241
  }
8096
- // Transform Enum.member to Enum_member
8097
- result = `${result}_${memberName}`;
8242
+ // Issue #304: Use :: for C++ enum classes, _ for C-Next enums
8243
+ result = `${result}${this.getScopeSeparator(isCppAccessChain)}${memberName}`;
8098
8244
  }
8099
8245
  // Check if this is a register member access: GPIO7.DR -> GPIO7_DR
8100
8246
  else if (this.symbols!.knownRegisters.has(result)) {
@@ -8276,7 +8422,7 @@ export default class CodeGenerator implements IOrchestrator {
8276
8422
  }
8277
8423
  }
8278
8424
  }
8279
- } else if (this.symbols!.knownScopes.has(result)) {
8425
+ } else if (this.isKnownScope(result)) {
8280
8426
  // ADR-016: Skip validation if we're already in a global. access chain
8281
8427
  if (!isGlobalAccess) {
8282
8428
  // ADR-016: Prevent self-referential scope access - must use 'this.' inside own scope
@@ -8294,8 +8440,8 @@ export default class CodeGenerator implements IOrchestrator {
8294
8440
  }
8295
8441
  // ADR-016: Validate visibility before allowing cross-scope access
8296
8442
  this.validateCrossScopeVisibility(result, memberName);
8297
- // Transform Scope.member to Scope_member
8298
- result = `${result}_${memberName}`;
8443
+ // Issue #304: Use :: for C++ namespaces, _ for C-Next scopes
8444
+ result = `${result}${this.getScopeSeparator(isCppAccessChain)}${memberName}`;
8299
8445
  currentIdentifier = result; // Track for .length lookups
8300
8446
  } else if (this.symbols!.knownEnums.has(result)) {
8301
8447
  // ADR-016: Inside a scope, accessing global enum requires global. prefix
@@ -8311,8 +8457,8 @@ export default class CodeGenerator implements IOrchestrator {
8311
8457
  );
8312
8458
  }
8313
8459
  }
8314
- // Transform Enum.member to Enum_member
8315
- result = `${result}_${memberName}`;
8460
+ // Issue #304: Use :: for C++ enum classes, _ for C-Next enums
8461
+ result = `${result}${this.getScopeSeparator(isCppAccessChain)}${memberName}`;
8316
8462
  } else if (this.symbols!.knownRegisters.has(result)) {
8317
8463
  // ADR-016: Inside a scope, accessing global register requires global. prefix
8318
8464
  // Exception: if the register belongs to the current scope, access is allowed
@@ -8349,6 +8495,14 @@ export default class CodeGenerator implements IOrchestrator {
8349
8495
  `Error: Unknown bitmap field '${memberName}' on type '${bitmapType}'`,
8350
8496
  );
8351
8497
  }
8498
+ }
8499
+ // Issue #304: C++ class/namespace static member access uses :: (e.g., CommandHandler::execute)
8500
+ // Also handles nested namespaces (hw::nested::configure) by checking if result already contains ::
8501
+ else if (
8502
+ isCppAccessChain &&
8503
+ (this.isCppScopeSymbol(result) || result.includes("::"))
8504
+ ) {
8505
+ result = `${result}::${memberName}`;
8352
8506
  } else {
8353
8507
  result = `${result}.${memberName}`;
8354
8508
  // Track this member for potential .length access (save BEFORE updating)
@@ -8608,6 +8762,12 @@ export default class CodeGenerator implements IOrchestrator {
8608
8762
  this,
8609
8763
  );
8610
8764
  this.applyEffects(result.effects);
8765
+
8766
+ // Issue #304: Transform NULL → nullptr in C++ mode
8767
+ if (result.code === "NULL" && this.cppMode) {
8768
+ return "nullptr";
8769
+ }
8770
+
8611
8771
  return result.code;
8612
8772
  }
8613
8773
  if (ctx.expression()) {
@@ -8711,7 +8871,7 @@ export default class CodeGenerator implements IOrchestrator {
8711
8871
  // Check if first identifier is a global variable
8712
8872
  // If not a scope or enum, it's likely a global struct variable
8713
8873
  if (
8714
- !this.symbols!.knownScopes.has(firstName) &&
8874
+ !this.isKnownScope(firstName) &&
8715
8875
  !this.symbols!.knownEnums.has(firstName)
8716
8876
  ) {
8717
8877
  return `sizeof(${firstName}.${memberName})`;
@@ -8941,7 +9101,7 @@ export default class CodeGenerator implements IOrchestrator {
8941
9101
  const isDirectRegister =
8942
9102
  parts.length > 1 && this.symbols!.knownRegisters.has(firstPart);
8943
9103
  const scopedRegisterName =
8944
- parts.length > 2 && this.symbols!.knownScopes.has(firstPart)
9104
+ parts.length > 2 && this.isKnownScope(firstPart)
8945
9105
  ? `${parts[0]}_${parts[1]}`
8946
9106
  : null;
8947
9107
  const isScopedRegister =
@@ -9018,7 +9178,7 @@ export default class CodeGenerator implements IOrchestrator {
9018
9178
  let exprIndex = 0;
9019
9179
 
9020
9180
  // Check if first identifier is a scope for special handling
9021
- const isCrossScope = this.symbols!.knownScopes.has(firstPart);
9181
+ const isCrossScope = this.isKnownScope(firstPart);
9022
9182
 
9023
9183
  // ADR-016: Inside a scope, accessing another scope requires global. prefix
9024
9184
  if (isCrossScope && this.context.currentScope) {
@@ -9179,7 +9339,7 @@ export default class CodeGenerator implements IOrchestrator {
9179
9339
  }
9180
9340
 
9181
9341
  // Check if it's a scope member access: Timing.tickCount -> Timing_tickCount (ADR-016)
9182
- if (this.symbols!.knownScopes.has(firstPart)) {
9342
+ if (this.isKnownScope(firstPart)) {
9183
9343
  // ADR-016: Inside a scope, accessing another scope requires global. prefix
9184
9344
  if (this.context.currentScope) {
9185
9345
  // Self-referential access should use 'this.'
@@ -104,12 +104,13 @@ class HeaderGenerator {
104
104
  );
105
105
 
106
106
  // Emit forward declarations for external types
107
+ // Issue #296: Use typedef forward declaration for compatibility with named structs
107
108
  if (externalTypes.size > 0) {
108
109
  lines.push(
109
110
  "/* External type dependencies - include appropriate headers */",
110
111
  );
111
112
  for (const typeName of externalTypes) {
112
- lines.push(`struct ${typeName};`);
113
+ lines.push(`typedef struct ${typeName} ${typeName};`);
113
114
  }
114
115
  lines.push("");
115
116
  }
@@ -230,6 +230,7 @@ class TypeResolver {
230
230
 
231
231
  /**
232
232
  * ADR-024: Get the type of a postfix expression.
233
+ * Issue #304: Enhanced to track type through member access chains (e.g., cfg.mode)
233
234
  */
234
235
  getPostfixExpressionType(
235
236
  ctx: Parser.PostfixExpressionContext,
@@ -238,13 +239,28 @@ class TypeResolver {
238
239
  if (!primary) return null;
239
240
 
240
241
  // Get base type from primary expression
241
- const baseType = this.getPrimaryExpressionType(primary);
242
+ let currentType = this.getPrimaryExpressionType(primary);
243
+ if (!currentType) return null;
242
244
 
243
- // Check for postfix operations like bit indexing
245
+ // Check for postfix operations: member access, array indexing, bit indexing
244
246
  const suffixes = ctx.children?.slice(1) || [];
245
247
  for (const suffix of suffixes) {
246
248
  const text = suffix.getText();
247
- // Bit indexing: [start, width] or [index]
249
+
250
+ // Member access: .fieldName
251
+ if (text.startsWith(".")) {
252
+ const memberName = text.slice(1);
253
+ const memberInfo = this.getMemberTypeInfo(currentType, memberName);
254
+ if (memberInfo) {
255
+ currentType = memberInfo.baseType;
256
+ } else {
257
+ // Can't determine member type, return null
258
+ return null;
259
+ }
260
+ continue;
261
+ }
262
+
263
+ // Array or bit indexing: [index] or [start, width]
248
264
  if (text.startsWith("[") && text.endsWith("]")) {
249
265
  const inner = text.slice(1, -1);
250
266
  if (inner.includes(",")) {
@@ -253,13 +269,19 @@ class TypeResolver {
253
269
  // Bit indexing is the explicit escape hatch for narrowing/sign conversions
254
270
  return null;
255
271
  } else {
256
- // Single bit indexing: [index] - returns bool
257
- return "bool";
272
+ // Single index: could be array access or bit indexing
273
+ // For arrays, the type stays the same (element type)
274
+ // For single bit on integer, returns bool
275
+ if (this.isIntegerType(currentType)) {
276
+ return "bool";
277
+ }
278
+ // For arrays, currentType is already the element type (from getMemberTypeInfo)
279
+ continue;
258
280
  }
259
281
  }
260
282
  }
261
283
 
262
- return baseType;
284
+ return currentType;
263
285
  }
264
286
 
265
287
  /**
@@ -149,6 +149,15 @@ interface IOrchestrator {
149
149
  /** Get known enums set for pass-by-value detection */
150
150
  getKnownEnums(): ReadonlySet<string>;
151
151
 
152
+ /** Issue #304: Check if we're generating C++ output */
153
+ isCppMode(): boolean;
154
+
155
+ /** Issue #304: Check if a type is a C++ enum class (needs :: syntax and explicit casts) */
156
+ isCppEnumClass(typeName: string): boolean;
157
+
158
+ /** Issue #304: Get the expression type */
159
+ getExpressionType(ctx: Parser.ExpressionContext): string | null;
160
+
152
161
  /** Issue #269: Check if a parameter is pass-by-value (small unmodified primitive) */
153
162
  isParameterPassByValue(funcName: string, paramIndex: number): boolean;
154
163
 
@@ -42,7 +42,8 @@ const generateStruct: TGeneratorFn<Parser.StructDeclarationContext> = (
42
42
  const callbackFields: Array<{ fieldName: string; callbackType: string }> = [];
43
43
 
44
44
  const lines: string[] = [];
45
- lines.push(`typedef struct {`);
45
+ // Issue #296: Use named struct for forward declaration compatibility
46
+ lines.push(`typedef struct ${name} {`);
46
47
 
47
48
  for (const member of node.structMember()) {
48
49
  const fieldName = member.IDENTIFIER().getText();
@@ -17,6 +17,58 @@ import IGeneratorInput from "../IGeneratorInput";
17
17
  import IGeneratorState from "../IGeneratorState";
18
18
  import IOrchestrator from "../IOrchestrator";
19
19
 
20
+ /**
21
+ * Issue #304: Map C-Next type to C type for static_cast.
22
+ */
23
+ const TYPE_MAP: Record<string, string> = {
24
+ u8: "uint8_t",
25
+ u16: "uint16_t",
26
+ u32: "uint32_t",
27
+ u64: "uint64_t",
28
+ i8: "int8_t",
29
+ i16: "int16_t",
30
+ i32: "int32_t",
31
+ i64: "int64_t",
32
+ f32: "float",
33
+ f64: "double",
34
+ bool: "bool",
35
+ };
36
+
37
+ const mapTypeToCType = (cnxType: string): string => {
38
+ return TYPE_MAP[cnxType] || cnxType;
39
+ };
40
+
41
+ /**
42
+ * Issue #304: Wrap argument with static_cast if it's a C++ enum class
43
+ * being passed to an integer parameter.
44
+ *
45
+ * @param argCode - The generated argument code
46
+ * @param argExpr - The argument expression context (for type lookup)
47
+ * @param targetParamBaseType - The target parameter's base type (if known)
48
+ * @param orchestrator - Orchestrator for type checking methods
49
+ * @returns The argument code, possibly wrapped with static_cast
50
+ */
51
+ const wrapWithCppEnumCast = (
52
+ argCode: string,
53
+ argExpr: ExpressionContext,
54
+ targetParamBaseType: string | undefined,
55
+ orchestrator: IOrchestrator,
56
+ ): string => {
57
+ if (!orchestrator.isCppMode() || !targetParamBaseType) {
58
+ return argCode;
59
+ }
60
+
61
+ const argType = orchestrator.getExpressionType(argExpr);
62
+ if (argType && orchestrator.isCppEnumClass(argType)) {
63
+ if (orchestrator.isIntegerType(targetParamBaseType)) {
64
+ const cType = mapTypeToCType(targetParamBaseType);
65
+ return `static_cast<${cType}>(${argCode})`;
66
+ }
67
+ }
68
+
69
+ return argCode;
70
+ };
71
+
20
72
  /**
21
73
  * Generate C code for a function call.
22
74
  *
@@ -61,14 +113,23 @@ const generateFunctionCall = (
61
113
 
62
114
  const args = argExprs
63
115
  .map((e, idx) => {
116
+ // Get function signature for parameter type info
117
+ const sig = input.functionSignatures.get(funcExpr);
118
+ const targetParam = sig?.parameters[idx];
119
+
64
120
  if (!isCNextFunc) {
65
121
  // C function: pass-by-value, just generate the expression
66
- return orchestrator.generateExpression(e);
122
+ const argCode = orchestrator.generateExpression(e);
123
+ // Issue #304: Wrap with static_cast if C++ enum class → integer
124
+ return wrapWithCppEnumCast(
125
+ argCode,
126
+ e,
127
+ targetParam?.baseType,
128
+ orchestrator,
129
+ );
67
130
  }
68
131
 
69
132
  // C-Next function: check if target parameter is a pass-by-value type
70
- const sig = input.functionSignatures.get(funcExpr);
71
- const targetParam = sig?.parameters[idx];
72
133
  const isFloatParam =
73
134
  targetParam && orchestrator.isFloatType(targetParam.baseType);
74
135
  const isEnumParam =
@@ -81,7 +142,14 @@ const generateFunctionCall = (
81
142
 
82
143
  if (isFloatParam || isEnumParam || isPrimitivePassByValue) {
83
144
  // Target parameter is pass-by-value: pass value directly
84
- return orchestrator.generateExpression(e);
145
+ const argCode = orchestrator.generateExpression(e);
146
+ // Issue #304: Wrap with static_cast if C++ enum class → integer
147
+ return wrapWithCppEnumCast(
148
+ argCode,
149
+ e,
150
+ targetParam?.baseType,
151
+ orchestrator,
152
+ );
85
153
  } else {
86
154
  // Target parameter is pass-by-reference: use & logic
87
155
  // Pass the target param type for proper literal handling
@@ -33,7 +33,8 @@ describe("generateStructHeader", () => {
33
33
 
34
34
  const result = generateStructHeader("Point3D", input);
35
35
 
36
- expect(result).toContain("typedef struct {");
36
+ // Issue #296: Named structs for forward declaration compatibility
37
+ expect(result).toContain("typedef struct Point3D {");
37
38
  expect(result).toContain("uint32_t x;");
38
39
  expect(result).toContain("uint32_t y;");
39
40
  expect(result).toContain("uint32_t z;");
@@ -158,7 +159,10 @@ describe("generateStructHeader", () => {
158
159
 
159
160
  const result = generateStructHeader("Wrapper", input);
160
161
 
161
- expect(result).toBe("typedef struct {\n int64_t value;\n} Wrapper;");
162
+ // Issue #296: Named structs for forward declaration compatibility
163
+ expect(result).toBe(
164
+ "typedef struct Wrapper {\n int64_t value;\n} Wrapper;",
165
+ );
162
166
  });
163
167
 
164
168
  it("should handle struct with pointer field type", () => {
@@ -13,9 +13,9 @@ const { mapType } = typeUtils;
13
13
  /**
14
14
  * Generate a C typedef struct declaration for the given struct name.
15
15
  *
16
- * Output format:
16
+ * Output format (Issue #296: uses named struct for forward declaration compatibility):
17
17
  * ```c
18
- * typedef struct {
18
+ * typedef struct StructName {
19
19
  * uint32_t field1;
20
20
  * uint8_t buffer[256];
21
21
  * } StructName;
@@ -35,7 +35,8 @@ function generateStructHeader(name: string, input: IHeaderTypeInput): string {
35
35
 
36
36
  const dimensions = input.structFieldDimensions.get(name);
37
37
  const lines: string[] = [];
38
- lines.push("typedef struct {");
38
+ // Issue #296: Use named struct for forward declaration compatibility
39
+ lines.push(`typedef struct ${name} {`);
39
40
 
40
41
  // Iterate fields in insertion order (Map preserves order)
41
42
  for (const [fieldName, fieldType] of fields) {
@@ -830,8 +830,9 @@ class Pipeline {
830
830
  }
831
831
  }
832
832
 
833
- // Step 3: Resolve and collect header files
833
+ // Step 3: Resolve and collect header files (C/C++ headers and C-Next includes)
834
834
  const headerFiles: IDiscoveredFile[] = [];
835
+ const cnextIncludes: IDiscoveredFile[] = [];
835
836
  for (const includePath of includes) {
836
837
  const resolved = IncludeDiscovery.resolveInclude(
837
838
  includePath,
@@ -839,17 +840,21 @@ class Pipeline {
839
840
  );
840
841
  if (resolved) {
841
842
  const file = FileDiscovery.discoverFile(resolved);
842
- if (
843
- file &&
844
- (file.type === EFileType.CHeader ||
845
- file.type === EFileType.CppHeader)
846
- ) {
847
- headerFiles.push(file);
843
+ if (file) {
844
+ if (
845
+ file.type === EFileType.CHeader ||
846
+ file.type === EFileType.CppHeader
847
+ ) {
848
+ headerFiles.push(file);
849
+ } else if (file.type === EFileType.CNext) {
850
+ // Issue #294: Also collect symbols from C-Next include files
851
+ cnextIncludes.push(file);
852
+ }
848
853
  }
849
854
  }
850
855
  }
851
856
 
852
- // Step 4: Parse headers to populate symbol table
857
+ // Step 4a: Parse C/C++ headers to populate symbol table
853
858
  for (const file of headerFiles) {
854
859
  try {
855
860
  await this.collectHeaderSymbols(file);
@@ -858,6 +863,18 @@ class Pipeline {
858
863
  }
859
864
  }
860
865
 
866
+ // Step 4b: Issue #294 - Parse C-Next includes to populate symbol table
867
+ // This enables cross-file scope references (e.g., decoder.getSpn() -> decoder_getSpn())
868
+ for (const file of cnextIncludes) {
869
+ try {
870
+ this.collectCNextSymbols(file);
871
+ } catch (err) {
872
+ this.warnings.push(
873
+ `Failed to process C-Next include ${file.path}: ${err}`,
874
+ );
875
+ }
876
+ }
877
+
861
878
  // Step 5: Parse C-Next source from string
862
879
  const charStream = CharStream.fromString(source);
863
880
  const lexer = new CNextLexer(charStream);