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 +1 -1
- package/src/codegen/CodeGenerator.ts +200 -40
- package/src/codegen/HeaderGenerator.ts +2 -1
- package/src/codegen/TypeResolver.ts +28 -6
- package/src/codegen/generators/IOrchestrator.ts +9 -0
- package/src/codegen/generators/declarationGenerators/StructGenerator.ts +2 -1
- package/src/codegen/generators/expressions/CallExprGenerator.ts +72 -4
- package/src/codegen/headerGenerators/generateStructHeader.test.ts +6 -2
- package/src/codegen/headerGenerators/generateStructHeader.ts +4 -3
- package/src/pipeline/Pipeline.ts +25 -8
package/package.json
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
//
|
|
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.
|
|
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
|
-
//
|
|
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.
|
|
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
|
-
//
|
|
8068
|
-
result = `${result}
|
|
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
|
-
//
|
|
8097
|
-
result = `${result}
|
|
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.
|
|
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
|
-
//
|
|
8298
|
-
result = `${result}
|
|
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
|
-
//
|
|
8315
|
-
result = `${result}
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
242
|
+
let currentType = this.getPrimaryExpressionType(primary);
|
|
243
|
+
if (!currentType) return null;
|
|
242
244
|
|
|
243
|
-
// Check for postfix operations
|
|
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
|
-
|
|
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
|
|
257
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|
package/src/pipeline/Pipeline.ts
CHANGED
|
@@ -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
|
-
|
|
844
|
-
|
|
845
|
-
file.type === EFileType.CppHeader
|
|
846
|
-
|
|
847
|
-
|
|
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
|
|
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);
|