c-next 0.2.8 → 0.2.10
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/dist/index.js +1019 -267
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/src/transpiler/Transpiler.ts +101 -8
- package/src/transpiler/__tests__/Transpiler.coverage.test.ts +170 -0
- package/src/transpiler/__tests__/needsConditionalPreprocessing.test.ts +246 -0
- package/src/transpiler/logic/IncludeExtractor.ts +12 -6
- package/src/transpiler/logic/__tests__/IncludeExtractor.test.ts +24 -0
- package/src/transpiler/logic/analysis/ArrayIndexTypeAnalyzer.ts +12 -1
- package/src/transpiler/logic/analysis/__tests__/ArrayIndexTypeAnalyzer.test.ts +172 -0
- package/src/transpiler/logic/symbols/SymbolTable.ts +84 -0
- package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +43 -0
- package/src/transpiler/logic/symbols/__tests__/TransitiveEnumCollector.test.ts +1 -0
- package/src/transpiler/logic/symbols/c/__tests__/CResolver.integration.test.ts +98 -1
- package/src/transpiler/logic/symbols/c/collectors/FunctionCollector.ts +23 -5
- package/src/transpiler/logic/symbols/c/collectors/StructCollector.ts +69 -2
- package/src/transpiler/logic/symbols/c/index.ts +85 -30
- package/src/transpiler/logic/symbols/c/utils/DeclaratorUtils.ts +18 -0
- package/src/transpiler/logic/symbols/cnext/__tests__/TSymbolInfoAdapter.test.ts +90 -0
- package/src/transpiler/logic/symbols/cnext/adapters/TSymbolInfoAdapter.ts +40 -39
- package/src/transpiler/output/codegen/CodeGenerator.ts +55 -25
- package/src/transpiler/output/codegen/TypeResolver.ts +14 -0
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +3 -3
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +35 -30
- package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +7 -7
- package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +1 -0
- package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +27 -4
- package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +6 -0
- package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +73 -0
- package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +4 -0
- package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +92 -0
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +5 -2
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/handlerTestUtils.ts +1 -0
- package/src/transpiler/output/codegen/generators/IOrchestrator.ts +14 -0
- package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +25 -3
- package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +49 -0
- package/src/transpiler/output/codegen/generators/expressions/AccessExprGenerator.ts +17 -5
- package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +26 -6
- package/src/transpiler/output/codegen/generators/expressions/LiteralGenerator.ts +31 -2
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +72 -13
- package/src/transpiler/output/codegen/generators/expressions/UnaryExprGenerator.ts +25 -1
- package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +2 -1
- package/src/transpiler/output/codegen/generators/statements/AtomicGenerator.ts +2 -17
- package/src/transpiler/output/codegen/generators/support/IncludeGenerator.ts +19 -7
- package/src/transpiler/output/codegen/generators/support/__tests__/IncludeGenerator.test.ts +68 -0
- package/src/transpiler/output/codegen/helpers/ArgumentGenerator.ts +14 -2
- package/src/transpiler/output/codegen/helpers/ArrayAccessHelper.ts +3 -0
- package/src/transpiler/output/codegen/helpers/ArrayInitHelper.ts +3 -5
- package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +56 -8
- package/src/transpiler/output/codegen/helpers/BitRangeHelper.ts +19 -3
- package/src/transpiler/output/codegen/helpers/NarrowingCastHelper.ts +191 -0
- package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +35 -5
- package/src/transpiler/output/codegen/helpers/__tests__/ArrayAccessHelper.test.ts +131 -1
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +61 -2
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentValidator.test.ts +1 -0
- package/src/transpiler/output/codegen/helpers/__tests__/BitRangeHelper.test.ts +53 -2
- package/src/transpiler/output/codegen/helpers/__tests__/FunctionContextManager.test.ts +1 -0
- package/src/transpiler/output/codegen/helpers/__tests__/NarrowingCastHelper.test.ts +159 -0
- package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +1 -0
- package/src/transpiler/output/codegen/types/COMPOUND_TO_BINARY.ts +21 -0
- package/src/transpiler/output/codegen/types/IArrayAccessInfo.ts +2 -0
- package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +14 -16
- package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +25 -0
- package/src/transpiler/state/CodeGenState.ts +89 -0
- package/src/transpiler/state/__tests__/CodeGenState.test.ts +53 -0
- package/src/transpiler/state/__tests__/TranspilerState.test.ts +1 -0
- package/src/transpiler/types/ICachedFileEntry.ts +2 -0
- package/src/transpiler/types/ICodeGenSymbols.ts +8 -0
- package/src/utils/BitUtils.ts +33 -4
- package/src/utils/cache/CacheManager.ts +9 -1
- package/src/utils/cache/__tests__/CacheManager.test.ts +1 -1
package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts
CHANGED
|
@@ -63,6 +63,7 @@ function setupStructFields(
|
|
|
63
63
|
scopePrivateConstValues: new Map(),
|
|
64
64
|
functionReturnTypes: new Map(),
|
|
65
65
|
getSingleFunctionForVariable: () => null,
|
|
66
|
+
opaqueTypes: new Set(),
|
|
66
67
|
hasPublicSymbols: () => false,
|
|
67
68
|
};
|
|
68
69
|
}
|
|
@@ -183,7 +184,8 @@ describe("AssignmentExpectedTypeResolver", () => {
|
|
|
183
184
|
});
|
|
184
185
|
|
|
185
186
|
describe("array access", () => {
|
|
186
|
-
|
|
187
|
+
// Issue #872: Array element assignments need expectedType for MISRA 7.2 U suffix
|
|
188
|
+
it("should resolve expected type for simple array element access", () => {
|
|
187
189
|
CodeGenState.setVariableTypeInfo("arr", {
|
|
188
190
|
baseType: "u32",
|
|
189
191
|
bitWidth: 32,
|
|
@@ -194,8 +196,65 @@ describe("AssignmentExpectedTypeResolver", () => {
|
|
|
194
196
|
|
|
195
197
|
const result = AssignmentExpectedTypeResolver.resolve(target);
|
|
196
198
|
|
|
199
|
+
expect(result.expectedType).toBe("u32");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("should resolve expected type for u8 array element access", () => {
|
|
203
|
+
CodeGenState.setVariableTypeInfo("buffer", {
|
|
204
|
+
baseType: "u8",
|
|
205
|
+
bitWidth: 8,
|
|
206
|
+
isArray: true,
|
|
207
|
+
isConst: false,
|
|
208
|
+
});
|
|
209
|
+
const target = parseAssignmentTarget("buffer[5]");
|
|
210
|
+
|
|
211
|
+
const result = AssignmentExpectedTypeResolver.resolve(target);
|
|
212
|
+
|
|
213
|
+
expect(result.expectedType).toBe("u8");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("should resolve expected type for struct member array access", () => {
|
|
217
|
+
CodeGenState.setVariableTypeInfo("pkt", {
|
|
218
|
+
baseType: "Packet",
|
|
219
|
+
bitWidth: 0,
|
|
220
|
+
isArray: false,
|
|
221
|
+
isConst: false,
|
|
222
|
+
});
|
|
223
|
+
setupStructFields("Packet", new Map([["header", "u8"]]));
|
|
224
|
+
// Mark header as an array field
|
|
225
|
+
if (CodeGenState.symbols) {
|
|
226
|
+
(
|
|
227
|
+
CodeGenState.symbols.structFieldArrays as Map<string, Set<string>>
|
|
228
|
+
).set("Packet", new Set(["header"]));
|
|
229
|
+
}
|
|
230
|
+
const target = parseAssignmentTarget("pkt.header[0]");
|
|
231
|
+
|
|
232
|
+
const result = AssignmentExpectedTypeResolver.resolve(target);
|
|
233
|
+
|
|
234
|
+
expect(result.expectedType).toBe("u8");
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("should resolve expected type for multi-dimensional array element", () => {
|
|
238
|
+
CodeGenState.setVariableTypeInfo("matrix", {
|
|
239
|
+
baseType: "u8",
|
|
240
|
+
bitWidth: 8,
|
|
241
|
+
isArray: true,
|
|
242
|
+
arrayDimensions: [4, 8],
|
|
243
|
+
isConst: false,
|
|
244
|
+
});
|
|
245
|
+
const target = parseAssignmentTarget("matrix[0][0]");
|
|
246
|
+
|
|
247
|
+
const result = AssignmentExpectedTypeResolver.resolve(target);
|
|
248
|
+
|
|
249
|
+
expect(result.expectedType).toBe("u8");
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("should return null for unknown array variable", () => {
|
|
253
|
+
const target = parseAssignmentTarget("unknown[0]");
|
|
254
|
+
|
|
255
|
+
const result = AssignmentExpectedTypeResolver.resolve(target);
|
|
256
|
+
|
|
197
257
|
expect(result.expectedType).toBeNull();
|
|
198
|
-
expect(result.assignmentContext).toBeNull();
|
|
199
258
|
});
|
|
200
259
|
});
|
|
201
260
|
});
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Unit tests for BitRangeHelper utility.
|
|
3
3
|
* Tests bit range access code generation patterns.
|
|
4
4
|
*/
|
|
5
|
-
import { describe, it, expect } from "vitest";
|
|
6
|
-
import BitRangeHelper from "../BitRangeHelper";
|
|
5
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
6
|
+
import BitRangeHelper from "../BitRangeHelper.js";
|
|
7
|
+
import CodeGenState from "../../../../state/CodeGenState.js";
|
|
7
8
|
|
|
8
9
|
describe("BitRangeHelper", () => {
|
|
9
10
|
describe("buildFloatBitReadExpr", () => {
|
|
@@ -114,6 +115,56 @@ describe("BitRangeHelper", () => {
|
|
|
114
115
|
});
|
|
115
116
|
});
|
|
116
117
|
|
|
118
|
+
describe("buildIntegerBitReadExpr with target type", () => {
|
|
119
|
+
beforeEach(() => {
|
|
120
|
+
CodeGenState.reset();
|
|
121
|
+
CodeGenState.cppMode = false;
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("adds cast when target type is narrower than source", () => {
|
|
125
|
+
const result = BitRangeHelper.buildIntegerBitReadExpr({
|
|
126
|
+
varName: "value",
|
|
127
|
+
start: "0",
|
|
128
|
+
mask: "0xFFU",
|
|
129
|
+
sourceType: "u32",
|
|
130
|
+
targetType: "u8",
|
|
131
|
+
});
|
|
132
|
+
expect(result).toBe("(uint8_t)((value) & 0xFFU)");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("returns plain expression when no narrowing", () => {
|
|
136
|
+
const result = BitRangeHelper.buildIntegerBitReadExpr({
|
|
137
|
+
varName: "value",
|
|
138
|
+
start: "8",
|
|
139
|
+
mask: "0xFFFFU",
|
|
140
|
+
sourceType: "u32",
|
|
141
|
+
targetType: "u32",
|
|
142
|
+
});
|
|
143
|
+
expect(result).toBe("((value >> 8) & 0xFFFFU)");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("returns plain expression when types not provided (backward compatible)", () => {
|
|
147
|
+
const result = BitRangeHelper.buildIntegerBitReadExpr({
|
|
148
|
+
varName: "value",
|
|
149
|
+
start: "0",
|
|
150
|
+
mask: "0xFFU",
|
|
151
|
+
});
|
|
152
|
+
expect(result).toBe("((value) & 0xFFU)");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("uses static_cast in C++ mode", () => {
|
|
156
|
+
CodeGenState.cppMode = true;
|
|
157
|
+
const result = BitRangeHelper.buildIntegerBitReadExpr({
|
|
158
|
+
varName: "value",
|
|
159
|
+
start: "0",
|
|
160
|
+
mask: "0xFFU",
|
|
161
|
+
sourceType: "u32",
|
|
162
|
+
targetType: "u8",
|
|
163
|
+
});
|
|
164
|
+
expect(result).toBe("static_cast<uint8_t>(((value) & 0xFFU))");
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
117
168
|
describe("getShadowVarName", () => {
|
|
118
169
|
it("should prefix with __bits_", () => {
|
|
119
170
|
expect(BitRangeHelper.getShadowVarName("value")).toBe("__bits_value");
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for NarrowingCastHelper
|
|
3
|
+
* Issue #845: MISRA C:2012 Rule 10.3 compliance
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
7
|
+
import NarrowingCastHelper from "../NarrowingCastHelper.js";
|
|
8
|
+
import CodeGenState from "../../../../state/CodeGenState.js";
|
|
9
|
+
|
|
10
|
+
describe("NarrowingCastHelper", () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
CodeGenState.reset();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("needsCast", () => {
|
|
16
|
+
it("returns false for same type", () => {
|
|
17
|
+
expect(NarrowingCastHelper.needsCast("u32", "u32")).toBe(false);
|
|
18
|
+
expect(NarrowingCastHelper.needsCast("u8", "u8")).toBe(false);
|
|
19
|
+
expect(NarrowingCastHelper.needsCast("bool", "bool")).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("returns false for widening (u8 -> u32)", () => {
|
|
23
|
+
expect(NarrowingCastHelper.needsCast("u8", "u32")).toBe(false);
|
|
24
|
+
expect(NarrowingCastHelper.needsCast("u16", "u32")).toBe(false);
|
|
25
|
+
expect(NarrowingCastHelper.needsCast("i8", "i32")).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("returns true for narrowing (u32 -> u8)", () => {
|
|
29
|
+
expect(NarrowingCastHelper.needsCast("u32", "u8")).toBe(true);
|
|
30
|
+
expect(NarrowingCastHelper.needsCast("u32", "u16")).toBe(true);
|
|
31
|
+
expect(NarrowingCastHelper.needsCast("i32", "i8")).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("returns true for int -> smaller unsigned (C promotion result)", () => {
|
|
35
|
+
expect(NarrowingCastHelper.needsCast("int", "u8")).toBe(true);
|
|
36
|
+
expect(NarrowingCastHelper.needsCast("int", "u16")).toBe(true);
|
|
37
|
+
expect(NarrowingCastHelper.needsCast("int", "i8")).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("returns true for int -> bool (different essential type)", () => {
|
|
41
|
+
expect(NarrowingCastHelper.needsCast("int", "bool")).toBe(true);
|
|
42
|
+
expect(NarrowingCastHelper.needsCast("u32", "bool")).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("returns true for char literal type -> u8", () => {
|
|
46
|
+
expect(NarrowingCastHelper.needsCast("int", "u8")).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("wrap (C mode)", () => {
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
CodeGenState.cppMode = false;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("returns expression unchanged for same type", () => {
|
|
56
|
+
expect(NarrowingCastHelper.wrap("x", "u32", "u32")).toBe("x");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("returns expression unchanged for widening", () => {
|
|
60
|
+
expect(NarrowingCastHelper.wrap("x", "u8", "u32")).toBe("x");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("adds C cast for narrowing u32 -> u8", () => {
|
|
64
|
+
const expr = "((value >> 0U) & 0xFFU)";
|
|
65
|
+
expect(NarrowingCastHelper.wrap(expr, "u32", "u8")).toBe(
|
|
66
|
+
"(uint8_t)((value >> 0U) & 0xFFU)",
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("adds C cast for narrowing u32 -> u16", () => {
|
|
71
|
+
const expr = "((value >> 0U) & 0xFFFFU)";
|
|
72
|
+
expect(NarrowingCastHelper.wrap(expr, "u32", "u16")).toBe(
|
|
73
|
+
"(uint16_t)((value >> 0U) & 0xFFFFU)",
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("adds C cast for int -> u8 (C promotion result)", () => {
|
|
78
|
+
const expr = "((flags >> 3) & 0x7)";
|
|
79
|
+
expect(NarrowingCastHelper.wrap(expr, "int", "u8")).toBe(
|
|
80
|
+
"(uint8_t)((flags >> 3) & 0x7)",
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("uses != 0U comparison for bool target (MISRA 10.5)", () => {
|
|
85
|
+
const expr = "((flags >> 0) & 1)";
|
|
86
|
+
expect(NarrowingCastHelper.wrap(expr, "int", "bool")).toBe(
|
|
87
|
+
"((((flags >> 0) & 1)) != 0U)",
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("adds cast for char literal to u8", () => {
|
|
92
|
+
expect(NarrowingCastHelper.wrap("'A'", "int", "u8")).toBe("(uint8_t)'A'");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe("wrap (C++ mode)", () => {
|
|
97
|
+
beforeEach(() => {
|
|
98
|
+
CodeGenState.cppMode = true;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("uses static_cast for narrowing", () => {
|
|
102
|
+
const expr = "((value >> 0U) & 0xFFU)";
|
|
103
|
+
expect(NarrowingCastHelper.wrap(expr, "u32", "u8")).toBe(
|
|
104
|
+
"static_cast<uint8_t>(((value >> 0U) & 0xFFU))",
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("uses != 0U for bool (same as C mode)", () => {
|
|
109
|
+
const expr = "((flags >> 0) & 1)";
|
|
110
|
+
expect(NarrowingCastHelper.wrap(expr, "int", "bool")).toBe(
|
|
111
|
+
"((((flags >> 0) & 1)) != 0U)",
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe("getPromotedType", () => {
|
|
117
|
+
it("returns 'int' for u8 (promoted)", () => {
|
|
118
|
+
expect(NarrowingCastHelper.getPromotedType("u8")).toBe("int");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("returns 'int' for i8 (promoted)", () => {
|
|
122
|
+
expect(NarrowingCastHelper.getPromotedType("i8")).toBe("int");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("returns 'int' for u16 (promoted)", () => {
|
|
126
|
+
expect(NarrowingCastHelper.getPromotedType("u16")).toBe("int");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("returns 'int' for i16 (promoted)", () => {
|
|
130
|
+
expect(NarrowingCastHelper.getPromotedType("i16")).toBe("int");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("returns same type for u32 (no promotion)", () => {
|
|
134
|
+
expect(NarrowingCastHelper.getPromotedType("u32")).toBe("u32");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("returns same type for i32 (no promotion)", () => {
|
|
138
|
+
expect(NarrowingCastHelper.getPromotedType("i32")).toBe("i32");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("returns same type for u64 (no promotion)", () => {
|
|
142
|
+
expect(NarrowingCastHelper.getPromotedType("u64")).toBe("u64");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("returns same type for i64 (no promotion)", () => {
|
|
146
|
+
expect(NarrowingCastHelper.getPromotedType("i64")).toBe("i64");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("returns 'int' for bool (promoted)", () => {
|
|
150
|
+
expect(NarrowingCastHelper.getPromotedType("bool")).toBe("int");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("returns same type for unknown types (conservative)", () => {
|
|
154
|
+
expect(NarrowingCastHelper.getPromotedType("custom_type")).toBe(
|
|
155
|
+
"custom_type",
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map compound assignment operator to its binary operator equivalent.
|
|
3
|
+
*
|
|
4
|
+
* Shared constant used by:
|
|
5
|
+
* - SimpleHandler.ts: MISRA 10.3 compound assignment expansion
|
|
6
|
+
* - AtomicGenerator.ts: Atomic compound operation expansion
|
|
7
|
+
*/
|
|
8
|
+
const COMPOUND_TO_BINARY: Record<string, string> = {
|
|
9
|
+
"+=": "+",
|
|
10
|
+
"-=": "-",
|
|
11
|
+
"*=": "*",
|
|
12
|
+
"/=": "/",
|
|
13
|
+
"%=": "%",
|
|
14
|
+
"&=": "&",
|
|
15
|
+
"|=": "|",
|
|
16
|
+
"^=": "^",
|
|
17
|
+
"<<=": "<<",
|
|
18
|
+
">>=": ">>",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default COMPOUND_TO_BINARY;
|
|
@@ -19,6 +19,8 @@ type IArrayAccessInfo = {
|
|
|
19
19
|
widthExpr?: string;
|
|
20
20
|
/** Type info for the variable being accessed */
|
|
21
21
|
typeInfo?: TTypeInfo;
|
|
22
|
+
/** Target type for narrowing cast (e.g., "u8" when extracting to uint8_t) */
|
|
23
|
+
targetType?: string;
|
|
22
24
|
/** Source line for error messages */
|
|
23
25
|
line: number;
|
|
24
26
|
};
|
|
@@ -277,29 +277,27 @@ class HeaderGeneratorUtils {
|
|
|
277
277
|
lines.push("#include <stdint.h>", "#include <stdbool.h>");
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
// User includes
|
|
281
|
-
// Issue #933: Transform .h to .hpp in C++ mode so headers include correct files
|
|
280
|
+
// User includes (already have correct extension from IncludeExtractor)
|
|
282
281
|
if (options.userIncludes && options.userIncludes.length > 0) {
|
|
283
282
|
for (const include of options.userIncludes) {
|
|
284
|
-
|
|
285
|
-
? include.replace(/\.h"/, '.hpp"').replace(/\.h>/, ".hpp>")
|
|
286
|
-
: include;
|
|
287
|
-
lines.push(transformedInclude);
|
|
283
|
+
lines.push(include);
|
|
288
284
|
}
|
|
289
285
|
}
|
|
290
286
|
|
|
291
287
|
// External type header includes (skip duplicates of user includes)
|
|
292
|
-
// Note:
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
288
|
+
// Note: headersToInclude comes from IncludeResolver which always produces .h
|
|
289
|
+
// (it runs before cppDetected is set). We need to match against userIncludes
|
|
290
|
+
// which may have .hpp in C++ mode, so normalize both for deduplication.
|
|
291
|
+
const userIncludeSet = new Set(options.userIncludes ?? []);
|
|
292
|
+
// Issue #941: Also add .h variants for dedup since headersToInclude uses .h
|
|
293
|
+
if (options.cppMode && options.userIncludes) {
|
|
294
|
+
for (const inc of options.userIncludes) {
|
|
295
|
+
// Add the .h version so we can match against headersToInclude
|
|
296
|
+
const hVersion = inc.replace(/\.hpp"/, '.h"').replace(/\.hpp>/, ".h>");
|
|
297
|
+
userIncludeSet.add(hVersion);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
301
300
|
for (const directive of headersToInclude) {
|
|
302
|
-
// Don't transform external C headers - they should stay as .h
|
|
303
301
|
if (!userIncludeSet.has(directive)) {
|
|
304
302
|
lines.push(directive);
|
|
305
303
|
}
|
|
@@ -542,6 +542,31 @@ describe("HeaderGeneratorUtils", () => {
|
|
|
542
542
|
|
|
543
543
|
expect(lines[lines.length - 1]).toBe("");
|
|
544
544
|
});
|
|
545
|
+
|
|
546
|
+
it("passes through userIncludes as-is (extension already correct from IncludeExtractor)", () => {
|
|
547
|
+
const lines = HeaderGeneratorUtils.generateIncludes(
|
|
548
|
+
{
|
|
549
|
+
userIncludes: ['#include "types.hpp"'],
|
|
550
|
+
cppMode: true,
|
|
551
|
+
},
|
|
552
|
+
new Set(),
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
expect(lines).toContain('#include "types.hpp"');
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it("deduplicates headersToInclude against userIncludes with .h/.hpp normalization in C++ mode", () => {
|
|
559
|
+
const result = HeaderGeneratorUtils.generateIncludes(
|
|
560
|
+
{
|
|
561
|
+
userIncludes: ['#include "types.hpp"'],
|
|
562
|
+
cppMode: true,
|
|
563
|
+
},
|
|
564
|
+
new Set(['#include "types.h"']),
|
|
565
|
+
);
|
|
566
|
+
// Should NOT have duplicate - the .h version should be filtered
|
|
567
|
+
const typeIncludes = result.filter((l) => l.includes("types"));
|
|
568
|
+
expect(typeIncludes).toEqual(['#include "types.hpp"']);
|
|
569
|
+
});
|
|
545
570
|
});
|
|
546
571
|
|
|
547
572
|
describe("generateCppWrapperStart", () => {
|
|
@@ -220,6 +220,14 @@ export default class CodeGenState {
|
|
|
220
220
|
/** Expected type for struct initializers and enum inference */
|
|
221
221
|
static expectedType: string | null = null;
|
|
222
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Suppress bare enum resolution even when expectedType is set.
|
|
225
|
+
* Issue #872: MISRA 7.2 requires expectedType for U suffix on function args,
|
|
226
|
+
* but bare enum resolution in function args was never allowed and changing
|
|
227
|
+
* that would require ADR approval.
|
|
228
|
+
*/
|
|
229
|
+
static suppressBareEnumResolution: boolean = false;
|
|
230
|
+
|
|
223
231
|
/** Track args parameter name for main() translation */
|
|
224
232
|
static mainArgsName: string | null = null;
|
|
225
233
|
|
|
@@ -270,6 +278,18 @@ export default class CodeGenState {
|
|
|
270
278
|
/** Issue #473: IRQ wrappers for critical sections */
|
|
271
279
|
static needsIrqWrappers: boolean = false;
|
|
272
280
|
|
|
281
|
+
// ===========================================================================
|
|
282
|
+
// OPAQUE TYPE SCOPE VARIABLES (Issue #948)
|
|
283
|
+
// ===========================================================================
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Tracks scope variables with opaque (forward-declared) struct types.
|
|
287
|
+
* These are generated as pointers with NULL initialization and should
|
|
288
|
+
* be passed directly (not with &) since they're already pointers.
|
|
289
|
+
* Maps qualified name (e.g., "MyScope_widget") to true.
|
|
290
|
+
*/
|
|
291
|
+
private static opaqueScopeVariables: Set<string> = new Set();
|
|
292
|
+
|
|
273
293
|
// ===========================================================================
|
|
274
294
|
// C++ MODE STATE (Issue #250)
|
|
275
295
|
// ===========================================================================
|
|
@@ -362,6 +382,7 @@ export default class CodeGenState {
|
|
|
362
382
|
this.indentLevel = 0;
|
|
363
383
|
this.inFunctionBody = false;
|
|
364
384
|
this.expectedType = null;
|
|
385
|
+
this.suppressBareEnumResolution = false;
|
|
365
386
|
this.mainArgsName = null;
|
|
366
387
|
this.assignmentContext = {
|
|
367
388
|
targetName: null,
|
|
@@ -391,6 +412,9 @@ export default class CodeGenState {
|
|
|
391
412
|
this.pendingCppClassAssignments = [];
|
|
392
413
|
this.selfIncludeAdded = false;
|
|
393
414
|
|
|
415
|
+
// Issue #948: Opaque scope variables (reset per-file)
|
|
416
|
+
this.opaqueScopeVariables = new Set();
|
|
417
|
+
|
|
394
418
|
// Source paths
|
|
395
419
|
this.sourcePath = null;
|
|
396
420
|
this.includeDirs = [];
|
|
@@ -421,6 +445,37 @@ export default class CodeGenState {
|
|
|
421
445
|
this.floatShadowCurrent.clear();
|
|
422
446
|
}
|
|
423
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Execute a function with a temporary expectedType, restoring on completion.
|
|
450
|
+
* Issue #872: Extracted to eliminate duplicate save/restore pattern and add exception safety.
|
|
451
|
+
*
|
|
452
|
+
* @param type - The expected type to set (if falsy, no change is made)
|
|
453
|
+
* @param fn - The function to execute
|
|
454
|
+
* @param suppressEnumResolution - If true, suppress bare enum resolution (for MISRA-only contexts)
|
|
455
|
+
* @returns The result of the function
|
|
456
|
+
*/
|
|
457
|
+
static withExpectedType<T>(
|
|
458
|
+
type: string | undefined | null,
|
|
459
|
+
fn: () => T,
|
|
460
|
+
suppressEnumResolution: boolean = false,
|
|
461
|
+
): T {
|
|
462
|
+
if (!type) {
|
|
463
|
+
return fn();
|
|
464
|
+
}
|
|
465
|
+
const savedType = this.expectedType;
|
|
466
|
+
const savedSuppress = this.suppressBareEnumResolution;
|
|
467
|
+
this.expectedType = type;
|
|
468
|
+
if (suppressEnumResolution) {
|
|
469
|
+
this.suppressBareEnumResolution = true;
|
|
470
|
+
}
|
|
471
|
+
try {
|
|
472
|
+
return fn();
|
|
473
|
+
} finally {
|
|
474
|
+
this.expectedType = savedType;
|
|
475
|
+
this.suppressBareEnumResolution = savedSuppress;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
424
479
|
// ===========================================================================
|
|
425
480
|
// CONVENIENCE LOOKUP METHODS
|
|
426
481
|
// ===========================================================================
|
|
@@ -464,6 +519,15 @@ export default class CodeGenState {
|
|
|
464
519
|
return this.symbols?.knownRegisters.has(name) ?? false;
|
|
465
520
|
}
|
|
466
521
|
|
|
522
|
+
/**
|
|
523
|
+
* Issue #948: Check if a type name is an opaque (forward-declared) struct type.
|
|
524
|
+
* Opaque types are incomplete types that can only be used as pointers.
|
|
525
|
+
* Example: `typedef struct _widget_t widget_t;` without a body makes `widget_t` opaque.
|
|
526
|
+
*/
|
|
527
|
+
static isOpaqueType(typeName: string): boolean {
|
|
528
|
+
return this.symbols?.opaqueTypes.has(typeName) ?? false;
|
|
529
|
+
}
|
|
530
|
+
|
|
467
531
|
/**
|
|
468
532
|
* Get type info for a variable.
|
|
469
533
|
* Checks local typeRegistry first, then falls back to SymbolTable
|
|
@@ -1006,6 +1070,31 @@ export default class CodeGenState {
|
|
|
1006
1070
|
return this.floatShadowCurrent.has(name);
|
|
1007
1071
|
}
|
|
1008
1072
|
|
|
1073
|
+
// ===========================================================================
|
|
1074
|
+
// OPAQUE SCOPE VARIABLE HELPERS (Issue #948)
|
|
1075
|
+
// ===========================================================================
|
|
1076
|
+
|
|
1077
|
+
/**
|
|
1078
|
+
* Mark a scope variable as having an opaque (forward-declared) struct type.
|
|
1079
|
+
* These are generated as pointers with NULL initialization.
|
|
1080
|
+
*
|
|
1081
|
+
* @param qualifiedName - The fully qualified variable name (e.g., "MyScope_widget")
|
|
1082
|
+
*/
|
|
1083
|
+
static markOpaqueScopeVariable(qualifiedName: string): void {
|
|
1084
|
+
this.opaqueScopeVariables.add(qualifiedName);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* Check if a scope variable has an opaque type (and is thus a pointer).
|
|
1089
|
+
* Used during access generation to determine if & prefix is needed.
|
|
1090
|
+
*
|
|
1091
|
+
* @param qualifiedName - The fully qualified variable name (e.g., "MyScope_widget")
|
|
1092
|
+
* @returns true if this is an opaque scope variable (already a pointer)
|
|
1093
|
+
*/
|
|
1094
|
+
static isOpaqueScopeVariable(qualifiedName: string): boolean {
|
|
1095
|
+
return this.opaqueScopeVariables.has(qualifiedName);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1009
1098
|
// ===========================================================================
|
|
1010
1099
|
// C++ MODE HELPERS
|
|
1011
1100
|
// ===========================================================================
|
|
@@ -69,6 +69,7 @@ function createMockSymbols(
|
|
|
69
69
|
structFieldArrays: Map<string, Set<string>>;
|
|
70
70
|
functionReturnTypes: Map<string, string>;
|
|
71
71
|
scopeMemberVisibility: Map<string, Map<string, "public" | "private">>;
|
|
72
|
+
opaqueTypes: Set<string>;
|
|
72
73
|
}> = {},
|
|
73
74
|
): ICodeGenSymbols {
|
|
74
75
|
return {
|
|
@@ -95,6 +96,7 @@ function createMockSymbols(
|
|
|
95
96
|
scopeVariableUsage: new Map(),
|
|
96
97
|
scopePrivateConstValues: new Map(),
|
|
97
98
|
functionReturnTypes: overrides.functionReturnTypes ?? new Map(),
|
|
99
|
+
opaqueTypes: overrides.opaqueTypes ?? new Set(),
|
|
98
100
|
getSingleFunctionForVariable: () => null,
|
|
99
101
|
hasPublicSymbols: () => false,
|
|
100
102
|
};
|
|
@@ -714,6 +716,21 @@ describe("CodeGenState", () => {
|
|
|
714
716
|
expect(CodeGenState.isKnownScope("MyScope")).toBe(true);
|
|
715
717
|
expect(CodeGenState.isKnownScope("UnknownScope")).toBe(false);
|
|
716
718
|
});
|
|
719
|
+
|
|
720
|
+
it("isOpaqueType returns false without symbols", () => {
|
|
721
|
+
CodeGenState.symbols = null;
|
|
722
|
+
expect(CodeGenState.isOpaqueType("widget_t")).toBe(false);
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
it("isOpaqueType returns true for opaque type", () => {
|
|
726
|
+
CodeGenState.symbols = createMockSymbols({
|
|
727
|
+
opaqueTypes: new Set(["widget_t", "display_t"]),
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
expect(CodeGenState.isOpaqueType("widget_t")).toBe(true);
|
|
731
|
+
expect(CodeGenState.isOpaqueType("display_t")).toBe(true);
|
|
732
|
+
expect(CodeGenState.isOpaqueType("Point")).toBe(false);
|
|
733
|
+
});
|
|
717
734
|
});
|
|
718
735
|
|
|
719
736
|
describe("Local Variable Helpers", () => {
|
|
@@ -848,4 +865,40 @@ describe("CodeGenState", () => {
|
|
|
848
865
|
expect(result.get("Simple")?.has("value")).toBe(true);
|
|
849
866
|
});
|
|
850
867
|
});
|
|
868
|
+
|
|
869
|
+
describe("Opaque Scope Variable Helpers (Issue #948)", () => {
|
|
870
|
+
it("markOpaqueScopeVariable adds to opaqueScopeVariables", () => {
|
|
871
|
+
CodeGenState.markOpaqueScopeVariable("MyScope_widget");
|
|
872
|
+
expect(CodeGenState.isOpaqueScopeVariable("MyScope_widget")).toBe(true);
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
it("isOpaqueScopeVariable returns false for unknown variable", () => {
|
|
876
|
+
expect(CodeGenState.isOpaqueScopeVariable("Unknown_var")).toBe(false);
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
it("isOpaqueScopeVariable returns true for marked variable", () => {
|
|
880
|
+
CodeGenState.markOpaqueScopeVariable("Gui_display");
|
|
881
|
+
expect(CodeGenState.isOpaqueScopeVariable("Gui_display")).toBe(true);
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
it("reset clears opaqueScopeVariables", () => {
|
|
885
|
+
CodeGenState.markOpaqueScopeVariable("Test_opaque");
|
|
886
|
+
expect(CodeGenState.isOpaqueScopeVariable("Test_opaque")).toBe(true);
|
|
887
|
+
|
|
888
|
+
CodeGenState.reset();
|
|
889
|
+
|
|
890
|
+
expect(CodeGenState.isOpaqueScopeVariable("Test_opaque")).toBe(false);
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
it("handles multiple opaque scope variables", () => {
|
|
894
|
+
CodeGenState.markOpaqueScopeVariable("Scope1_widget");
|
|
895
|
+
CodeGenState.markOpaqueScopeVariable("Scope1_display");
|
|
896
|
+
CodeGenState.markOpaqueScopeVariable("Scope2_handle");
|
|
897
|
+
|
|
898
|
+
expect(CodeGenState.isOpaqueScopeVariable("Scope1_widget")).toBe(true);
|
|
899
|
+
expect(CodeGenState.isOpaqueScopeVariable("Scope1_display")).toBe(true);
|
|
900
|
+
expect(CodeGenState.isOpaqueScopeVariable("Scope2_handle")).toBe(true);
|
|
901
|
+
expect(CodeGenState.isOpaqueScopeVariable("Scope1_other")).toBe(false);
|
|
902
|
+
});
|
|
903
|
+
});
|
|
851
904
|
});
|