c-next 0.2.5 → 0.2.7
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 +617 -474
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/src/transpiler/Transpiler.ts +50 -29
- package/src/transpiler/__tests__/Transpiler.coverage.test.ts +2 -1
- package/src/transpiler/data/PathResolver.ts +10 -6
- package/src/transpiler/data/__tests__/PathResolver.test.ts +32 -0
- package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +82 -7
- package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +1 -12
- package/src/transpiler/logic/analysis/SignedShiftAnalyzer.ts +239 -0
- package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +92 -0
- package/src/transpiler/logic/analysis/__tests__/SignedShiftAnalyzer.test.ts +414 -0
- package/src/transpiler/logic/analysis/__tests__/StructFieldAnalyzer.test.ts +15 -75
- package/src/transpiler/logic/analysis/__tests__/runAnalyzers.test.ts +3 -15
- package/src/transpiler/logic/analysis/runAnalyzers.ts +11 -2
- package/src/transpiler/logic/analysis/types/ISignedShiftError.ts +15 -0
- package/src/transpiler/logic/symbols/SymbolUtils.ts +4 -1
- package/src/transpiler/logic/symbols/__tests__/SymbolUtils.test.ts +10 -11
- package/src/transpiler/logic/symbols/c/__tests__/CResolver.integration.test.ts +27 -4
- package/src/transpiler/logic/symbols/c/collectors/FunctionCollector.ts +11 -5
- package/src/transpiler/logic/symbols/cpp/__tests__/CppResolver.integration.test.ts +4 -4
- package/src/transpiler/output/codegen/CodeGenerator.ts +169 -11
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +132 -3
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +56 -31
- package/src/transpiler/output/codegen/analysis/StringLengthCounter.ts +12 -11
- package/src/transpiler/output/codegen/analysis/__tests__/StringLengthCounter.test.ts +21 -21
- package/src/transpiler/output/codegen/assignment/handlers/AccessPatternHandlers.ts +2 -2
- package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +2 -2
- package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +2 -2
- package/src/transpiler/output/codegen/assignment/handlers/RegisterHandlers.ts +4 -4
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/AccessPatternHandlers.test.ts +4 -4
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +8 -8
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitmapHandlers.test.ts +5 -5
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/RegisterHandlers.test.ts +4 -4
- package/src/transpiler/output/codegen/generators/expressions/AccessExprGenerator.ts +7 -326
- package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +8 -1
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +14 -275
- package/src/transpiler/output/codegen/generators/expressions/UnaryExprGenerator.ts +13 -1
- package/src/transpiler/output/codegen/generators/expressions/__tests__/AccessExprGenerator.test.ts +0 -573
- package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +8 -439
- package/src/transpiler/output/codegen/generators/expressions/__tests__/UnaryExprGenerator.test.ts +196 -0
- package/src/transpiler/output/codegen/helpers/ArgumentGenerator.ts +5 -0
- package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +7 -2
- package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +8 -1
- package/src/transpiler/output/codegen/helpers/TypedefParamParser.ts +34 -0
- package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +11 -0
- package/src/transpiler/output/codegen/helpers/VariableModifierBuilder.ts +16 -1
- package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +48 -0
- package/src/transpiler/output/codegen/helpers/__tests__/TypedefParamParser.test.ts +88 -0
- package/src/transpiler/output/codegen/helpers/__tests__/VariableModifierBuilder.test.ts +34 -2
- package/src/transpiler/output/codegen/types/TTypeInfo.ts +1 -0
- package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +15 -2
- package/src/transpiler/output/headers/__tests__/BaseHeaderGenerator.test.ts +87 -0
- package/src/utils/BitUtils.ts +17 -13
- package/src/utils/ExpressionUtils.ts +51 -0
- package/src/utils/__tests__/BitUtils.test.ts +56 -56
- package/src/utils/types/IParameterSymbol.ts +2 -0
|
@@ -303,7 +303,14 @@ class StringDeclHelper {
|
|
|
303
303
|
initValue,
|
|
304
304
|
arrayDims,
|
|
305
305
|
);
|
|
306
|
-
|
|
306
|
+
// MISRA C:2012 Rules 9.3/9.4 - String literals don't fill all inner array bytes,
|
|
307
|
+
// but C standard guarantees zero-initialization of remaining elements
|
|
308
|
+
const suppression =
|
|
309
|
+
"// cppcheck-suppress misra-c2012-9.3\n// cppcheck-suppress misra-c2012-9.4\n";
|
|
310
|
+
return {
|
|
311
|
+
code: `${suppression}${decl} = ${finalInitValue};`,
|
|
312
|
+
handled: true,
|
|
313
|
+
};
|
|
307
314
|
}
|
|
308
315
|
|
|
309
316
|
/**
|
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
* "void (*)(Point p)"
|
|
7
7
|
*
|
|
8
8
|
* Used by Issue #895 to determine if callback params should be pointers or values.
|
|
9
|
+
* Used by Issue #914 to resolve callback pointer/const info onto IParameterSymbol.
|
|
9
10
|
*/
|
|
10
11
|
|
|
12
|
+
import IParameterSymbol from "../../../../utils/types/IParameterSymbol";
|
|
13
|
+
|
|
11
14
|
/**
|
|
12
15
|
* Parsed parameter info from a typedef.
|
|
13
16
|
*/
|
|
@@ -215,6 +218,37 @@ class TypedefParamParser {
|
|
|
215
218
|
TypedefParamParser.getParamAt(typedefType, paramIndex)?.isConst ?? null
|
|
216
219
|
);
|
|
217
220
|
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Resolve callback pointer/const overrides onto an array of IParameterSymbol.
|
|
224
|
+
*
|
|
225
|
+
* Issue #914: This is the single point where callback typedef info is applied
|
|
226
|
+
* to parameters — used by both .c and .h generation paths via IParameterSymbol.
|
|
227
|
+
*
|
|
228
|
+
* @param params - The original parameter symbols
|
|
229
|
+
* @param callbackTypedefType - The typedef type string (e.g., "void (*)(widget_t *, const rect_t *)")
|
|
230
|
+
* @returns New array with isCallbackPointer/isCallbackConst resolved
|
|
231
|
+
*/
|
|
232
|
+
static resolveCallbackParams(
|
|
233
|
+
params: readonly IParameterSymbol[],
|
|
234
|
+
callbackTypedefType: string,
|
|
235
|
+
): IParameterSymbol[] {
|
|
236
|
+
return params.map((param, index) => {
|
|
237
|
+
const shouldBePointer = TypedefParamParser.shouldBePointer(
|
|
238
|
+
callbackTypedefType,
|
|
239
|
+
index,
|
|
240
|
+
);
|
|
241
|
+
const shouldBeConst = TypedefParamParser.shouldBeConst(
|
|
242
|
+
callbackTypedefType,
|
|
243
|
+
index,
|
|
244
|
+
);
|
|
245
|
+
return {
|
|
246
|
+
...param,
|
|
247
|
+
isCallbackPointer: shouldBePointer ?? undefined,
|
|
248
|
+
isCallbackConst: shouldBeConst ?? undefined,
|
|
249
|
+
};
|
|
250
|
+
});
|
|
251
|
+
}
|
|
218
252
|
}
|
|
219
253
|
|
|
220
254
|
export default TypedefParamParser;
|
|
@@ -139,6 +139,8 @@ interface IVariableDeclCallbacks {
|
|
|
139
139
|
ctx: Parser.VariableDeclarationContext,
|
|
140
140
|
name: string,
|
|
141
141
|
) => void;
|
|
142
|
+
/** Issue #895 Bug B: Mark variable as pointer in type registry */
|
|
143
|
+
markVariableAsPointer: (name: string) => void;
|
|
142
144
|
/** Get string concatenation operands */
|
|
143
145
|
getStringConcatOperands: (
|
|
144
146
|
ctx: Parser.ExpressionContext,
|
|
@@ -571,9 +573,13 @@ class VariableDeclHelper {
|
|
|
571
573
|
}
|
|
572
574
|
|
|
573
575
|
// Issue #696: Use helper for modifier extraction and validation
|
|
576
|
+
// Issue #852 (MISRA Rule 8.5): Pass hasInitializer and cppMode for correct extern behavior
|
|
577
|
+
const hasInitializer = ctx.expression() !== null;
|
|
574
578
|
const modifiers = VariableModifierBuilder.build(
|
|
575
579
|
ctx,
|
|
576
580
|
CodeGenState.inFunctionBody,
|
|
581
|
+
hasInitializer,
|
|
582
|
+
CodeGenState.cppMode,
|
|
577
583
|
);
|
|
578
584
|
|
|
579
585
|
const name = ctx.IDENTIFIER().getText();
|
|
@@ -587,6 +593,11 @@ class VariableDeclHelper {
|
|
|
587
593
|
// Track local variable metadata
|
|
588
594
|
callbacks.trackLocalVariable(ctx, name);
|
|
589
595
|
|
|
596
|
+
// Issue #895 Bug B: If type was inferred as pointer, mark it in the registry
|
|
597
|
+
if (type.endsWith("*")) {
|
|
598
|
+
callbacks.markVariableAsPointer(name);
|
|
599
|
+
}
|
|
600
|
+
|
|
590
601
|
// ADR-045: Handle bounded string type specially - early return
|
|
591
602
|
const stringResult = StringDeclHelper.generateStringDecl(
|
|
592
603
|
typeCtx,
|
|
@@ -46,12 +46,16 @@ class VariableModifierBuilder {
|
|
|
46
46
|
*
|
|
47
47
|
* @param ctx - Parser context with modifier methods
|
|
48
48
|
* @param inFunctionBody - Whether we're inside a function body (affects extern)
|
|
49
|
+
* @param hasInitializer - Whether the variable has an initializer (affects extern in C mode)
|
|
50
|
+
* @param cppMode - Whether we're generating C++ code (affects extern behavior)
|
|
49
51
|
* @returns Modifier strings ready for use in generated code
|
|
50
52
|
* @throws Error if both atomic and volatile are specified
|
|
51
53
|
*/
|
|
52
54
|
static build(
|
|
53
55
|
ctx: IModifierContext,
|
|
54
56
|
inFunctionBody: boolean,
|
|
57
|
+
hasInitializer: boolean = false,
|
|
58
|
+
cppMode: boolean = false,
|
|
55
59
|
): IVariableModifiers {
|
|
56
60
|
const hasConst = ctx.constModifier?.() ?? false;
|
|
57
61
|
const constMod = hasConst ? "const " : "";
|
|
@@ -59,7 +63,18 @@ class VariableModifierBuilder {
|
|
|
59
63
|
const volatileMod = ctx.volatileModifier() ? "volatile " : "";
|
|
60
64
|
|
|
61
65
|
// Issue #525: Add extern for top-level const in C++ for external linkage
|
|
62
|
-
const
|
|
66
|
+
// In C++, const at file scope has internal linkage by default, so extern is needed.
|
|
67
|
+
//
|
|
68
|
+
// Issue #852 (MISRA Rule 8.5): In C mode, do NOT add extern to definitions
|
|
69
|
+
// (variables with initializers). The extern declaration comes from the header.
|
|
70
|
+
//
|
|
71
|
+
// Summary:
|
|
72
|
+
// - C mode + no initializer: extern (declaration)
|
|
73
|
+
// - C mode + initializer: NO extern (definition - MISRA 8.5)
|
|
74
|
+
// - C++ mode: ALWAYS extern for external linkage (both declarations and definitions)
|
|
75
|
+
const needsExtern =
|
|
76
|
+
hasConst && !inFunctionBody && (cppMode || !hasInitializer);
|
|
77
|
+
const externMod = needsExtern ? "extern " : "";
|
|
63
78
|
|
|
64
79
|
// Validate: cannot use both atomic and volatile
|
|
65
80
|
if (ctx.atomicModifier() && ctx.volatileModifier()) {
|
|
@@ -285,6 +285,54 @@ describe("ParameterInputAdapter", () => {
|
|
|
285
285
|
|
|
286
286
|
expect(result.isAutoConst).toBe(false);
|
|
287
287
|
});
|
|
288
|
+
|
|
289
|
+
it("forces pointer when isCallbackPointer is set (Issue #914)", () => {
|
|
290
|
+
const param: IParameterSymbol = {
|
|
291
|
+
name: "buf",
|
|
292
|
+
type: "u8",
|
|
293
|
+
isConst: false,
|
|
294
|
+
isArray: false,
|
|
295
|
+
isCallbackPointer: true,
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// Even though deps says pass-by-value, callback overrides it
|
|
299
|
+
const deps = { ...defaultDeps, isPassByValue: true };
|
|
300
|
+
const result = ParameterInputAdapter.fromSymbol(param, deps);
|
|
301
|
+
|
|
302
|
+
expect(result.isPassByValue).toBe(false);
|
|
303
|
+
expect(result.isPassByReference).toBe(true);
|
|
304
|
+
expect(result.forcePointerSyntax).toBe(true);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("forces const when isCallbackConst is set (Issue #914)", () => {
|
|
308
|
+
const param: IParameterSymbol = {
|
|
309
|
+
name: "area",
|
|
310
|
+
type: "rect_t",
|
|
311
|
+
isConst: false,
|
|
312
|
+
isArray: false,
|
|
313
|
+
isCallbackPointer: true,
|
|
314
|
+
isCallbackConst: true,
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const result = ParameterInputAdapter.fromSymbol(param, defaultDeps);
|
|
318
|
+
|
|
319
|
+
expect(result.forceConst).toBe(true);
|
|
320
|
+
expect(result.forcePointerSyntax).toBe(true);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it("does not force pointer when isCallbackPointer is not set", () => {
|
|
324
|
+
const param: IParameterSymbol = {
|
|
325
|
+
name: "value",
|
|
326
|
+
type: "u32",
|
|
327
|
+
isConst: false,
|
|
328
|
+
isArray: false,
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const result = ParameterInputAdapter.fromSymbol(param, defaultDeps);
|
|
332
|
+
|
|
333
|
+
expect(result.forcePointerSyntax).toBeUndefined();
|
|
334
|
+
expect(result.forceConst).toBeUndefined();
|
|
335
|
+
});
|
|
288
336
|
});
|
|
289
337
|
|
|
290
338
|
describe("fromAST", () => {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { describe, expect, it } from "vitest";
|
|
6
6
|
import TypedefParamParser from "../TypedefParamParser";
|
|
7
|
+
import IParameterSymbol from "../../../../../utils/types/IParameterSymbol";
|
|
7
8
|
|
|
8
9
|
describe("TypedefParamParser", () => {
|
|
9
10
|
describe("parse", () => {
|
|
@@ -206,4 +207,91 @@ describe("TypedefParamParser", () => {
|
|
|
206
207
|
expect(TypedefParamParser.shouldBeConst(typedef, 2)).toBe(false);
|
|
207
208
|
});
|
|
208
209
|
});
|
|
210
|
+
|
|
211
|
+
describe("resolveCallbackParams (Issue #914)", () => {
|
|
212
|
+
function makeParam(
|
|
213
|
+
name: string,
|
|
214
|
+
type: string,
|
|
215
|
+
overrides?: Partial<IParameterSymbol>,
|
|
216
|
+
): IParameterSymbol {
|
|
217
|
+
return {
|
|
218
|
+
name,
|
|
219
|
+
type,
|
|
220
|
+
isConst: false,
|
|
221
|
+
isArray: false,
|
|
222
|
+
...overrides,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
it("should set isCallbackPointer for pointer params", () => {
|
|
227
|
+
const params = [makeParam("w", "widget_t"), makeParam("buf", "u8")];
|
|
228
|
+
const typedef = "void (*)(widget_t *, uint8_t *)";
|
|
229
|
+
|
|
230
|
+
const result = TypedefParamParser.resolveCallbackParams(params, typedef);
|
|
231
|
+
|
|
232
|
+
expect(result[0].isCallbackPointer).toBe(true);
|
|
233
|
+
expect(result[1].isCallbackPointer).toBe(true);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("should set isCallbackConst for const pointer params", () => {
|
|
237
|
+
const params = [
|
|
238
|
+
makeParam("w", "widget_t"),
|
|
239
|
+
makeParam("area", "rect_t"),
|
|
240
|
+
makeParam("buf", "u8"),
|
|
241
|
+
];
|
|
242
|
+
const typedef = "void (*)(widget_t *, const rect_t *, uint8_t *)";
|
|
243
|
+
|
|
244
|
+
const result = TypedefParamParser.resolveCallbackParams(params, typedef);
|
|
245
|
+
|
|
246
|
+
expect(result[0].isCallbackPointer).toBe(true);
|
|
247
|
+
expect(result[0].isCallbackConst).toBe(false);
|
|
248
|
+
|
|
249
|
+
expect(result[1].isCallbackPointer).toBe(true);
|
|
250
|
+
expect(result[1].isCallbackConst).toBe(true);
|
|
251
|
+
|
|
252
|
+
expect(result[2].isCallbackPointer).toBe(true);
|
|
253
|
+
expect(result[2].isCallbackConst).toBe(false);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("should not set callback flags for value params", () => {
|
|
257
|
+
const params = [makeParam("count", "u32")];
|
|
258
|
+
const typedef = "void (*)(int count)";
|
|
259
|
+
|
|
260
|
+
const result = TypedefParamParser.resolveCallbackParams(params, typedef);
|
|
261
|
+
|
|
262
|
+
expect(result[0].isCallbackPointer).toBe(false);
|
|
263
|
+
expect(result[0].isCallbackConst).toBe(false);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("should preserve existing param fields", () => {
|
|
267
|
+
const params = [
|
|
268
|
+
makeParam("data", "u8", {
|
|
269
|
+
isConst: true,
|
|
270
|
+
isArray: true,
|
|
271
|
+
arrayDimensions: ["10"],
|
|
272
|
+
}),
|
|
273
|
+
];
|
|
274
|
+
const typedef = "void (*)(uint8_t *)";
|
|
275
|
+
|
|
276
|
+
const result = TypedefParamParser.resolveCallbackParams(params, typedef);
|
|
277
|
+
|
|
278
|
+
expect(result[0].name).toBe("data");
|
|
279
|
+
expect(result[0].type).toBe("u8");
|
|
280
|
+
expect(result[0].isConst).toBe(true);
|
|
281
|
+
expect(result[0].isArray).toBe(true);
|
|
282
|
+
expect(result[0].arrayDimensions).toEqual(["10"]);
|
|
283
|
+
expect(result[0].isCallbackPointer).toBe(true);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("should handle params beyond typedef length gracefully", () => {
|
|
287
|
+
const params = [makeParam("w", "widget_t"), makeParam("extra", "u32")];
|
|
288
|
+
const typedef = "void (*)(widget_t *)";
|
|
289
|
+
|
|
290
|
+
const result = TypedefParamParser.resolveCallbackParams(params, typedef);
|
|
291
|
+
|
|
292
|
+
expect(result[0].isCallbackPointer).toBe(true);
|
|
293
|
+
expect(result[1].isCallbackPointer).toBeUndefined();
|
|
294
|
+
expect(result[1].isCallbackConst).toBeUndefined();
|
|
295
|
+
});
|
|
296
|
+
});
|
|
209
297
|
});
|
|
@@ -54,14 +54,46 @@ describe("VariableModifierBuilder", () => {
|
|
|
54
54
|
expect(result.volatile).toBe("volatile ");
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
-
it("returns extern for const at file scope", () => {
|
|
57
|
+
it("returns extern for const at file scope (declaration without initializer, C mode)", () => {
|
|
58
58
|
const ctx = {
|
|
59
59
|
constModifier: () => ({}),
|
|
60
60
|
atomicModifier: () => null,
|
|
61
61
|
volatileModifier: () => null,
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
// Declaration (no initializer) in C mode - should have extern
|
|
65
|
+
const result = VariableModifierBuilder.build(ctx, false, false, false);
|
|
66
|
+
|
|
67
|
+
expect(result.extern).toBe("extern ");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("does not return extern for const at file scope with initializer in C mode (MISRA 8.5)", () => {
|
|
71
|
+
// MISRA Rule 8.5: External object/function shall be declared once in one file only.
|
|
72
|
+
// When a variable has an initializer, it's a DEFINITION, not a declaration.
|
|
73
|
+
// The extern declaration comes from the header; the .c file should not duplicate it.
|
|
74
|
+
const ctx = {
|
|
75
|
+
constModifier: () => ({}),
|
|
76
|
+
atomicModifier: () => null,
|
|
77
|
+
volatileModifier: () => null,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Definition (has initializer) in C mode - should NOT have extern
|
|
81
|
+
const result = VariableModifierBuilder.build(ctx, false, true, false);
|
|
82
|
+
|
|
83
|
+
expect(result.extern).toBe("");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("returns extern for const at file scope with initializer in C++ mode (Issue #525)", () => {
|
|
87
|
+
// In C++, const at file scope has internal linkage by default.
|
|
88
|
+
// extern is needed for cross-file access, even for definitions.
|
|
89
|
+
const ctx = {
|
|
90
|
+
constModifier: () => ({}),
|
|
91
|
+
atomicModifier: () => null,
|
|
92
|
+
volatileModifier: () => null,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Definition (has initializer) in C++ mode - SHOULD have extern for external linkage
|
|
96
|
+
const result = VariableModifierBuilder.build(ctx, false, true, true);
|
|
65
97
|
|
|
66
98
|
expect(result.extern).toBe("extern ");
|
|
67
99
|
});
|
|
@@ -19,6 +19,7 @@ type TTypeInfo = {
|
|
|
19
19
|
isAtomic?: boolean;
|
|
20
20
|
isExternalCppType?: boolean; // Issue #375: C++ types instantiated via constructor
|
|
21
21
|
isParameter?: boolean; // Issue #579: Track if this is a function parameter (becomes pointer in C)
|
|
22
|
+
isPointer?: boolean; // Issue #895 Bug B: Track if variable is a pointer (inferred from C function return type)
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
export default TTypeInfo;
|
|
@@ -278,15 +278,28 @@ class HeaderGeneratorUtils {
|
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
// User includes
|
|
281
|
+
// Issue #933: Transform .h to .hpp in C++ mode so headers include correct files
|
|
281
282
|
if (options.userIncludes && options.userIncludes.length > 0) {
|
|
282
283
|
for (const include of options.userIncludes) {
|
|
283
|
-
|
|
284
|
+
const transformedInclude = options.cppMode
|
|
285
|
+
? include.replace(/\.h"/, '.hpp"').replace(/\.h>/, ".hpp>")
|
|
286
|
+
: include;
|
|
287
|
+
lines.push(transformedInclude);
|
|
284
288
|
}
|
|
285
289
|
}
|
|
286
290
|
|
|
287
291
|
// External type header includes (skip duplicates of user includes)
|
|
288
|
-
|
|
292
|
+
// Note: External C headers stay as .h - only user includes from .cnx files get
|
|
293
|
+
// transformed to .hpp in C++ mode (those are C-Next generated headers)
|
|
294
|
+
const userIncludeSet = new Set(
|
|
295
|
+
options.userIncludes?.map((inc) =>
|
|
296
|
+
options.cppMode
|
|
297
|
+
? inc.replace(/\.h"/, '.hpp"').replace(/\.h>/, ".hpp>")
|
|
298
|
+
: inc,
|
|
299
|
+
) ?? [],
|
|
300
|
+
);
|
|
289
301
|
for (const directive of headersToInclude) {
|
|
302
|
+
// Don't transform external C headers - they should stay as .h
|
|
290
303
|
if (!userIncludeSet.has(directive)) {
|
|
291
304
|
lines.push(directive);
|
|
292
305
|
}
|
|
@@ -20,6 +20,8 @@ function createFunctionSymbol(
|
|
|
20
20
|
isAutoConst?: boolean;
|
|
21
21
|
isArray?: boolean;
|
|
22
22
|
arrayDimensions?: string[];
|
|
23
|
+
isCallbackPointer?: boolean;
|
|
24
|
+
isCallbackConst?: boolean;
|
|
23
25
|
}>,
|
|
24
26
|
): IHeaderSymbol {
|
|
25
27
|
return {
|
|
@@ -36,6 +38,8 @@ function createFunctionSymbol(
|
|
|
36
38
|
isAutoConst: p.isAutoConst ?? false,
|
|
37
39
|
isArray: p.isArray ?? false,
|
|
38
40
|
arrayDimensions: p.arrayDimensions,
|
|
41
|
+
isCallbackPointer: p.isCallbackPointer,
|
|
42
|
+
isCallbackConst: p.isCallbackConst,
|
|
39
43
|
})),
|
|
40
44
|
};
|
|
41
45
|
}
|
|
@@ -239,4 +243,87 @@ describe("BaseHeaderGenerator", () => {
|
|
|
239
243
|
expect(result).toContain("void processData(const MyStruct* data);");
|
|
240
244
|
});
|
|
241
245
|
});
|
|
246
|
+
|
|
247
|
+
describe("Callback-compatible functions (Issue #914)", () => {
|
|
248
|
+
it("should use pointer for primitive param when isCallbackPointer is set (C mode)", () => {
|
|
249
|
+
const generator = new CHeaderGenerator();
|
|
250
|
+
const symbols: IHeaderSymbol[] = [
|
|
251
|
+
createFunctionSymbol("Renderer_flush", "void", [
|
|
252
|
+
{ name: "buf", type: "u8", isCallbackPointer: true },
|
|
253
|
+
]),
|
|
254
|
+
];
|
|
255
|
+
|
|
256
|
+
const result = generator.generate(symbols, "test.h");
|
|
257
|
+
|
|
258
|
+
expect(result).toContain("uint8_t* buf");
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("should use pointer for primitive param when isCallbackPointer is set (C++ mode)", () => {
|
|
262
|
+
const generator = new CppHeaderGenerator();
|
|
263
|
+
const symbols: IHeaderSymbol[] = [
|
|
264
|
+
createFunctionSymbol("Renderer_flush", "void", [
|
|
265
|
+
{ name: "buf", type: "u8", isCallbackPointer: true },
|
|
266
|
+
]),
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
const result = generator.generate(symbols, "test.h");
|
|
270
|
+
|
|
271
|
+
// Must use * not & because headers are wrapped in extern "C"
|
|
272
|
+
expect(result).toContain("uint8_t* buf");
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("should use const pointer for struct param with isCallbackConst (C mode)", () => {
|
|
276
|
+
const generator = new CHeaderGenerator();
|
|
277
|
+
const symbols: IHeaderSymbol[] = [
|
|
278
|
+
createFunctionSymbol("Renderer_flush", "void", [
|
|
279
|
+
{
|
|
280
|
+
name: "area",
|
|
281
|
+
type: "rect_t",
|
|
282
|
+
isCallbackPointer: true,
|
|
283
|
+
isCallbackConst: true,
|
|
284
|
+
},
|
|
285
|
+
]),
|
|
286
|
+
];
|
|
287
|
+
|
|
288
|
+
const result = generator.generate(symbols, "test.h");
|
|
289
|
+
|
|
290
|
+
expect(result).toContain("const rect_t* area");
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("should use non-const pointer for struct param without isCallbackConst (C++ mode)", () => {
|
|
294
|
+
const generator = new CppHeaderGenerator();
|
|
295
|
+
const symbols: IHeaderSymbol[] = [
|
|
296
|
+
createFunctionSymbol("Renderer_flush", "void", [
|
|
297
|
+
{ name: "w", type: "widget_t", isCallbackPointer: true },
|
|
298
|
+
]),
|
|
299
|
+
];
|
|
300
|
+
|
|
301
|
+
const result = generator.generate(symbols, "test.h");
|
|
302
|
+
|
|
303
|
+
// Must use * not & for callback params
|
|
304
|
+
expect(result).toContain("widget_t* w");
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("should handle mixed callback and non-callback params", () => {
|
|
308
|
+
const generator = new CHeaderGenerator();
|
|
309
|
+
const symbols: IHeaderSymbol[] = [
|
|
310
|
+
createFunctionSymbol("Renderer_flush", "void", [
|
|
311
|
+
{ name: "w", type: "widget_t", isCallbackPointer: true },
|
|
312
|
+
{
|
|
313
|
+
name: "area",
|
|
314
|
+
type: "rect_t",
|
|
315
|
+
isCallbackPointer: true,
|
|
316
|
+
isCallbackConst: true,
|
|
317
|
+
},
|
|
318
|
+
{ name: "buf", type: "u8", isCallbackPointer: true },
|
|
319
|
+
]),
|
|
320
|
+
];
|
|
321
|
+
|
|
322
|
+
const result = generator.generate(symbols, "test.h");
|
|
323
|
+
|
|
324
|
+
expect(result).toContain(
|
|
325
|
+
"void Renderer_flush(widget_t* w, const rect_t* area, uint8_t* buf);",
|
|
326
|
+
);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
242
329
|
});
|
package/src/utils/BitUtils.ts
CHANGED
|
@@ -6,16 +6,17 @@
|
|
|
6
6
|
*/
|
|
7
7
|
class BitUtils {
|
|
8
8
|
/**
|
|
9
|
-
* Convert a boolean expression to an integer (
|
|
9
|
+
* Convert a boolean expression to an unsigned integer (0U or 1U).
|
|
10
10
|
* Handles literal "true"/"false" and generates ternary for expressions.
|
|
11
|
+
* Uses unsigned literals for MISRA C:2012 Rule 10.1 compliance.
|
|
11
12
|
*
|
|
12
13
|
* @param expr - The expression to convert
|
|
13
|
-
* @returns C code string representing the integer value
|
|
14
|
+
* @returns C code string representing the unsigned integer value
|
|
14
15
|
*/
|
|
15
16
|
static boolToInt(expr: string): string {
|
|
16
|
-
if (expr === "true") return "
|
|
17
|
-
if (expr === "false") return "
|
|
18
|
-
return `(${expr} ?
|
|
17
|
+
if (expr === "true") return "1U";
|
|
18
|
+
if (expr === "false") return "0U";
|
|
19
|
+
return `(${expr} ? 1U : 0U)`;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -78,25 +79,27 @@ class BitUtils {
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
/**
|
|
81
|
-
* Return the appropriate "1" literal for a given type.
|
|
82
|
-
* Uses "1ULL" for 64-bit types
|
|
82
|
+
* Return the appropriate unsigned "1" literal for a given type.
|
|
83
|
+
* Uses "1ULL" for 64-bit types, "1U" for others.
|
|
84
|
+
* MISRA C:2012 Rule 10.1 requires unsigned operands for bitwise operations.
|
|
83
85
|
*
|
|
84
86
|
* @param typeName - The C-Next type name (e.g., "u64", "i32")
|
|
85
|
-
* @returns "1ULL" for 64-bit types, "
|
|
87
|
+
* @returns "1ULL" for 64-bit types, "1U" otherwise
|
|
86
88
|
*/
|
|
87
89
|
static oneForType(typeName: string): string {
|
|
88
|
-
return typeName === "u64" || typeName === "i64" ? "1ULL" : "
|
|
90
|
+
return typeName === "u64" || typeName === "i64" ? "1ULL" : "1U";
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
/**
|
|
92
|
-
* Format a number as an uppercase hex string (e.g., 255 -> "
|
|
94
|
+
* Format a number as an unsigned uppercase hex string (e.g., 255 -> "0xFFU").
|
|
93
95
|
* Used for generating hex mask literals in generated C code.
|
|
96
|
+
* Includes U suffix for MISRA C:2012 Rule 10.1 compliance.
|
|
94
97
|
*
|
|
95
98
|
* @param value - The numeric value to format
|
|
96
|
-
* @returns Hex string like "
|
|
99
|
+
* @returns Hex string like "0xFFU" or "0x1FU"
|
|
97
100
|
*/
|
|
98
101
|
static formatHex(value: number): string {
|
|
99
|
-
return `0x${value.toString(16).toUpperCase()}`;
|
|
102
|
+
return `0x${value.toString(16).toUpperCase()}U`;
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
/**
|
|
@@ -154,7 +157,7 @@ class BitUtils {
|
|
|
154
157
|
): string {
|
|
155
158
|
const intValue = BitUtils.boolToInt(value);
|
|
156
159
|
const is64Bit = targetType === "u64" || targetType === "i64";
|
|
157
|
-
const one = is64Bit ? "1ULL" : "
|
|
160
|
+
const one = is64Bit ? "1ULL" : "1U";
|
|
158
161
|
// For 64-bit types, cast the value to ensure shift doesn't overflow
|
|
159
162
|
const valueShift = is64Bit
|
|
160
163
|
? `((uint64_t)${intValue} << ${offset})`
|
|
@@ -203,6 +206,7 @@ class BitUtils {
|
|
|
203
206
|
): string {
|
|
204
207
|
const intValue = BitUtils.boolToInt(value);
|
|
205
208
|
// For 64-bit types, cast to ensure correct shift width
|
|
209
|
+
// boolToInt already returns unsigned values (1U/0U) for MISRA 10.1 compliance
|
|
206
210
|
const castPrefix =
|
|
207
211
|
targetType === "u64" || targetType === "i64" ? "(uint64_t)" : "";
|
|
208
212
|
return `${target} = (${castPrefix}${intValue} << ${offset});`;
|
|
@@ -140,6 +140,57 @@ class ExpressionUtils {
|
|
|
140
140
|
return identifier?.getText() ?? null;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Collect all additive expressions from a ternary expression.
|
|
145
|
+
*
|
|
146
|
+
* Uses flatMap chains to traverse the expression grammar efficiently.
|
|
147
|
+
* Useful for analyzers that need to examine operands at the additive level.
|
|
148
|
+
*
|
|
149
|
+
* @param ctx - The ternary expression context
|
|
150
|
+
* @returns Array of all additive expression contexts in the tree
|
|
151
|
+
*/
|
|
152
|
+
static collectAdditiveExpressions(
|
|
153
|
+
ctx: Parser.TernaryExpressionContext,
|
|
154
|
+
): Parser.AdditiveExpressionContext[] {
|
|
155
|
+
return ExpressionUtils.collectAdditiveFromOrExprs(ctx.orExpression());
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Collect additive expressions from an array of orExpression contexts.
|
|
160
|
+
*
|
|
161
|
+
* Internal helper that performs the actual flatMap traversal.
|
|
162
|
+
*/
|
|
163
|
+
private static collectAdditiveFromOrExprs(
|
|
164
|
+
orExprs: Parser.OrExpressionContext[],
|
|
165
|
+
): Parser.AdditiveExpressionContext[] {
|
|
166
|
+
return orExprs
|
|
167
|
+
.flatMap((or) => or.andExpression())
|
|
168
|
+
.flatMap((and) => and.equalityExpression())
|
|
169
|
+
.flatMap((eq) => eq.relationalExpression())
|
|
170
|
+
.flatMap((rel) => rel.bitwiseOrExpression())
|
|
171
|
+
.flatMap((bor) => bor.bitwiseXorExpression())
|
|
172
|
+
.flatMap((bxor) => bxor.bitwiseAndExpression())
|
|
173
|
+
.flatMap((band) => band.shiftExpression())
|
|
174
|
+
.flatMap((shift) => shift.additiveExpression());
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Collect all unary expressions from an orExpression context.
|
|
179
|
+
*
|
|
180
|
+
* Traverses through the entire expression hierarchy to find all unary expressions.
|
|
181
|
+
* Useful for analyzers that need to examine leaf operands.
|
|
182
|
+
*
|
|
183
|
+
* @param orExpr - The orExpression context
|
|
184
|
+
* @returns Array of all unary expression contexts in the tree
|
|
185
|
+
*/
|
|
186
|
+
static collectUnaryFromOrExpr(
|
|
187
|
+
orExpr: Parser.OrExpressionContext,
|
|
188
|
+
): Parser.UnaryExpressionContext[] {
|
|
189
|
+
return ExpressionUtils.collectAdditiveFromOrExprs([orExpr])
|
|
190
|
+
.flatMap((add) => add.multiplicativeExpression())
|
|
191
|
+
.flatMap((mul) => mul.unaryExpression());
|
|
192
|
+
}
|
|
193
|
+
|
|
143
194
|
/**
|
|
144
195
|
* ADR-023: Check if an expression contains a function call anywhere in its tree.
|
|
145
196
|
*
|