c-next 0.1.65 → 0.1.66
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 +5 -1
- package/src/transpiler/logic/symbols/cnext/__tests__/TSymbolAdapter.test.ts +179 -0
- package/src/transpiler/logic/symbols/cnext/adapters/TSymbolAdapter.ts +76 -8
- package/src/transpiler/output/codegen/CodeGenerator.ts +109 -127
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +1086 -0
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +618 -12
- package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +819 -0
- package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +337 -0
- package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +135 -0
- package/src/transpiler/output/codegen/helpers/VariableDeclarationFormatter.ts +118 -0
- package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +426 -0
- package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +315 -0
- package/src/transpiler/output/codegen/helpers/__tests__/VariableDeclarationFormatter.test.ts +333 -0
- package/src/transpiler/output/codegen/types/IParameterInput.ts +58 -0
- package/src/transpiler/output/codegen/types/IVariableFormatInput.ts +51 -0
- package/src/transpiler/output/headers/BaseHeaderGenerator.ts +20 -35
- package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +21 -48
- package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +0 -64
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c-next",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.66",
|
|
4
4
|
"description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
|
|
5
5
|
"packageManager": "npm@11.9.0",
|
|
6
6
|
"type": "module",
|
|
@@ -118,5 +118,9 @@
|
|
|
118
118
|
"*.ts": [
|
|
119
119
|
"oxlint --fix"
|
|
120
120
|
]
|
|
121
|
+
},
|
|
122
|
+
"volta": {
|
|
123
|
+
"node": "24.13.1",
|
|
124
|
+
"npm": "11.8.0"
|
|
121
125
|
}
|
|
122
126
|
}
|
|
@@ -463,4 +463,183 @@ describe("TSymbolAdapter", () => {
|
|
|
463
463
|
expect(symbolTable.getStructFieldType("Point", "x")).toBe("i32");
|
|
464
464
|
});
|
|
465
465
|
});
|
|
466
|
+
|
|
467
|
+
describe("enum member resolution in array dimensions", () => {
|
|
468
|
+
it("resolves unqualified enum member in variable array dimension", () => {
|
|
469
|
+
const enumSym: IEnumSymbol = {
|
|
470
|
+
kind: ESymbolKind.Enum,
|
|
471
|
+
name: "EColor",
|
|
472
|
+
sourceFile: "test.cnx",
|
|
473
|
+
sourceLine: 1,
|
|
474
|
+
sourceLanguage: ESourceLanguage.CNext,
|
|
475
|
+
isExported: true,
|
|
476
|
+
members: new Map([
|
|
477
|
+
["RED", 0],
|
|
478
|
+
["GREEN", 1],
|
|
479
|
+
["BLUE", 2],
|
|
480
|
+
["COUNT", 3],
|
|
481
|
+
]),
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
const variable: IVariableSymbol = {
|
|
485
|
+
kind: ESymbolKind.Variable,
|
|
486
|
+
name: "DATA",
|
|
487
|
+
sourceFile: "test.cnx",
|
|
488
|
+
sourceLine: 10,
|
|
489
|
+
sourceLanguage: ESourceLanguage.CNext,
|
|
490
|
+
isExported: true,
|
|
491
|
+
type: "u8",
|
|
492
|
+
isConst: true,
|
|
493
|
+
isAtomic: false,
|
|
494
|
+
isArray: true,
|
|
495
|
+
arrayDimensions: ["COUNT"], // Unqualified enum member
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
const result = TSymbolAdapter.toISymbols(
|
|
499
|
+
[enumSym, variable],
|
|
500
|
+
symbolTable,
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
// Find the variable symbol
|
|
504
|
+
const varSym = result.find(
|
|
505
|
+
(s) => s.kind === ESymbolKind.Variable && s.name === "DATA",
|
|
506
|
+
);
|
|
507
|
+
expect(varSym).toBeDefined();
|
|
508
|
+
expect(varSym!.arrayDimensions).toEqual(["EColor_COUNT"]);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it("resolves enum member in function parameter array dimension", () => {
|
|
512
|
+
const enumSym: IEnumSymbol = {
|
|
513
|
+
kind: ESymbolKind.Enum,
|
|
514
|
+
name: "Size",
|
|
515
|
+
sourceFile: "test.cnx",
|
|
516
|
+
sourceLine: 1,
|
|
517
|
+
sourceLanguage: ESourceLanguage.CNext,
|
|
518
|
+
isExported: true,
|
|
519
|
+
members: new Map([
|
|
520
|
+
["SMALL", 10],
|
|
521
|
+
["MEDIUM", 20],
|
|
522
|
+
["LARGE", 30],
|
|
523
|
+
]),
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
const funcSym: IFunctionSymbol = {
|
|
527
|
+
kind: ESymbolKind.Function,
|
|
528
|
+
name: "process",
|
|
529
|
+
sourceFile: "test.cnx",
|
|
530
|
+
sourceLine: 5,
|
|
531
|
+
sourceLanguage: ESourceLanguage.CNext,
|
|
532
|
+
isExported: true,
|
|
533
|
+
returnType: "void",
|
|
534
|
+
visibility: "public",
|
|
535
|
+
parameters: [
|
|
536
|
+
{
|
|
537
|
+
name: "buffer",
|
|
538
|
+
type: "u8",
|
|
539
|
+
isConst: false,
|
|
540
|
+
isArray: true,
|
|
541
|
+
arrayDimensions: ["MEDIUM"], // Unqualified enum member
|
|
542
|
+
},
|
|
543
|
+
],
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
const result = TSymbolAdapter.toISymbols([enumSym, funcSym], symbolTable);
|
|
547
|
+
|
|
548
|
+
// Find the function symbol
|
|
549
|
+
const funcResult = result.find(
|
|
550
|
+
(s) => s.kind === ESymbolKind.Function && s.name === "process",
|
|
551
|
+
);
|
|
552
|
+
expect(funcResult).toBeDefined();
|
|
553
|
+
expect(funcResult!.parameters).toBeDefined();
|
|
554
|
+
expect(funcResult!.parameters![0].arrayDimensions).toEqual([
|
|
555
|
+
"Size_MEDIUM",
|
|
556
|
+
]);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it("does not resolve ambiguous enum member (exists in multiple enums)", () => {
|
|
560
|
+
const enum1: IEnumSymbol = {
|
|
561
|
+
kind: ESymbolKind.Enum,
|
|
562
|
+
name: "EColor",
|
|
563
|
+
sourceFile: "test.cnx",
|
|
564
|
+
sourceLine: 1,
|
|
565
|
+
sourceLanguage: ESourceLanguage.CNext,
|
|
566
|
+
isExported: true,
|
|
567
|
+
members: new Map([["COUNT", 3]]),
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
const enum2: IEnumSymbol = {
|
|
571
|
+
kind: ESymbolKind.Enum,
|
|
572
|
+
name: "ESize",
|
|
573
|
+
sourceFile: "test.cnx",
|
|
574
|
+
sourceLine: 5,
|
|
575
|
+
sourceLanguage: ESourceLanguage.CNext,
|
|
576
|
+
isExported: true,
|
|
577
|
+
members: new Map([
|
|
578
|
+
["COUNT", 5], // Same member name in different enum
|
|
579
|
+
]),
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
const variable: IVariableSymbol = {
|
|
583
|
+
kind: ESymbolKind.Variable,
|
|
584
|
+
name: "DATA",
|
|
585
|
+
sourceFile: "test.cnx",
|
|
586
|
+
sourceLine: 10,
|
|
587
|
+
sourceLanguage: ESourceLanguage.CNext,
|
|
588
|
+
isExported: true,
|
|
589
|
+
type: "u8",
|
|
590
|
+
isConst: true,
|
|
591
|
+
isAtomic: false,
|
|
592
|
+
isArray: true,
|
|
593
|
+
arrayDimensions: ["COUNT"], // Ambiguous - exists in both enums
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
const result = TSymbolAdapter.toISymbols(
|
|
597
|
+
[enum1, enum2, variable],
|
|
598
|
+
symbolTable,
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
// Find the variable symbol - dimension should NOT be resolved
|
|
602
|
+
const varSym = result.find(
|
|
603
|
+
(s) => s.kind === ESymbolKind.Variable && s.name === "DATA",
|
|
604
|
+
);
|
|
605
|
+
expect(varSym).toBeDefined();
|
|
606
|
+
expect(varSym!.arrayDimensions).toEqual(["COUNT"]); // Left as-is
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
it("preserves numeric array dimensions", () => {
|
|
610
|
+
const enumSym: IEnumSymbol = {
|
|
611
|
+
kind: ESymbolKind.Enum,
|
|
612
|
+
name: "EColor",
|
|
613
|
+
sourceFile: "test.cnx",
|
|
614
|
+
sourceLine: 1,
|
|
615
|
+
sourceLanguage: ESourceLanguage.CNext,
|
|
616
|
+
isExported: true,
|
|
617
|
+
members: new Map([["COUNT", 3]]),
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
const variable: IVariableSymbol = {
|
|
621
|
+
kind: ESymbolKind.Variable,
|
|
622
|
+
name: "DATA",
|
|
623
|
+
sourceFile: "test.cnx",
|
|
624
|
+
sourceLine: 10,
|
|
625
|
+
sourceLanguage: ESourceLanguage.CNext,
|
|
626
|
+
isExported: true,
|
|
627
|
+
type: "u8",
|
|
628
|
+
isConst: true,
|
|
629
|
+
isAtomic: false,
|
|
630
|
+
isArray: true,
|
|
631
|
+
arrayDimensions: [256], // Numeric dimension
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
const result = TSymbolAdapter.toISymbols(
|
|
635
|
+
[enumSym, variable],
|
|
636
|
+
symbolTable,
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
const varSym = result.find(
|
|
640
|
+
(s) => s.kind === ESymbolKind.Variable && s.name === "DATA",
|
|
641
|
+
);
|
|
642
|
+
expect(varSym!.arrayDimensions).toEqual(["256"]);
|
|
643
|
+
});
|
|
644
|
+
});
|
|
466
645
|
});
|
|
@@ -40,6 +40,9 @@ class TSymbolAdapter {
|
|
|
40
40
|
* @returns Array of flat ISymbol objects for Pipeline consumption
|
|
41
41
|
*/
|
|
42
42
|
static toISymbols(symbols: TSymbol[], symbolTable: SymbolTable): ISymbol[] {
|
|
43
|
+
// First pass: Build enum member lookup for array dimension resolution
|
|
44
|
+
const enumMemberLookup = TSymbolAdapter.buildEnumMemberLookup(symbols);
|
|
45
|
+
|
|
43
46
|
const result: ISymbol[] = [];
|
|
44
47
|
|
|
45
48
|
for (const symbol of symbols) {
|
|
@@ -54,10 +57,12 @@ class TSymbolAdapter {
|
|
|
54
57
|
result.push(TSymbolAdapter.convertStruct(symbol, symbolTable));
|
|
55
58
|
break;
|
|
56
59
|
case ESymbolKind.Function:
|
|
57
|
-
result.push(
|
|
60
|
+
result.push(
|
|
61
|
+
...TSymbolAdapter.convertFunction(symbol, enumMemberLookup),
|
|
62
|
+
);
|
|
58
63
|
break;
|
|
59
64
|
case ESymbolKind.Variable:
|
|
60
|
-
result.push(TSymbolAdapter.convertVariable(symbol));
|
|
65
|
+
result.push(TSymbolAdapter.convertVariable(symbol, enumMemberLookup));
|
|
61
66
|
break;
|
|
62
67
|
case ESymbolKind.Register:
|
|
63
68
|
result.push(...TSymbolAdapter.convertRegister(symbol));
|
|
@@ -71,6 +76,57 @@ class TSymbolAdapter {
|
|
|
71
76
|
return result;
|
|
72
77
|
}
|
|
73
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Build a lookup map from enum member names to their fully-qualified enum names.
|
|
81
|
+
* Used to resolve unqualified enum members in array dimensions.
|
|
82
|
+
*
|
|
83
|
+
* If a member name exists in multiple enums, it's marked as ambiguous (null value)
|
|
84
|
+
* and will not be resolved (the header will fail to compile, surfacing the issue).
|
|
85
|
+
*/
|
|
86
|
+
private static buildEnumMemberLookup(
|
|
87
|
+
symbols: TSymbol[],
|
|
88
|
+
): Map<string, string | null> {
|
|
89
|
+
const lookup = new Map<string, string | null>();
|
|
90
|
+
|
|
91
|
+
for (const symbol of symbols) {
|
|
92
|
+
if (symbol.kind !== ESymbolKind.Enum) continue;
|
|
93
|
+
|
|
94
|
+
for (const memberName of symbol.members.keys()) {
|
|
95
|
+
if (lookup.has(memberName)) {
|
|
96
|
+
// Ambiguous: member exists in multiple enums
|
|
97
|
+
lookup.set(memberName, null);
|
|
98
|
+
} else {
|
|
99
|
+
lookup.set(memberName, symbol.name);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return lookup;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Resolve an array dimension string, adding enum prefix if it's an unqualified enum member.
|
|
109
|
+
*/
|
|
110
|
+
private static resolveArrayDimension(
|
|
111
|
+
dim: number | string,
|
|
112
|
+
enumMemberLookup: Map<string, string | null>,
|
|
113
|
+
): string {
|
|
114
|
+
// Numeric dimensions don't need resolution
|
|
115
|
+
if (typeof dim === "number") {
|
|
116
|
+
return String(dim);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Check if this is an unqualified enum member
|
|
120
|
+
const enumName = enumMemberLookup.get(dim);
|
|
121
|
+
if (enumName) {
|
|
122
|
+
// Found in exactly one enum - add the prefix
|
|
123
|
+
return `${enumName}_${dim}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Not an enum member, or ambiguous - return as-is
|
|
127
|
+
return dim;
|
|
128
|
+
}
|
|
129
|
+
|
|
74
130
|
/**
|
|
75
131
|
* Convert IBitmapSymbol to ISymbol + BitmapField symbols.
|
|
76
132
|
*/
|
|
@@ -180,21 +236,27 @@ class TSymbolAdapter {
|
|
|
180
236
|
|
|
181
237
|
/**
|
|
182
238
|
* Convert IFunctionSymbol to ISymbol + parameter symbols for hover support.
|
|
239
|
+
* Resolves unqualified enum members in parameter array dimensions.
|
|
183
240
|
*/
|
|
184
|
-
private static convertFunction(
|
|
241
|
+
private static convertFunction(
|
|
242
|
+
func: IFunctionSymbol,
|
|
243
|
+
enumMemberLookup: Map<string, string | null>,
|
|
244
|
+
): ISymbol[] {
|
|
185
245
|
const result: ISymbol[] = [];
|
|
186
246
|
|
|
187
247
|
// Build parameter types for signature
|
|
188
248
|
const paramTypes = func.parameters.map((p) => p.type);
|
|
189
249
|
const signature = `${func.returnType} ${func.name}(${paramTypes.join(", ")})`;
|
|
190
250
|
|
|
191
|
-
// Build parameter info for header generation
|
|
251
|
+
// Build parameter info for header generation, resolving enum members in dimensions
|
|
192
252
|
const parameters = func.parameters.map((p) => ({
|
|
193
253
|
name: p.name,
|
|
194
254
|
type: p.type,
|
|
195
255
|
isConst: p.isConst,
|
|
196
256
|
isArray: p.isArray,
|
|
197
|
-
arrayDimensions: p.arrayDimensions
|
|
257
|
+
arrayDimensions: p.arrayDimensions?.map((dim) =>
|
|
258
|
+
TSymbolAdapter.resolveArrayDimension(dim, enumMemberLookup),
|
|
259
|
+
),
|
|
198
260
|
isAutoConst: p.isAutoConst,
|
|
199
261
|
}));
|
|
200
262
|
|
|
@@ -233,10 +295,16 @@ class TSymbolAdapter {
|
|
|
233
295
|
|
|
234
296
|
/**
|
|
235
297
|
* Convert IVariableSymbol to ISymbol.
|
|
298
|
+
* Resolves unqualified enum members in array dimensions.
|
|
236
299
|
*/
|
|
237
|
-
private static convertVariable(
|
|
238
|
-
|
|
239
|
-
|
|
300
|
+
private static convertVariable(
|
|
301
|
+
variable: IVariableSymbol,
|
|
302
|
+
enumMemberLookup: Map<string, string | null>,
|
|
303
|
+
): ISymbol {
|
|
304
|
+
// Convert dimensions to string dimensions, resolving enum member references
|
|
305
|
+
const arrayDimensions = variable.arrayDimensions?.map((dim) =>
|
|
306
|
+
TSymbolAdapter.resolveArrayDimension(dim, enumMemberLookup),
|
|
307
|
+
);
|
|
240
308
|
|
|
241
309
|
// Get first dimension for legacy size field (only if numeric)
|
|
242
310
|
const firstDim = variable.arrayDimensions?.[0];
|
|
@@ -134,6 +134,9 @@ import TypeGenerationHelper from "./helpers/TypeGenerationHelper";
|
|
|
134
134
|
import CastValidator from "./helpers/CastValidator";
|
|
135
135
|
// Global state for code generation (simplifies debugging, eliminates DI complexity)
|
|
136
136
|
import CodeGenState from "./CodeGenState";
|
|
137
|
+
// Unified parameter generation (Phase 1)
|
|
138
|
+
import ParameterInputAdapter from "./helpers/ParameterInputAdapter";
|
|
139
|
+
import ParameterSignatureBuilder from "./helpers/ParameterSignatureBuilder";
|
|
137
140
|
// Extracted resolvers that use CodeGenState
|
|
138
141
|
import SizeofResolver from "./resolution/SizeofResolver";
|
|
139
142
|
import EnumTypeResolver from "./resolution/EnumTypeResolver";
|
|
@@ -802,30 +805,54 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
802
805
|
}
|
|
803
806
|
|
|
804
807
|
// Issue #137: Check for array element access (e.g., names[0], arr[i])
|
|
808
|
+
return this._isArrayAccessStringExpression(text);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Check if array access expression evaluates to a string.
|
|
813
|
+
* Extracted from isStringExpression to reduce cognitive complexity.
|
|
814
|
+
*/
|
|
815
|
+
private _isArrayAccessStringExpression(text: string): boolean {
|
|
805
816
|
// Pattern: identifier[expression] or identifier[expression][expression]...
|
|
806
817
|
// BUT NOT if accessing .length/.capacity/.size (those return numbers, not strings)
|
|
807
818
|
const arrayAccessMatch = /^([a-zA-Z_]\w*)\[/.exec(text);
|
|
808
|
-
if (arrayAccessMatch) {
|
|
809
|
-
|
|
810
|
-
if (
|
|
811
|
-
text.endsWith(".length") ||
|
|
812
|
-
text.endsWith(".capacity") ||
|
|
813
|
-
text.endsWith(".size")
|
|
814
|
-
) {
|
|
815
|
-
return false;
|
|
816
|
-
}
|
|
817
|
-
const arrayName = arrayAccessMatch[1];
|
|
818
|
-
const typeInfo = CodeGenState.typeRegistry.get(arrayName);
|
|
819
|
-
// Check if base type is a string type
|
|
820
|
-
if (
|
|
821
|
-
typeInfo?.isString ||
|
|
822
|
-
(typeInfo?.baseType && TypeCheckUtils.isString(typeInfo.baseType))
|
|
823
|
-
) {
|
|
824
|
-
return true;
|
|
825
|
-
}
|
|
819
|
+
if (!arrayAccessMatch) {
|
|
820
|
+
return false;
|
|
826
821
|
}
|
|
827
822
|
|
|
828
|
-
return
|
|
823
|
+
// ADR-045: String properties return numeric values, not strings
|
|
824
|
+
if (
|
|
825
|
+
text.endsWith(".length") ||
|
|
826
|
+
text.endsWith(".capacity") ||
|
|
827
|
+
text.endsWith(".size")
|
|
828
|
+
) {
|
|
829
|
+
return false;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const arrayName = arrayAccessMatch[1];
|
|
833
|
+
const typeInfo = CodeGenState.typeRegistry.get(arrayName);
|
|
834
|
+
if (!typeInfo) {
|
|
835
|
+
return false;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// Check if it's an ARRAY OF STRINGS (not a single string being indexed)
|
|
839
|
+
// A single string<50> has arrayDimensions=[51] (just the char buffer)
|
|
840
|
+
// An array of strings string<50>[10] has arrayDimensions=[10, 51]
|
|
841
|
+
// Single string indexing (e.g., userName[i]) returns a char, not a string
|
|
842
|
+
// Array of strings indexing (e.g., names[0]) returns a string
|
|
843
|
+
if (typeInfo.isString) {
|
|
844
|
+
// For strings, only treat as string expression if it's an array of strings
|
|
845
|
+
// (arrayDimensions.length > 1 means it's string<N>[M], not just string<N>)
|
|
846
|
+
const dims = typeInfo.arrayDimensions;
|
|
847
|
+
return Array.isArray(dims) && dims.length > 1;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Non-string array with string base type
|
|
851
|
+
return Boolean(
|
|
852
|
+
typeInfo.isArray &&
|
|
853
|
+
typeInfo.baseType &&
|
|
854
|
+
TypeCheckUtils.isString(typeInfo.baseType),
|
|
855
|
+
);
|
|
829
856
|
}
|
|
830
857
|
|
|
831
858
|
/**
|
|
@@ -1505,7 +1532,9 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
1505
1532
|
return `${constMod}${p.type} ${p.name}${p.arrayDims}`;
|
|
1506
1533
|
} else if (p.isPointer) {
|
|
1507
1534
|
// ADR-006: Non-array, non-callback parameters become pointers
|
|
1508
|
-
|
|
1535
|
+
// In C++ mode, use reference (&) instead of pointer (*)
|
|
1536
|
+
const ptrOrRef = this.isCppMode() ? "&" : "*";
|
|
1537
|
+
return `${constMod}${p.type}${ptrOrRef}`;
|
|
1509
1538
|
} else {
|
|
1510
1539
|
// ADR-029: Callback parameters are already function pointers
|
|
1511
1540
|
return `${p.type}`;
|
|
@@ -2373,9 +2402,13 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
2373
2402
|
*/
|
|
2374
2403
|
private addGeneratedHelpers(output: string[]): void {
|
|
2375
2404
|
if (CodeGenState.needsFloatStaticAssert) {
|
|
2405
|
+
// Use static_assert for C++ (standard), _Static_assert for C11
|
|
2406
|
+
const assertKeyword = this.isCppMode()
|
|
2407
|
+
? "static_assert"
|
|
2408
|
+
: "_Static_assert";
|
|
2376
2409
|
output.push(
|
|
2377
|
-
|
|
2378
|
-
|
|
2410
|
+
`${assertKeyword}(sizeof(float) == 4, "Float bit indexing requires 32-bit float");`,
|
|
2411
|
+
`${assertKeyword}(sizeof(double) == 8, "Float bit indexing requires 64-bit double");`,
|
|
2379
2412
|
"",
|
|
2380
2413
|
);
|
|
2381
2414
|
}
|
|
@@ -5273,16 +5306,47 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
5273
5306
|
}
|
|
5274
5307
|
|
|
5275
5308
|
private generateParameter(ctx: Parser.ParameterContext): string {
|
|
5276
|
-
const constMod = ctx.constModifier() ? "const " : "";
|
|
5277
5309
|
const typeName = this.getTypeName(ctx.type());
|
|
5278
5310
|
const name = ctx.IDENTIFIER().getText();
|
|
5279
|
-
const dims = ctx.arrayDimension();
|
|
5280
5311
|
|
|
5281
|
-
// Reject
|
|
5282
|
-
|
|
5283
|
-
|
|
5312
|
+
// Validate: Reject C-style array parameters
|
|
5313
|
+
this._validateCStyleArrayParam(ctx, typeName, name);
|
|
5314
|
+
|
|
5315
|
+
// Validate: Reject unbounded array dimensions
|
|
5316
|
+
this._validateUnboundedArrayParam(ctx);
|
|
5317
|
+
|
|
5318
|
+
// Pre-compute CodeGenState-dependent values
|
|
5319
|
+
const isModified = this._isCurrentParameterModified(name);
|
|
5320
|
+
const isPassByValue = this._isPassByValueType(typeName, name);
|
|
5321
|
+
|
|
5322
|
+
// Build normalized input using adapter
|
|
5323
|
+
const input = ParameterInputAdapter.fromAST(ctx, {
|
|
5324
|
+
getTypeName: (t) => this.getTypeName(t),
|
|
5325
|
+
generateType: (t) => this.generateType(t),
|
|
5326
|
+
generateExpression: (e) => this.generateExpression(e),
|
|
5327
|
+
callbackTypes: CodeGenState.callbackTypes,
|
|
5328
|
+
isKnownStruct: (t) => this.isKnownStruct(t),
|
|
5329
|
+
typeMap: TYPE_MAP,
|
|
5330
|
+
isModified,
|
|
5331
|
+
isPassByValue,
|
|
5332
|
+
});
|
|
5333
|
+
|
|
5334
|
+
// Use shared builder with C/C++ mode
|
|
5335
|
+
return ParameterSignatureBuilder.build(input, CppModeHelper.refOrPtr());
|
|
5336
|
+
}
|
|
5337
|
+
|
|
5338
|
+
/**
|
|
5339
|
+
* Validate: Reject C-style array parameters
|
|
5340
|
+
* C-style: u8 data[8], u8 data[4][4], u8 data[]
|
|
5341
|
+
* C-Next: u8[8] data, u8[4][4] data, u8[] data
|
|
5342
|
+
*/
|
|
5343
|
+
private _validateCStyleArrayParam(
|
|
5344
|
+
ctx: Parser.ParameterContext,
|
|
5345
|
+
typeName: string,
|
|
5346
|
+
name: string,
|
|
5347
|
+
): void {
|
|
5348
|
+
const dims = ctx.arrayDimension();
|
|
5284
5349
|
if (dims.length > 0) {
|
|
5285
|
-
const baseType = typeName;
|
|
5286
5350
|
const dimensions = dims
|
|
5287
5351
|
.map((dim) => `[${dim.expression()?.getText() ?? ""}]`)
|
|
5288
5352
|
.join("");
|
|
@@ -5290,73 +5354,28 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
5290
5354
|
const col = ctx.start?.column ?? 0;
|
|
5291
5355
|
throw new Error(
|
|
5292
5356
|
`${line}:${col} C-style array parameter is not allowed. ` +
|
|
5293
|
-
`Use '${
|
|
5357
|
+
`Use '${typeName}${dimensions} ${name}' instead of '${typeName} ${name}${dimensions}'`,
|
|
5294
5358
|
);
|
|
5295
5359
|
}
|
|
5360
|
+
}
|
|
5296
5361
|
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
}
|
|
5302
|
-
|
|
5303
|
-
const type = this.generateType(ctx.type());
|
|
5304
|
-
|
|
5305
|
-
// Handle C-Next style array type in parameter (e.g., u8[8] param, u8[4][4] param, string<32>[5] param)
|
|
5362
|
+
/**
|
|
5363
|
+
* Validate: Reject unbounded array dimensions for memory safety
|
|
5364
|
+
*/
|
|
5365
|
+
private _validateUnboundedArrayParam(ctx: Parser.ParameterContext): void {
|
|
5306
5366
|
const arrayTypeCtx = ctx.type().arrayType();
|
|
5307
|
-
if (arrayTypeCtx)
|
|
5308
|
-
// Check for unbounded dimensions - reject for memory safety
|
|
5309
|
-
const allDims = arrayTypeCtx.arrayTypeDimension();
|
|
5310
|
-
const hasUnboundedDim = allDims.some((d) => !d.expression());
|
|
5311
|
-
if (hasUnboundedDim) {
|
|
5312
|
-
const line = ctx.start?.line ?? 0;
|
|
5313
|
-
const col = ctx.start?.column ?? 0;
|
|
5314
|
-
throw new Error(
|
|
5315
|
-
`${line}:${col} Unbounded array parameters are not allowed. ` +
|
|
5316
|
-
`All dimensions must have explicit sizes for memory safety.`,
|
|
5317
|
-
);
|
|
5318
|
-
}
|
|
5319
|
-
|
|
5320
|
-
let dims = allDims
|
|
5321
|
-
.map((d) => {
|
|
5322
|
-
const expr = d.expression();
|
|
5323
|
-
return expr ? `[${this.generateExpression(expr)}]` : "[]";
|
|
5324
|
-
})
|
|
5325
|
-
.join("");
|
|
5326
|
-
// For string arrays, add the string capacity dimension (string<32>[5] -> char[5][33])
|
|
5327
|
-
if (arrayTypeCtx.stringType()) {
|
|
5328
|
-
const stringCtx = arrayTypeCtx.stringType()!;
|
|
5329
|
-
const intLiteral = stringCtx.INTEGER_LITERAL();
|
|
5330
|
-
if (intLiteral) {
|
|
5331
|
-
const capacity = Number.parseInt(intLiteral.getText(), 10);
|
|
5332
|
-
dims += `[${capacity + 1}]`;
|
|
5333
|
-
}
|
|
5334
|
-
}
|
|
5335
|
-
const wasModified = this._isCurrentParameterModified(name);
|
|
5336
|
-
const autoConst = !wasModified && !constMod ? "const " : "";
|
|
5337
|
-
return `${autoConst}${constMod}${type} ${name}${dims}`;
|
|
5338
|
-
}
|
|
5367
|
+
if (!arrayTypeCtx) return;
|
|
5339
5368
|
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5369
|
+
const allDims = arrayTypeCtx.arrayTypeDimension();
|
|
5370
|
+
const hasUnboundedDim = allDims.some((d) => !d.expression());
|
|
5371
|
+
if (hasUnboundedDim) {
|
|
5372
|
+
const line = ctx.start?.line ?? 0;
|
|
5373
|
+
const col = ctx.start?.column ?? 0;
|
|
5374
|
+
throw new Error(
|
|
5375
|
+
`${line}:${col} Unbounded array parameters are not allowed. ` +
|
|
5376
|
+
`All dimensions must have explicit sizes for memory safety.`,
|
|
5377
|
+
);
|
|
5343
5378
|
}
|
|
5344
|
-
|
|
5345
|
-
// Non-array string parameters
|
|
5346
|
-
const stringResult = this._tryGenerateStringParam(
|
|
5347
|
-
ctx,
|
|
5348
|
-
constMod,
|
|
5349
|
-
name,
|
|
5350
|
-
dims,
|
|
5351
|
-
);
|
|
5352
|
-
if (stringResult) return stringResult;
|
|
5353
|
-
|
|
5354
|
-
// Pass-by-reference types
|
|
5355
|
-
const refResult = this._tryGenerateRefParam(constMod, type, typeName, name);
|
|
5356
|
-
if (refResult) return refResult;
|
|
5357
|
-
|
|
5358
|
-
// Unknown types use pass-by-value (standard C semantics)
|
|
5359
|
-
return `${constMod}${type} ${name}`;
|
|
5360
5379
|
}
|
|
5361
5380
|
|
|
5362
5381
|
/**
|
|
@@ -5366,7 +5385,7 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
5366
5385
|
// ISR, float, enum types
|
|
5367
5386
|
if (typeName === "ISR") return true;
|
|
5368
5387
|
if (this._isFloatType(typeName)) return true;
|
|
5369
|
-
if (CodeGenState.symbols
|
|
5388
|
+
if (CodeGenState.symbols?.knownEnums.has(typeName)) return true;
|
|
5370
5389
|
|
|
5371
5390
|
// Small unmodified primitives
|
|
5372
5391
|
if (
|
|
@@ -5379,43 +5398,6 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
5379
5398
|
return false;
|
|
5380
5399
|
}
|
|
5381
5400
|
|
|
5382
|
-
/**
|
|
5383
|
-
* Try to generate non-array string parameter: string<N> -> char*
|
|
5384
|
-
*/
|
|
5385
|
-
private _tryGenerateStringParam(
|
|
5386
|
-
ctx: Parser.ParameterContext,
|
|
5387
|
-
constMod: string,
|
|
5388
|
-
name: string,
|
|
5389
|
-
dims: Parser.ArrayDimensionContext[],
|
|
5390
|
-
): string | null {
|
|
5391
|
-
if (!ctx.type().stringType() || dims.length !== 0) {
|
|
5392
|
-
return null;
|
|
5393
|
-
}
|
|
5394
|
-
|
|
5395
|
-
const wasModified = this._isCurrentParameterModified(name);
|
|
5396
|
-
const autoConst = !wasModified && !constMod ? "const " : "";
|
|
5397
|
-
return `${autoConst}${constMod}char* ${name}`;
|
|
5398
|
-
}
|
|
5399
|
-
|
|
5400
|
-
/**
|
|
5401
|
-
* Try to generate pass-by-reference parameter for known types
|
|
5402
|
-
*/
|
|
5403
|
-
private _tryGenerateRefParam(
|
|
5404
|
-
constMod: string,
|
|
5405
|
-
type: string,
|
|
5406
|
-
typeName: string,
|
|
5407
|
-
name: string,
|
|
5408
|
-
): string | null {
|
|
5409
|
-
if (!this.isKnownStruct(typeName) && !this._isKnownPrimitive(typeName)) {
|
|
5410
|
-
return null;
|
|
5411
|
-
}
|
|
5412
|
-
|
|
5413
|
-
const wasModified = this._isCurrentParameterModified(name);
|
|
5414
|
-
const autoConst = !wasModified && !constMod ? "const " : "";
|
|
5415
|
-
const refOrPtr = CppModeHelper.refOrPtr();
|
|
5416
|
-
return `${autoConst}${constMod}${type}${refOrPtr} ${name}`;
|
|
5417
|
-
}
|
|
5418
|
-
|
|
5419
5401
|
// ========================================================================
|
|
5420
5402
|
// Variables
|
|
5421
5403
|
// ========================================================================
|