c-next 0.1.30 → 0.1.31
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/grammar/CNext.g4 +9 -1
- package/package.json +4 -1
- package/src/codegen/CodeGenerator.ts +207 -1
- package/src/codegen/SymbolCollector.ts +27 -6
- package/src/codegen/generators/declarationGenerators/ScopeGenerator.ts +90 -0
- package/src/codegen/types/TTypeInfo.ts +1 -0
- package/src/parser/grammar/CNext.interp +2 -1
- package/src/parser/grammar/CNextListener.ts +11 -0
- package/src/parser/grammar/CNextParser.ts +1409 -1224
- package/src/parser/grammar/CNextVisitor.ts +7 -0
package/grammar/CNext.g4
CHANGED
|
@@ -84,6 +84,7 @@ scopeMember
|
|
|
84
84
|
| visibilityModifier? enumDeclaration
|
|
85
85
|
| visibilityModifier? bitmapDeclaration
|
|
86
86
|
| visibilityModifier? registerDeclaration
|
|
87
|
+
| visibilityModifier? structDeclaration
|
|
87
88
|
;
|
|
88
89
|
|
|
89
90
|
visibilityModifier
|
|
@@ -193,6 +194,12 @@ arrayDimension
|
|
|
193
194
|
// ----------------------------------------------------------------------------
|
|
194
195
|
variableDeclaration
|
|
195
196
|
: atomicModifier? volatileModifier? constModifier? overflowModifier? type IDENTIFIER arrayDimension* ('<-' expression)? ';'
|
|
197
|
+
| type IDENTIFIER '(' constructorArgumentList ')' ';' // Issue #375: C++ constructor
|
|
198
|
+
;
|
|
199
|
+
|
|
200
|
+
// Issue #375: Constructor argument list - identifiers only (must be const variables)
|
|
201
|
+
constructorArgumentList
|
|
202
|
+
: IDENTIFIER (',' IDENTIFIER)*
|
|
196
203
|
;
|
|
197
204
|
|
|
198
205
|
// ----------------------------------------------------------------------------
|
|
@@ -264,7 +271,8 @@ thisMemberAccess
|
|
|
264
271
|
|
|
265
272
|
// ADR-016: this.member[idx] or this.member.member[idx] for scope-local array/bit access
|
|
266
273
|
thisArrayAccess
|
|
267
|
-
: 'this' '.' IDENTIFIER '[' expression ']'
|
|
274
|
+
: 'this' '.' IDENTIFIER '[' expression ']' ('.' IDENTIFIER)+ // this.arr[i].field.field2...
|
|
275
|
+
| 'this' '.' IDENTIFIER '[' expression ']' // this.arr[i]
|
|
268
276
|
| 'this' '.' IDENTIFIER '[' expression ',' expression ']' // this.reg[offset, width]
|
|
269
277
|
| 'this' '.' IDENTIFIER ('.' IDENTIFIER)+ '[' expression ']' // this.GPIO7.DR_SET[i]
|
|
270
278
|
| 'this' '.' IDENTIFIER ('.' IDENTIFIER)+ '[' expression ',' expression ']' // this.GPIO7.ICR1[6, 2]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c-next",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"bin": {
|
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
"coverage:grammar:console": "tsx scripts/grammar-coverage.ts console",
|
|
38
38
|
"unit": "vitest run",
|
|
39
39
|
"unit:watch": "vitest",
|
|
40
|
+
"unit:coverage": "vitest run --coverage",
|
|
41
|
+
"unit:coverage:html": "vitest run --coverage && echo 'Coverage report: coverage/index.html'",
|
|
40
42
|
"test:all": "npm run unit && npm run test:q"
|
|
41
43
|
},
|
|
42
44
|
"keywords": [
|
|
@@ -68,6 +70,7 @@
|
|
|
68
70
|
],
|
|
69
71
|
"devDependencies": {
|
|
70
72
|
"@types/node": "^25.0.3",
|
|
73
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
71
74
|
"antlr4ng-cli": "^2.0.0",
|
|
72
75
|
"husky": "^9.1.7",
|
|
73
76
|
"lint-staged": "^16.2.7",
|
|
@@ -1665,6 +1665,10 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
1665
1665
|
const scopeDecl = decl.scopeDeclaration()!;
|
|
1666
1666
|
const scopeName = scopeDecl.IDENTIFIER().getText();
|
|
1667
1667
|
|
|
1668
|
+
// Set scope context for scoped type resolution (this.Type)
|
|
1669
|
+
const savedScope = this.context.currentScope;
|
|
1670
|
+
this.context.currentScope = scopeName;
|
|
1671
|
+
|
|
1668
1672
|
for (const member of scopeDecl.scopeMember()) {
|
|
1669
1673
|
if (member.functionDeclaration()) {
|
|
1670
1674
|
const funcDecl = member.functionDeclaration()!;
|
|
@@ -1682,6 +1686,9 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
1682
1686
|
this.registerCallbackType(fullName, funcDecl);
|
|
1683
1687
|
}
|
|
1684
1688
|
}
|
|
1689
|
+
|
|
1690
|
+
// Restore previous scope context
|
|
1691
|
+
this.context.currentScope = savedScope;
|
|
1685
1692
|
}
|
|
1686
1693
|
|
|
1687
1694
|
// ADR-029: Track callback field types in structs
|
|
@@ -3173,12 +3180,24 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
3173
3180
|
// ADR-029: Check if this is a callback type
|
|
3174
3181
|
isCallback = this.callbackTypes.has(typeName);
|
|
3175
3182
|
} else if (typeCtx.qualifiedType()) {
|
|
3176
|
-
// ADR-016: Handle qualified
|
|
3183
|
+
// ADR-016: Handle qualified types like Scope.Type
|
|
3177
3184
|
typeName = typeCtx
|
|
3178
3185
|
.qualifiedType()!
|
|
3179
3186
|
.IDENTIFIER()
|
|
3180
3187
|
.map((id) => id.getText())
|
|
3181
3188
|
.join("_");
|
|
3189
|
+
// Check if this is a struct type
|
|
3190
|
+
isStruct = this.isStructType(typeName);
|
|
3191
|
+
} else if (typeCtx.scopedType()) {
|
|
3192
|
+
// ADR-016: Handle scoped types like this.Type (inside a scope)
|
|
3193
|
+
const localTypeName = typeCtx.scopedType()!.IDENTIFIER().getText();
|
|
3194
|
+
if (this.context.currentScope) {
|
|
3195
|
+
typeName = `${this.context.currentScope}_${localTypeName}`;
|
|
3196
|
+
} else {
|
|
3197
|
+
typeName = localTypeName;
|
|
3198
|
+
}
|
|
3199
|
+
// Check if this is a struct type
|
|
3200
|
+
isStruct = this.isStructType(typeName);
|
|
3182
3201
|
} else if (typeCtx.stringType()) {
|
|
3183
3202
|
// ADR-045: String parameter
|
|
3184
3203
|
isString = true;
|
|
@@ -5232,6 +5251,12 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
5232
5251
|
// ========================================================================
|
|
5233
5252
|
|
|
5234
5253
|
private generateVariableDecl(ctx: Parser.VariableDeclarationContext): string {
|
|
5254
|
+
// Issue #375: Check for C++ constructor syntax
|
|
5255
|
+
const constructorArgList = ctx.constructorArgumentList();
|
|
5256
|
+
if (constructorArgList) {
|
|
5257
|
+
return this._generateConstructorDecl(ctx, constructorArgList);
|
|
5258
|
+
}
|
|
5259
|
+
|
|
5235
5260
|
const constMod = ctx.constModifier() ? "const " : "";
|
|
5236
5261
|
// ADR-049: Add volatile for atomic variables
|
|
5237
5262
|
const atomicMod = ctx.atomicModifier() ? "volatile " : "";
|
|
@@ -5647,6 +5672,75 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
5647
5672
|
return decl + ";";
|
|
5648
5673
|
}
|
|
5649
5674
|
|
|
5675
|
+
/**
|
|
5676
|
+
* Issue #375: Generate C++ constructor-style declaration
|
|
5677
|
+
* Validates that all arguments are const variables.
|
|
5678
|
+
* Example: `Adafruit_MAX31856 thermocouple(pinConst);` -> `Adafruit_MAX31856 thermocouple(pinConst);`
|
|
5679
|
+
*/
|
|
5680
|
+
private _generateConstructorDecl(
|
|
5681
|
+
ctx: Parser.VariableDeclarationContext,
|
|
5682
|
+
argListCtx: Parser.ConstructorArgumentListContext,
|
|
5683
|
+
): string {
|
|
5684
|
+
const type = this._generateType(ctx.type());
|
|
5685
|
+
const name = ctx.IDENTIFIER().getText();
|
|
5686
|
+
const line = ctx.start?.line ?? 0;
|
|
5687
|
+
|
|
5688
|
+
// Collect and validate all arguments
|
|
5689
|
+
const argIdentifiers = argListCtx.IDENTIFIER();
|
|
5690
|
+
const resolvedArgs: string[] = [];
|
|
5691
|
+
|
|
5692
|
+
for (const argNode of argIdentifiers) {
|
|
5693
|
+
const argName = argNode.getText();
|
|
5694
|
+
|
|
5695
|
+
// Check if it exists in type registry
|
|
5696
|
+
const typeInfo = this.context.typeRegistry.get(argName);
|
|
5697
|
+
|
|
5698
|
+
// Also check scoped variables if inside a scope
|
|
5699
|
+
let scopedArgName = argName;
|
|
5700
|
+
let scopedTypeInfo = typeInfo;
|
|
5701
|
+
if (!typeInfo && this.context.currentScope) {
|
|
5702
|
+
scopedArgName = `${this.context.currentScope}_${argName}`;
|
|
5703
|
+
scopedTypeInfo = this.context.typeRegistry.get(scopedArgName);
|
|
5704
|
+
}
|
|
5705
|
+
|
|
5706
|
+
if (!typeInfo && !scopedTypeInfo) {
|
|
5707
|
+
throw new Error(
|
|
5708
|
+
`Error at line ${line}: Constructor argument '${argName}' is not declared`,
|
|
5709
|
+
);
|
|
5710
|
+
}
|
|
5711
|
+
|
|
5712
|
+
const finalTypeInfo = typeInfo ?? scopedTypeInfo!;
|
|
5713
|
+
const finalArgName = typeInfo ? argName : scopedArgName;
|
|
5714
|
+
|
|
5715
|
+
// Check if it's const
|
|
5716
|
+
if (!finalTypeInfo.isConst) {
|
|
5717
|
+
throw new Error(
|
|
5718
|
+
`Error at line ${line}: Constructor argument '${argName}' must be const. ` +
|
|
5719
|
+
`C++ constructors in C-Next only accept const variables.`,
|
|
5720
|
+
);
|
|
5721
|
+
}
|
|
5722
|
+
|
|
5723
|
+
resolvedArgs.push(finalArgName);
|
|
5724
|
+
}
|
|
5725
|
+
|
|
5726
|
+
// Track the variable in type registry (as an external C++ type)
|
|
5727
|
+
this.context.typeRegistry.set(name, {
|
|
5728
|
+
baseType: type,
|
|
5729
|
+
bitWidth: 0, // Unknown for C++ types
|
|
5730
|
+
isArray: false,
|
|
5731
|
+
arrayDimensions: [],
|
|
5732
|
+
isConst: false,
|
|
5733
|
+
isExternalCppType: true,
|
|
5734
|
+
});
|
|
5735
|
+
|
|
5736
|
+
// Track as local variable if inside function body
|
|
5737
|
+
if (this.context.inFunctionBody) {
|
|
5738
|
+
this.context.localVariables.add(name);
|
|
5739
|
+
}
|
|
5740
|
+
|
|
5741
|
+
return `${type} ${name}(${resolvedArgs.join(", ")});`;
|
|
5742
|
+
}
|
|
5743
|
+
|
|
5650
5744
|
/**
|
|
5651
5745
|
* ADR-015: Get the appropriate zero initializer for a type
|
|
5652
5746
|
* ADR-017: Handle enum types by initializing to first member
|
|
@@ -5660,6 +5754,64 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
5660
5754
|
return "{0}";
|
|
5661
5755
|
}
|
|
5662
5756
|
|
|
5757
|
+
// ADR-016: Check for scoped types (this.Type)
|
|
5758
|
+
// These are always struct/enum types defined in a scope
|
|
5759
|
+
if (typeCtx.scopedType()) {
|
|
5760
|
+
const localTypeName = typeCtx.scopedType()!.IDENTIFIER().getText();
|
|
5761
|
+
const fullTypeName = this.context.currentScope
|
|
5762
|
+
? `${this.context.currentScope}_${localTypeName}`
|
|
5763
|
+
: localTypeName;
|
|
5764
|
+
|
|
5765
|
+
// Check if it's an enum
|
|
5766
|
+
if (this.symbols!.knownEnums.has(fullTypeName)) {
|
|
5767
|
+
const members = this.symbols!.enumMembers.get(fullTypeName);
|
|
5768
|
+
if (members) {
|
|
5769
|
+
for (const [memberName, value] of members.entries()) {
|
|
5770
|
+
if (value === 0) {
|
|
5771
|
+
return `${fullTypeName}_${memberName}`;
|
|
5772
|
+
}
|
|
5773
|
+
}
|
|
5774
|
+
const firstMember = members.keys().next().value;
|
|
5775
|
+
if (firstMember) {
|
|
5776
|
+
return `${fullTypeName}_${firstMember}`;
|
|
5777
|
+
}
|
|
5778
|
+
}
|
|
5779
|
+
return `(${fullTypeName})0`;
|
|
5780
|
+
}
|
|
5781
|
+
|
|
5782
|
+
// Otherwise it's a struct - use {0}
|
|
5783
|
+
return "{0}";
|
|
5784
|
+
}
|
|
5785
|
+
|
|
5786
|
+
// ADR-016: Check for qualified types (Scope.Type)
|
|
5787
|
+
if (typeCtx.qualifiedType()) {
|
|
5788
|
+
const qualifiedCtx = typeCtx.qualifiedType()!;
|
|
5789
|
+
const parts = qualifiedCtx.IDENTIFIER();
|
|
5790
|
+
const scopeName = parts[0].getText();
|
|
5791
|
+
const localTypeName = parts[1].getText();
|
|
5792
|
+
const fullTypeName = `${scopeName}_${localTypeName}`;
|
|
5793
|
+
|
|
5794
|
+
// Check if it's an enum
|
|
5795
|
+
if (this.symbols!.knownEnums.has(fullTypeName)) {
|
|
5796
|
+
const members = this.symbols!.enumMembers.get(fullTypeName);
|
|
5797
|
+
if (members) {
|
|
5798
|
+
for (const [memberName, value] of members.entries()) {
|
|
5799
|
+
if (value === 0) {
|
|
5800
|
+
return `${fullTypeName}_${memberName}`;
|
|
5801
|
+
}
|
|
5802
|
+
}
|
|
5803
|
+
const firstMember = members.keys().next().value;
|
|
5804
|
+
if (firstMember) {
|
|
5805
|
+
return `${fullTypeName}_${firstMember}`;
|
|
5806
|
+
}
|
|
5807
|
+
}
|
|
5808
|
+
return `(${fullTypeName})0`;
|
|
5809
|
+
}
|
|
5810
|
+
|
|
5811
|
+
// Otherwise it's a struct - use {0}
|
|
5812
|
+
return "{0}";
|
|
5813
|
+
}
|
|
5814
|
+
|
|
5663
5815
|
// Check for user-defined types (structs/classes/enums)
|
|
5664
5816
|
if (typeCtx.userType()) {
|
|
5665
5817
|
const typeName = typeCtx.userType()!.getText();
|
|
@@ -6839,6 +6991,18 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
6839
6991
|
if (parts.length === 1) {
|
|
6840
6992
|
return `${scopeName}_${parts[0]}[${expr}] ${cOp} ${value};`;
|
|
6841
6993
|
}
|
|
6994
|
+
|
|
6995
|
+
// Check if this is array-then-field (this.arr[i].field) vs field-then-array (this.a.b[i])
|
|
6996
|
+
// by examining the context text - if '[' comes right after first identifier, it's array-first
|
|
6997
|
+
const ctxText = thisArrayAccessCtx.getText();
|
|
6998
|
+
const firstIdEnd = ctxText.indexOf(parts[0]) + parts[0].length;
|
|
6999
|
+
|
|
7000
|
+
if (ctxText.charAt(firstIdEnd) === "[") {
|
|
7001
|
+
// Array access comes first: this.buffer[i].field -> Scope_buffer[i].field
|
|
7002
|
+
return `${scopeName}_${parts[0]}[${expr}].${parts.slice(1).join(".")} ${cOp} ${value};`;
|
|
7003
|
+
}
|
|
7004
|
+
|
|
7005
|
+
// Field access comes first: this.GPIO7.DR_SET[i] -> Scope_GPIO7.DR_SET[i]
|
|
6842
7006
|
return `${scopeName}_${parts[0]}.${parts.slice(1).join(".")}[${expr}] ${cOp} ${value};`;
|
|
6843
7007
|
}
|
|
6844
7008
|
}
|
|
@@ -7530,6 +7694,19 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
7530
7694
|
// ADR-016: Validate visibility before allowing cross-scope access
|
|
7531
7695
|
const memberName = parts[1];
|
|
7532
7696
|
this.validateCrossScopeVisibility(firstId, memberName);
|
|
7697
|
+
|
|
7698
|
+
// Check if the scope variable is a struct type - if so, remaining parts
|
|
7699
|
+
// are struct field access and should use '.' not '_'
|
|
7700
|
+
// e.g., global.Motor.current.speed -> Motor_current.speed
|
|
7701
|
+
if (parts.length > 2) {
|
|
7702
|
+
const scopeVarName = `${firstId}_${memberName}`;
|
|
7703
|
+
const scopeVarType = this.context.typeRegistry.get(scopeVarName);
|
|
7704
|
+
if (scopeVarType && this._isKnownStruct(scopeVarType.baseType)) {
|
|
7705
|
+
// Scope variable is a struct - use '.' for field access
|
|
7706
|
+
return `${scopeVarName}.${parts.slice(2).join(".")}`;
|
|
7707
|
+
}
|
|
7708
|
+
}
|
|
7709
|
+
|
|
7533
7710
|
// Issue #304: Use :: for C++ namespaces, _ for C-Next scopes
|
|
7534
7711
|
return parts.join(this.getScopeSeparator(isCppAccess));
|
|
7535
7712
|
}
|
|
@@ -7655,6 +7832,19 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
7655
7832
|
if (parts.length === 1) {
|
|
7656
7833
|
return `${scopeName}_${parts[0]}[${expr}]`;
|
|
7657
7834
|
}
|
|
7835
|
+
|
|
7836
|
+
// Check if this is array-then-field (this.arr[i].field) vs field-then-array (this.a.b[i])
|
|
7837
|
+
// by examining the context text - if '[' comes right after first identifier, it's array-first
|
|
7838
|
+
const ctxText = ctx.getText();
|
|
7839
|
+
const firstIdEnd = ctxText.indexOf(parts[0]) + parts[0].length;
|
|
7840
|
+
const bracketPos = ctxText.indexOf("[");
|
|
7841
|
+
|
|
7842
|
+
if (bracketPos === firstIdEnd + 1 || ctxText.charAt(firstIdEnd) === "[") {
|
|
7843
|
+
// Array access comes first: this.buffer[i].field -> Scope_buffer[i].field
|
|
7844
|
+
return `${scopeName}_${parts[0]}[${expr}].${parts.slice(1).join(".")}`;
|
|
7845
|
+
}
|
|
7846
|
+
|
|
7847
|
+
// Field access comes first: this.GPIO7.DR_SET[i] -> Scope_GPIO7.DR_SET[i]
|
|
7658
7848
|
return `${scopeName}_${parts[0]}.${parts.slice(1).join(".")}[${expr}]`;
|
|
7659
7849
|
}
|
|
7660
7850
|
|
|
@@ -9589,6 +9779,18 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
9589
9779
|
// ADR-016: Validate visibility before allowing cross-scope access
|
|
9590
9780
|
const memberName = parts[1];
|
|
9591
9781
|
this.validateCrossScopeVisibility(firstPart, memberName);
|
|
9782
|
+
|
|
9783
|
+
// Check if the scope variable is a struct type - if so, remaining parts
|
|
9784
|
+
// are struct field access and should use '.' not '_'
|
|
9785
|
+
// e.g., Motor.current.speed -> Motor_current.speed (not Motor_current_speed)
|
|
9786
|
+
if (parts.length > 2) {
|
|
9787
|
+
const scopeVarName = `${firstPart}_${memberName}`;
|
|
9788
|
+
const scopeVarType = this.context.typeRegistry.get(scopeVarName);
|
|
9789
|
+
if (scopeVarType && this._isKnownStruct(scopeVarType.baseType)) {
|
|
9790
|
+
// Scope variable is a struct - use '.' for field access
|
|
9791
|
+
return `${scopeVarName}.${parts.slice(2).join(".")}`;
|
|
9792
|
+
}
|
|
9793
|
+
}
|
|
9592
9794
|
return parts.join("_");
|
|
9593
9795
|
}
|
|
9594
9796
|
|
|
@@ -9737,6 +9939,10 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
9737
9939
|
const identifiers = ctx.qualifiedType()!.IDENTIFIER();
|
|
9738
9940
|
const scopeName = identifiers[0].getText();
|
|
9739
9941
|
const typeName = identifiers[1].getText();
|
|
9942
|
+
|
|
9943
|
+
// Check visibility for scoped types (structs, enums, bitmaps)
|
|
9944
|
+
this._validateCrossScopeVisibility(scopeName, typeName);
|
|
9945
|
+
|
|
9740
9946
|
return `${scopeName}_${typeName}`;
|
|
9741
9947
|
}
|
|
9742
9948
|
if (ctx.userType()) {
|
|
@@ -240,7 +240,7 @@ class SymbolCollector {
|
|
|
240
240
|
if (decl.bitmapDeclaration()) {
|
|
241
241
|
this.collectBitmap(decl.bitmapDeclaration()!);
|
|
242
242
|
}
|
|
243
|
-
// Also collect bitmaps inside scopes first
|
|
243
|
+
// Also collect bitmaps and structs inside scopes first
|
|
244
244
|
if (decl.scopeDeclaration()) {
|
|
245
245
|
const scopeDecl = decl.scopeDeclaration()!;
|
|
246
246
|
const scopeName = scopeDecl.IDENTIFIER().getText();
|
|
@@ -248,6 +248,10 @@ class SymbolCollector {
|
|
|
248
248
|
if (member.bitmapDeclaration()) {
|
|
249
249
|
this.collectBitmap(member.bitmapDeclaration()!, scopeName);
|
|
250
250
|
}
|
|
251
|
+
// Collect scoped structs early so they're available as types
|
|
252
|
+
if (member.structDeclaration()) {
|
|
253
|
+
this.collectStruct(member.structDeclaration()!, scopeName);
|
|
254
|
+
}
|
|
251
255
|
}
|
|
252
256
|
}
|
|
253
257
|
}
|
|
@@ -378,6 +382,17 @@ class SymbolCollector {
|
|
|
378
382
|
}
|
|
379
383
|
}
|
|
380
384
|
}
|
|
385
|
+
|
|
386
|
+
// Handle struct declarations inside scopes
|
|
387
|
+
// Note: Struct collection happens in first pass for early availability,
|
|
388
|
+
// but we still need to add to scope members and visibility here
|
|
389
|
+
if (member.structDeclaration()) {
|
|
390
|
+
const structDecl = member.structDeclaration()!;
|
|
391
|
+
const structName = structDecl.IDENTIFIER().getText();
|
|
392
|
+
members.add(structName);
|
|
393
|
+
memberVisibility.set(structName, visibility);
|
|
394
|
+
// Note: collectStruct already called in first pass
|
|
395
|
+
}
|
|
381
396
|
}
|
|
382
397
|
|
|
383
398
|
this._scopeMembers.set(name, members);
|
|
@@ -523,10 +538,16 @@ class SymbolCollector {
|
|
|
523
538
|
|
|
524
539
|
/**
|
|
525
540
|
* Collect struct declarations and track field types
|
|
541
|
+
* @param structDecl The struct declaration context
|
|
542
|
+
* @param scopeName Optional scope name for scoped struct declarations
|
|
526
543
|
*/
|
|
527
|
-
private collectStruct(
|
|
544
|
+
private collectStruct(
|
|
545
|
+
structDecl: Parser.StructDeclarationContext,
|
|
546
|
+
scopeName?: string,
|
|
547
|
+
): void {
|
|
528
548
|
const name = structDecl.IDENTIFIER().getText();
|
|
529
|
-
|
|
549
|
+
const fullName = scopeName ? `${scopeName}_${name}` : name;
|
|
550
|
+
this._knownStructs.add(fullName);
|
|
530
551
|
|
|
531
552
|
const fields = new Map<string, string>();
|
|
532
553
|
const arrayFields = new Set<string>();
|
|
@@ -584,9 +605,9 @@ class SymbolCollector {
|
|
|
584
605
|
}
|
|
585
606
|
}
|
|
586
607
|
|
|
587
|
-
this._structFields.set(
|
|
588
|
-
this._structFieldArrays.set(
|
|
589
|
-
this._structFieldDimensions.set(
|
|
608
|
+
this._structFields.set(fullName, fields);
|
|
609
|
+
this._structFieldArrays.set(fullName, arrayFields);
|
|
610
|
+
this._structFieldDimensions.set(fullName, fieldDimensions);
|
|
590
611
|
}
|
|
591
612
|
|
|
592
613
|
/**
|
|
@@ -53,6 +53,39 @@ const generateScope: TGeneratorFn<Parser.ScopeDeclarationContext> = (
|
|
|
53
53
|
const varDecl = member.variableDeclaration()!;
|
|
54
54
|
const varName = varDecl.IDENTIFIER().getText();
|
|
55
55
|
|
|
56
|
+
// Issue #375: Check for constructor syntax
|
|
57
|
+
const constructorArgList = varDecl.constructorArgumentList();
|
|
58
|
+
if (constructorArgList) {
|
|
59
|
+
// ADR-016: All scope variables are emitted at file scope
|
|
60
|
+
const type = orchestrator.generateType(varDecl.type());
|
|
61
|
+
const fullName = `${name}_${varName}`;
|
|
62
|
+
const prefix = isPrivate ? "static " : "";
|
|
63
|
+
|
|
64
|
+
// Validate and resolve constructor arguments
|
|
65
|
+
const argIdentifiers = constructorArgList.IDENTIFIER();
|
|
66
|
+
const resolvedArgs: string[] = [];
|
|
67
|
+
const line = varDecl.start?.line ?? 0;
|
|
68
|
+
|
|
69
|
+
for (const argNode of argIdentifiers) {
|
|
70
|
+
const argName = argNode.getText();
|
|
71
|
+
// Arguments must be resolved with scope prefix
|
|
72
|
+
const scopedArgName = `${name}_${argName}`;
|
|
73
|
+
|
|
74
|
+
// Check if it's const using orchestrator
|
|
75
|
+
if (!orchestrator.isConstValue(scopedArgName)) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Error at line ${line}: Constructor argument '${argName}' must be const. ` +
|
|
78
|
+
`C++ constructors in C-Next only accept const variables.`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
resolvedArgs.push(scopedArgName);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
lines.push(`${prefix}${type} ${fullName}(${resolvedArgs.join(", ")});`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
56
89
|
// Issue #282: Check if this is a const variable - const values should be inlined
|
|
57
90
|
const isConst = varDecl.constModifier() !== null;
|
|
58
91
|
|
|
@@ -170,6 +203,16 @@ const generateScope: TGeneratorFn<Parser.ScopeDeclarationContext> = (
|
|
|
170
203
|
);
|
|
171
204
|
lines.push(result.code);
|
|
172
205
|
}
|
|
206
|
+
|
|
207
|
+
// Handle struct declarations inside scopes
|
|
208
|
+
// Issue #369: Skip struct definition if self-include was added (it will be in the header)
|
|
209
|
+
if (member.structDeclaration() && !state.selfIncludeAdded) {
|
|
210
|
+
const structDecl = member.structDeclaration()!;
|
|
211
|
+
lines.push("");
|
|
212
|
+
lines.push(
|
|
213
|
+
generateScopedStructInline(structDecl, name, input, orchestrator),
|
|
214
|
+
);
|
|
215
|
+
}
|
|
173
216
|
}
|
|
174
217
|
|
|
175
218
|
lines.push("");
|
|
@@ -320,4 +363,51 @@ function generateScopedBitmapInline(
|
|
|
320
363
|
return lines.join("\n");
|
|
321
364
|
}
|
|
322
365
|
|
|
366
|
+
/**
|
|
367
|
+
* Generate struct inside a scope with proper prefixing.
|
|
368
|
+
* Struct fields maintain their original types.
|
|
369
|
+
*/
|
|
370
|
+
function generateScopedStructInline(
|
|
371
|
+
node: Parser.StructDeclarationContext,
|
|
372
|
+
scopeName: string,
|
|
373
|
+
input: IGeneratorInput,
|
|
374
|
+
orchestrator: IOrchestrator,
|
|
375
|
+
): string {
|
|
376
|
+
const name = node.IDENTIFIER().getText();
|
|
377
|
+
const fullName = `${scopeName}_${name}`;
|
|
378
|
+
|
|
379
|
+
const lines: string[] = [];
|
|
380
|
+
lines.push(`typedef struct ${fullName} {`);
|
|
381
|
+
|
|
382
|
+
// Process struct members
|
|
383
|
+
for (const member of node.structMember()) {
|
|
384
|
+
const fieldName = member.IDENTIFIER().getText();
|
|
385
|
+
const fieldType = orchestrator.generateType(member.type());
|
|
386
|
+
|
|
387
|
+
// Handle array dimensions if present
|
|
388
|
+
const arrayDims = member.arrayDimension();
|
|
389
|
+
let dimStr = "";
|
|
390
|
+
if (arrayDims.length > 0) {
|
|
391
|
+
dimStr = orchestrator.generateArrayDimensions(arrayDims);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Handle string capacity for string fields
|
|
395
|
+
if (member.type().stringType()) {
|
|
396
|
+
const stringCtx = member.type().stringType()!;
|
|
397
|
+
const intLiteral = stringCtx.INTEGER_LITERAL();
|
|
398
|
+
if (intLiteral) {
|
|
399
|
+
const capacity = parseInt(intLiteral.getText(), 10);
|
|
400
|
+
dimStr += `[${capacity + 1}]`;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
lines.push(` ${fieldType} ${fieldName}${dimStr};`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
lines.push(`} ${fullName};`);
|
|
408
|
+
lines.push("");
|
|
409
|
+
|
|
410
|
+
return lines.join("\n");
|
|
411
|
+
}
|
|
412
|
+
|
|
323
413
|
export default generateScope;
|