c-next 0.2.16 → 0.2.17
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/README.md +16 -0
- package/dist/index.js +1347 -414
- package/dist/index.js.map +3 -3
- package/grammar/CNext.g4 +4 -0
- package/package.json +5 -1
- package/src/transpiler/Transpiler.ts +90 -22
- package/src/transpiler/__tests__/DualCodePaths.test.ts +1 -1
- package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +57 -10
- package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +186 -14
- package/src/transpiler/logic/analysis/SignedShiftAnalyzer.ts +124 -12
- package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +200 -0
- package/src/transpiler/logic/analysis/__tests__/InitializationAnalyzer.test.ts +386 -1
- package/src/transpiler/logic/analysis/__tests__/SignedShiftAnalyzer.test.ts +211 -0
- package/src/transpiler/logic/parser/grammar/CNext.interp +1 -1
- package/src/transpiler/logic/parser/grammar/CNextParser.ts +154 -86
- package/src/transpiler/logic/symbols/SymbolTable.ts +17 -2
- package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +6 -4
- package/src/transpiler/logic/symbols/cnext/collectors/VariableCollector.ts +15 -2
- package/src/transpiler/logic/symbols/cnext/utils/TypeUtils.ts +64 -50
- package/src/transpiler/output/codegen/CodeGenerator.ts +151 -94
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +165 -17
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +150 -34
- package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +2 -2
- package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +11 -0
- package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +26 -7
- package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +86 -0
- package/src/transpiler/output/codegen/generators/expressions/BinaryExprGenerator.ts +43 -24
- package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +48 -43
- package/src/transpiler/output/codegen/generators/expressions/ExpressionGenerator.ts +9 -2
- package/src/transpiler/output/codegen/generators/expressions/__tests__/CallExprGenerator.test.ts +44 -0
- package/src/transpiler/output/codegen/generators/expressions/__tests__/ExpressionGenerator.test.ts +82 -1
- package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +17 -3
- package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +17 -4
- package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +227 -32
- package/src/transpiler/output/codegen/helpers/SymbolLookupHelper.ts +0 -21
- package/src/transpiler/output/codegen/helpers/TypeGenerationHelper.ts +60 -39
- package/src/transpiler/output/codegen/helpers/TypeRegistrationEngine.ts +170 -36
- package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +37 -39
- package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +117 -0
- package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +94 -2
- package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +268 -1
- package/src/transpiler/output/codegen/helpers/__tests__/SymbolLookupHelper.test.ts +0 -64
- package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +101 -0
- package/src/transpiler/output/codegen/helpers/__tests__/VariableDeclHelper.test.ts +29 -5
- package/src/transpiler/output/codegen/types/ICallbackTypeInfo.ts +2 -1
- package/src/transpiler/output/codegen/types/IParameterInput.ts +7 -0
- package/src/transpiler/output/headers/BaseHeaderGenerator.ts +8 -0
- package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +75 -0
- package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +280 -0
- package/src/transpiler/output/headers/generators/IHeaderTypeInput.ts +13 -0
- package/src/transpiler/output/headers/generators/generateStructHeader.ts +48 -28
- package/src/transpiler/state/CodeGenState.ts +71 -6
- package/src/transpiler/state/__tests__/CodeGenState.test.ts +253 -11
- package/src/utils/LiteralUtils.ts +23 -0
- package/src/utils/__tests__/LiteralUtils.test.ts +101 -0
- package/src/utils/types/IParameterSymbol.ts +1 -0
|
@@ -26,6 +26,7 @@ import ArrayDimensionUtils from "./ArrayDimensionUtils";
|
|
|
26
26
|
import QualifiedNameGenerator from "../../utils/QualifiedNameGenerator";
|
|
27
27
|
import CodeGenState from "../../../../state/CodeGenState";
|
|
28
28
|
import ScopeUtils from "../../../../../utils/ScopeUtils";
|
|
29
|
+
import VariableModifierBuilder from "../../helpers/VariableModifierBuilder";
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* Generate initializer expression for a variable declaration.
|
|
@@ -38,10 +39,12 @@ function generateInitializer(
|
|
|
38
39
|
): string {
|
|
39
40
|
if (varDecl.expression()) {
|
|
40
41
|
// Issue #872: Set expectedType for MISRA 7.2 U suffix compliance
|
|
42
|
+
// Issue #992: withDeclarationInit suppresses compound literals at file scope (GCC 9-12 compat)
|
|
41
43
|
const typeName = orchestrator.generateType(varDecl.type());
|
|
42
|
-
return CodeGenState.withExpectedType(
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
return CodeGenState.withExpectedType(typeName, () =>
|
|
45
|
+
CodeGenState.withDeclarationInit(
|
|
46
|
+
() => ` = ${orchestrator.generateExpression(varDecl.expression()!)}`,
|
|
47
|
+
),
|
|
45
48
|
);
|
|
46
49
|
}
|
|
47
50
|
// ADR-015: Zero initialization for uninitialized scope variables
|
|
@@ -204,12 +207,23 @@ function generateRegularVariable(
|
|
|
204
207
|
orchestrator.markOpaqueScopeVariable(fullName);
|
|
205
208
|
}
|
|
206
209
|
|
|
207
|
-
// Issue #
|
|
210
|
+
// Issue #998: Use VariableModifierBuilder for consistent modifier handling
|
|
211
|
+
// This handles const, atomic, volatile, and validates mutual exclusion
|
|
212
|
+
// Scope variables are always at file scope (not in function body), no initializer for modifier purposes
|
|
213
|
+
const modifiers = VariableModifierBuilder.build(
|
|
214
|
+
varDecl,
|
|
215
|
+
false, // inFunctionBody - scope vars are file scope
|
|
216
|
+
false, // hasInitializer - doesn't affect volatile/atomic handling
|
|
217
|
+
false, // cppMode - doesn't affect volatile/atomic handling
|
|
218
|
+
);
|
|
219
|
+
// For scope variables: static for private, no modifier for public
|
|
220
|
+
// Then add volatile (from atomic or volatile keyword), then const
|
|
221
|
+
const staticPrefix = isPrivate ? "static " : "";
|
|
222
|
+
const volatilePrefix = modifiers.atomic || modifiers.volatile;
|
|
208
223
|
const constPrefix = isConst ? "const " : "";
|
|
209
|
-
const prefix = isPrivate ? "static " : "";
|
|
210
224
|
|
|
211
225
|
// Build declaration with all dimensions
|
|
212
|
-
let decl = `${
|
|
226
|
+
let decl = `${staticPrefix}${volatilePrefix}${constPrefix}${type} ${fullName}`;
|
|
213
227
|
decl += ArrayDimensionUtils.generateArrayTypeDimension(
|
|
214
228
|
arrayTypeCtx,
|
|
215
229
|
orchestrator,
|
|
@@ -225,7 +239,12 @@ function generateRegularVariable(
|
|
|
225
239
|
|
|
226
240
|
// Issue #948: Opaque types use NULL initialization instead of {0}
|
|
227
241
|
// Issue #958: External typedef struct types also use NULL initialization
|
|
228
|
-
|
|
242
|
+
// Issue #996: ...but only for SCALAR handles, which are single pointers. An
|
|
243
|
+
// *array* of opaque handles needs a brace initializer ({0}), not a scalar
|
|
244
|
+
// NULL. Route arrays through generateInitializer, which uses
|
|
245
|
+
// getZeroInitializer(type, isArray) — the single source of truth for
|
|
246
|
+
// zero-initialization (ADR-015).
|
|
247
|
+
if ((isOpaque || isExternalStruct) && !isArray) {
|
|
229
248
|
decl += " = NULL";
|
|
230
249
|
} else {
|
|
231
250
|
decl += generateInitializer(varDecl, isArray, orchestrator);
|
|
@@ -126,6 +126,8 @@ function createMockVariableDecl(options: {
|
|
|
126
126
|
name: string;
|
|
127
127
|
type: string;
|
|
128
128
|
isConst?: boolean;
|
|
129
|
+
isAtomic?: boolean;
|
|
130
|
+
isVolatile?: boolean;
|
|
129
131
|
initialValue?: string;
|
|
130
132
|
arrayDims?: string[];
|
|
131
133
|
hasStringType?: boolean;
|
|
@@ -142,6 +144,10 @@ function createMockVariableDecl(options: {
|
|
|
142
144
|
options.arrayTypeSize,
|
|
143
145
|
),
|
|
144
146
|
constModifier: () => createMockConstModifier(options.isConst ?? false),
|
|
147
|
+
// Issue #998: atomic and volatile modifiers for scope variables
|
|
148
|
+
// Uses VariableModifierBuilder for consistent handling
|
|
149
|
+
atomicModifier: () => (options.isAtomic ? ({} as unknown) : null),
|
|
150
|
+
volatileModifier: () => (options.isVolatile ? ({} as unknown) : null),
|
|
145
151
|
expression: () =>
|
|
146
152
|
options.initialValue ? createMockExpression(options.initialValue) : null,
|
|
147
153
|
arrayDimension: () =>
|
|
@@ -772,6 +778,86 @@ describe("ScopeGenerator", () => {
|
|
|
772
778
|
expect(result.code).toContain("static Point Canvas_point = 0;");
|
|
773
779
|
expect(result.code).not.toContain("Point*");
|
|
774
780
|
});
|
|
781
|
+
|
|
782
|
+
it("generates atomic variable with volatile modifier (Issue #998)", () => {
|
|
783
|
+
const varDecl = createMockVariableDecl({
|
|
784
|
+
name: "counter",
|
|
785
|
+
type: "u32",
|
|
786
|
+
isAtomic: true,
|
|
787
|
+
initialValue: "0",
|
|
788
|
+
});
|
|
789
|
+
const member = createMockScopeMember({ variableDecl: varDecl });
|
|
790
|
+
const ctx = createMockScopeContext("ISR", [member]);
|
|
791
|
+
const input = createMockInput();
|
|
792
|
+
const state = createMockState();
|
|
793
|
+
const orchestrator = createMockOrchestrator();
|
|
794
|
+
|
|
795
|
+
const result = generateScope(ctx, input, state, orchestrator);
|
|
796
|
+
|
|
797
|
+
expect(result.code).toContain(
|
|
798
|
+
"static volatile uint32_t ISR_counter = 0;",
|
|
799
|
+
);
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
it("generates public atomic variable with volatile but without static (Issue #998)", () => {
|
|
803
|
+
const varDecl = createMockVariableDecl({
|
|
804
|
+
name: "flag",
|
|
805
|
+
type: "bool",
|
|
806
|
+
isAtomic: true,
|
|
807
|
+
initialValue: "false",
|
|
808
|
+
});
|
|
809
|
+
const member = createMockScopeMember({
|
|
810
|
+
visibility: "public",
|
|
811
|
+
variableDecl: varDecl,
|
|
812
|
+
});
|
|
813
|
+
const ctx = createMockScopeContext("Shared", [member]);
|
|
814
|
+
const input = createMockInput();
|
|
815
|
+
const state = createMockState();
|
|
816
|
+
const orchestrator = createMockOrchestrator();
|
|
817
|
+
|
|
818
|
+
const result = generateScope(ctx, input, state, orchestrator);
|
|
819
|
+
|
|
820
|
+
expect(result.code).toContain("volatile bool Shared_flag = false;");
|
|
821
|
+
expect(result.code).not.toContain("static volatile bool Shared_flag");
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
it("generates volatile-keyword variable with volatile modifier (Issue #998)", () => {
|
|
825
|
+
const varDecl = createMockVariableDecl({
|
|
826
|
+
name: "reg",
|
|
827
|
+
type: "u8",
|
|
828
|
+
isVolatile: true,
|
|
829
|
+
initialValue: "0",
|
|
830
|
+
});
|
|
831
|
+
const member = createMockScopeMember({ variableDecl: varDecl });
|
|
832
|
+
const ctx = createMockScopeContext("HW", [member]);
|
|
833
|
+
const input = createMockInput();
|
|
834
|
+
const state = createMockState();
|
|
835
|
+
const orchestrator = createMockOrchestrator();
|
|
836
|
+
|
|
837
|
+
const result = generateScope(ctx, input, state, orchestrator);
|
|
838
|
+
|
|
839
|
+
expect(result.code).toContain("static volatile uint8_t HW_reg = 0;");
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
it("throws error when both atomic and volatile are specified (Issue #998)", () => {
|
|
843
|
+
const varDecl = createMockVariableDecl({
|
|
844
|
+
name: "bad",
|
|
845
|
+
type: "u8",
|
|
846
|
+
isAtomic: true,
|
|
847
|
+
isVolatile: true,
|
|
848
|
+
initialValue: "0",
|
|
849
|
+
startLine: 10,
|
|
850
|
+
});
|
|
851
|
+
const member = createMockScopeMember({ variableDecl: varDecl });
|
|
852
|
+
const ctx = createMockScopeContext("Test", [member]);
|
|
853
|
+
const input = createMockInput();
|
|
854
|
+
const state = createMockState();
|
|
855
|
+
const orchestrator = createMockOrchestrator();
|
|
856
|
+
|
|
857
|
+
expect(() => generateScope(ctx, input, state, orchestrator)).toThrow(
|
|
858
|
+
/Cannot use both 'atomic' and 'volatile' modifiers/,
|
|
859
|
+
);
|
|
860
|
+
});
|
|
775
861
|
});
|
|
776
862
|
|
|
777
863
|
// ========================================================================
|
|
@@ -18,6 +18,7 @@ import IGeneratorInput from "../IGeneratorInput";
|
|
|
18
18
|
import IGeneratorState from "../IGeneratorState";
|
|
19
19
|
import IOrchestrator from "../IOrchestrator";
|
|
20
20
|
import BinaryExprUtils from "./BinaryExprUtils";
|
|
21
|
+
import CodeGenState from "../../../../state/CodeGenState";
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Generator context passed to child generators.
|
|
@@ -116,6 +117,7 @@ const generateAndExpr = (
|
|
|
116
117
|
* ADR-001: = becomes == in C
|
|
117
118
|
* ADR-017: Enum type safety validation
|
|
118
119
|
* ADR-045: String comparison via strcmp()
|
|
120
|
+
* Issue #1032: Clear expectedType for comparison operands
|
|
119
121
|
*/
|
|
120
122
|
const generateEqualityExpr = (
|
|
121
123
|
node: Parser.EqualityExpressionContext,
|
|
@@ -151,18 +153,14 @@ const generateEqualityExpr = (
|
|
|
151
153
|
// Generate strcmp for string comparison - needs string.h
|
|
152
154
|
effects.push({ type: "include", header: "string" });
|
|
153
155
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
input,
|
|
163
|
-
state,
|
|
164
|
-
orchestrator,
|
|
165
|
-
);
|
|
156
|
+
// Issue #1032: Clear expectedType for equality comparisons.
|
|
157
|
+
// Use CodeGenState.withoutExpectedType() to clear the global state that
|
|
158
|
+
// generators read via getState(). The passed state is not used for
|
|
159
|
+
// expectedType lookup - generators read from CodeGenState directly.
|
|
160
|
+
const [leftResult, rightResult] = CodeGenState.withoutExpectedType(() => [
|
|
161
|
+
generateRelationalExpr(exprs[0], input, state, orchestrator),
|
|
162
|
+
generateRelationalExpr(exprs[1], input, state, orchestrator),
|
|
163
|
+
]);
|
|
166
164
|
effects.push(...leftResult.effects, ...rightResult.effects);
|
|
167
165
|
|
|
168
166
|
const fullText = node.getText();
|
|
@@ -183,18 +181,29 @@ const generateEqualityExpr = (
|
|
|
183
181
|
// Issue #152: Extract operators in order from parse tree children
|
|
184
182
|
// ADR-001: C-Next uses = for equality, transpile to ==
|
|
185
183
|
const operators = orchestrator.getOperatorsFromChildren(node);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
184
|
+
|
|
185
|
+
// Issue #1032: Clear expectedType for equality comparisons.
|
|
186
|
+
// The U suffix for MISRA 7.2 compliance applies to assignments, not comparisons.
|
|
187
|
+
// Use CodeGenState.withoutExpectedType() to clear the global state that
|
|
188
|
+
// generators read via getState(). The passed state is not used for
|
|
189
|
+
// expectedType lookup - generators read from CodeGenState directly.
|
|
190
|
+
return CodeGenState.withoutExpectedType(() =>
|
|
191
|
+
accumulateBinaryExprs(
|
|
192
|
+
exprs,
|
|
193
|
+
operators,
|
|
194
|
+
"=",
|
|
195
|
+
generateRelationalExpr,
|
|
196
|
+
{ input, state, orchestrator },
|
|
197
|
+
BinaryExprUtils.mapEqualityOperator,
|
|
198
|
+
),
|
|
193
199
|
);
|
|
194
200
|
};
|
|
195
201
|
|
|
196
202
|
/**
|
|
197
203
|
* Generate C code for a relational expression.
|
|
204
|
+
* Issue #1032: Clear expectedType for comparison operands - MISRA 7.2 suffix
|
|
205
|
+
* should not apply to comparisons, only to assignments. This prevents
|
|
206
|
+
* `i32 < 0` from becoming `signedIdx < 0U` which changes comparison semantics.
|
|
198
207
|
*/
|
|
199
208
|
const generateRelationalExpr = (
|
|
200
209
|
node: Parser.RelationalExpressionContext,
|
|
@@ -210,11 +219,21 @@ const generateRelationalExpr = (
|
|
|
210
219
|
|
|
211
220
|
// Issue #152: Extract operators in order from parse tree children
|
|
212
221
|
const operators = orchestrator.getOperatorsFromChildren(node);
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
222
|
+
|
|
223
|
+
// Issue #1032: Clear expectedType for relational comparisons.
|
|
224
|
+
// The U suffix for MISRA 7.2 compliance applies to assignments, not comparisons.
|
|
225
|
+
// Comparing `i32 < 0` should NOT generate `signedIdx < 0U` because that
|
|
226
|
+
// changes semantics due to C's integer promotion rules.
|
|
227
|
+
// Use CodeGenState.withoutExpectedType() to clear the global state that
|
|
228
|
+
// generators read via getState(). The passed state is not used for
|
|
229
|
+
// expectedType lookup - generators read from CodeGenState directly.
|
|
230
|
+
return CodeGenState.withoutExpectedType(() =>
|
|
231
|
+
accumulateBinaryExprs(exprs, operators, "<", generateBitwiseOrExpr, {
|
|
232
|
+
input,
|
|
233
|
+
state,
|
|
234
|
+
orchestrator,
|
|
235
|
+
}),
|
|
236
|
+
);
|
|
218
237
|
};
|
|
219
238
|
|
|
220
239
|
/**
|
|
@@ -166,7 +166,8 @@ const _generateCFunctionArg = (
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
// Issue #948: Check if argument is an opaque scope variable (already a pointer)
|
|
169
|
-
|
|
169
|
+
// Issue #996: ...including an element of an opaque-handle array (arr[i])
|
|
170
|
+
const isOpaqueScopeVar = CodeGenState.isOpaqueScopeVariableAccess(argCode);
|
|
170
171
|
|
|
171
172
|
// Add & if argument needs address-of to match parameter type.
|
|
172
173
|
// Issue #322: struct types passed to pointer params.
|
|
@@ -284,50 +285,54 @@ const generateFunctionCall = (
|
|
|
284
285
|
// Get function signature once for all arguments
|
|
285
286
|
const sig = input.functionSignatures.get(funcExpr);
|
|
286
287
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
);
|
|
296
|
-
const targetParam = resolved.param;
|
|
297
|
-
|
|
298
|
-
// C/C++ function: use pass-by-value semantics
|
|
299
|
-
if (!isCNextFunc) {
|
|
300
|
-
return _generateCFunctionArg(e, targetParam, input, orchestrator);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// C-Next function: check if target parameter should be passed by value
|
|
304
|
-
if (
|
|
305
|
-
_shouldPassByValue(
|
|
306
|
-
funcExpr,
|
|
288
|
+
// Issue #992: Clear inDeclarationInit for function call arguments — struct
|
|
289
|
+
// initializers inside function args need compound literals, not plain designated initializers.
|
|
290
|
+
const args = CodeGenState.withoutDeclarationInit(() =>
|
|
291
|
+
argExprs
|
|
292
|
+
.map((e, idx) => {
|
|
293
|
+
// Get parameter type info from local signature or cross-file SymbolTable
|
|
294
|
+
const resolved = CallExprUtils.resolveTargetParam(
|
|
295
|
+
sig,
|
|
307
296
|
idx,
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
orchestrator,
|
|
311
|
-
)
|
|
312
|
-
) {
|
|
313
|
-
// Issue #872: Set expectedType for MISRA 7.2 compliance, but suppress bare enum resolution
|
|
314
|
-
const argCode = CodeGenState.withExpectedType(
|
|
315
|
-
targetParam?.baseType,
|
|
316
|
-
() => orchestrator.generateExpression(e),
|
|
317
|
-
true, // suppressEnumResolution
|
|
318
|
-
);
|
|
319
|
-
return wrapWithCppEnumCast(
|
|
320
|
-
argCode,
|
|
321
|
-
e,
|
|
322
|
-
targetParam?.baseType,
|
|
323
|
-
orchestrator,
|
|
297
|
+
funcExpr,
|
|
298
|
+
input.symbolTable,
|
|
324
299
|
);
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
300
|
+
const targetParam = resolved.param;
|
|
301
|
+
|
|
302
|
+
// C/C++ function: use pass-by-value semantics
|
|
303
|
+
if (!isCNextFunc) {
|
|
304
|
+
return _generateCFunctionArg(e, targetParam, input, orchestrator);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// C-Next function: check if target parameter should be passed by value
|
|
308
|
+
if (
|
|
309
|
+
_shouldPassByValue(
|
|
310
|
+
funcExpr,
|
|
311
|
+
idx,
|
|
312
|
+
targetParam,
|
|
313
|
+
resolved.isCrossFile,
|
|
314
|
+
orchestrator,
|
|
315
|
+
)
|
|
316
|
+
) {
|
|
317
|
+
// Issue #872: Set expectedType for MISRA 7.2 compliance, but suppress bare enum resolution
|
|
318
|
+
const argCode = CodeGenState.withExpectedType(
|
|
319
|
+
targetParam?.baseType,
|
|
320
|
+
() => orchestrator.generateExpression(e),
|
|
321
|
+
true, // suppressEnumResolution
|
|
322
|
+
);
|
|
323
|
+
return wrapWithCppEnumCast(
|
|
324
|
+
argCode,
|
|
325
|
+
e,
|
|
326
|
+
targetParam?.baseType,
|
|
327
|
+
orchestrator,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Target parameter is pass-by-reference: use & logic
|
|
332
|
+
return orchestrator.generateFunctionArg(e, targetParam?.baseType);
|
|
333
|
+
})
|
|
334
|
+
.join(", "),
|
|
335
|
+
);
|
|
331
336
|
|
|
332
337
|
return { code: `${funcExpr}(${args})`, effects };
|
|
333
338
|
};
|
|
@@ -15,6 +15,7 @@ import TGeneratorEffect from "../TGeneratorEffect";
|
|
|
15
15
|
import IGeneratorInput from "../IGeneratorInput";
|
|
16
16
|
import IGeneratorState from "../IGeneratorState";
|
|
17
17
|
import IOrchestrator from "../IOrchestrator";
|
|
18
|
+
import CodeGenState from "../../../../state/CodeGenState";
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Generate C code for an expression (entry point).
|
|
@@ -74,9 +75,15 @@ const generateTernaryExpr = (
|
|
|
74
75
|
orchestrator.validateTernaryConditionNoFunctionCall(condition);
|
|
75
76
|
|
|
76
77
|
// Generate C output - parentheses already present from grammar
|
|
78
|
+
// Issue #992: Clear inDeclarationInit in ternary arms — struct initializers
|
|
79
|
+
// inside ternary branches need compound literals, not plain designated initializers.
|
|
77
80
|
const condCode = orchestrator.generateOrExpr(condition);
|
|
78
|
-
const trueCode =
|
|
79
|
-
|
|
81
|
+
const trueCode = CodeGenState.withoutDeclarationInit(() =>
|
|
82
|
+
orchestrator.generateOrExpr(trueExpr),
|
|
83
|
+
);
|
|
84
|
+
const falseCode = CodeGenState.withoutDeclarationInit(() =>
|
|
85
|
+
orchestrator.generateOrExpr(falseExpr),
|
|
86
|
+
);
|
|
80
87
|
|
|
81
88
|
return { code: `(${condCode}) ? ${trueCode} : ${falseCode}`, effects };
|
|
82
89
|
};
|
package/src/transpiler/output/codegen/generators/expressions/__tests__/CallExprGenerator.test.ts
CHANGED
|
@@ -1477,4 +1477,48 @@ describe("CallExprGenerator", () => {
|
|
|
1477
1477
|
expect(result.code).toBe("use_handle(my_handle)");
|
|
1478
1478
|
});
|
|
1479
1479
|
});
|
|
1480
|
+
|
|
1481
|
+
describe("inDeclarationInit clearing (Issue #992)", () => {
|
|
1482
|
+
it("clears inDeclarationInit during function argument generation", () => {
|
|
1483
|
+
CodeGenState.inDeclarationInit = true;
|
|
1484
|
+
|
|
1485
|
+
const argExprs = [createMockExpressionContext("myArg")];
|
|
1486
|
+
const argCtx = createMockArgListContext(argExprs);
|
|
1487
|
+
const input = createMockInput();
|
|
1488
|
+
const state = createMockState();
|
|
1489
|
+
|
|
1490
|
+
let flagDuringArg = true;
|
|
1491
|
+
const orchestrator = createMockOrchestrator({
|
|
1492
|
+
isCNextFunction: vi.fn(() => false),
|
|
1493
|
+
generateExpression: vi.fn((ctx: Parser.ExpressionContext) => {
|
|
1494
|
+
flagDuringArg = CodeGenState.inDeclarationInit;
|
|
1495
|
+
return ctx.getText();
|
|
1496
|
+
}),
|
|
1497
|
+
});
|
|
1498
|
+
|
|
1499
|
+
generateFunctionCall("myFunc", argCtx, input, state, orchestrator);
|
|
1500
|
+
|
|
1501
|
+
expect(flagDuringArg).toBe(false);
|
|
1502
|
+
expect(CodeGenState.inDeclarationInit).toBe(true);
|
|
1503
|
+
});
|
|
1504
|
+
|
|
1505
|
+
it("restores inDeclarationInit after argument generation", () => {
|
|
1506
|
+
CodeGenState.inDeclarationInit = true;
|
|
1507
|
+
|
|
1508
|
+
const argExprs = [
|
|
1509
|
+
createMockExpressionContext("a"),
|
|
1510
|
+
createMockExpressionContext("b"),
|
|
1511
|
+
];
|
|
1512
|
+
const argCtx = createMockArgListContext(argExprs);
|
|
1513
|
+
const input = createMockInput();
|
|
1514
|
+
const state = createMockState();
|
|
1515
|
+
const orchestrator = createMockOrchestrator({
|
|
1516
|
+
isCNextFunction: vi.fn(() => false),
|
|
1517
|
+
});
|
|
1518
|
+
|
|
1519
|
+
generateFunctionCall("myFunc", argCtx, input, state, orchestrator);
|
|
1520
|
+
|
|
1521
|
+
expect(CodeGenState.inDeclarationInit).toBe(true);
|
|
1522
|
+
});
|
|
1523
|
+
});
|
|
1480
1524
|
});
|
package/src/transpiler/output/codegen/generators/expressions/__tests__/ExpressionGenerator.test.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
2
|
import expressionGenerators from "../ExpressionGenerator";
|
|
3
3
|
import IGeneratorInput from "../../IGeneratorInput";
|
|
4
4
|
import IGeneratorState from "../../IGeneratorState";
|
|
5
5
|
import IOrchestrator from "../../IOrchestrator";
|
|
6
6
|
import * as Parser from "../../../../../logic/parser/grammar/CNextParser";
|
|
7
|
+
import CodeGenState from "../../../../../state/CodeGenState";
|
|
7
8
|
|
|
8
9
|
// ========================================================================
|
|
9
10
|
// Test Helpers
|
|
@@ -455,6 +456,86 @@ describe("ExpressionGenerator", () => {
|
|
|
455
456
|
});
|
|
456
457
|
});
|
|
457
458
|
|
|
459
|
+
describe("inDeclarationInit clearing (Issue #992)", () => {
|
|
460
|
+
beforeEach(() => {
|
|
461
|
+
CodeGenState.reset();
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it("clears inDeclarationInit in ternary true and false arms", () => {
|
|
465
|
+
CodeGenState.inDeclarationInit = true;
|
|
466
|
+
|
|
467
|
+
const condition = createMockOrExpr("x > 0");
|
|
468
|
+
const trueExpr = createMockOrExpr("a");
|
|
469
|
+
const falseExpr = createMockOrExpr("b");
|
|
470
|
+
const ctx = createMockTernaryContext([condition, trueExpr, falseExpr]);
|
|
471
|
+
|
|
472
|
+
const input = createMockInput();
|
|
473
|
+
const state = createMockState();
|
|
474
|
+
|
|
475
|
+
const flagDuringTrue: boolean[] = [];
|
|
476
|
+
const flagDuringFalse: boolean[] = [];
|
|
477
|
+
|
|
478
|
+
const orchestrator = createMockOrchestrator();
|
|
479
|
+
let callCount = 0;
|
|
480
|
+
(
|
|
481
|
+
orchestrator.generateOrExpr as ReturnType<typeof vi.fn>
|
|
482
|
+
).mockImplementation((ctx: Parser.OrExpressionContext) => {
|
|
483
|
+
callCount++;
|
|
484
|
+
if (callCount === 2) {
|
|
485
|
+
flagDuringTrue.push(CodeGenState.inDeclarationInit);
|
|
486
|
+
} else if (callCount === 3) {
|
|
487
|
+
flagDuringFalse.push(CodeGenState.inDeclarationInit);
|
|
488
|
+
}
|
|
489
|
+
return ctx.getText();
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
expressionGenerators.generateTernaryExpr(
|
|
493
|
+
ctx,
|
|
494
|
+
input,
|
|
495
|
+
state,
|
|
496
|
+
orchestrator,
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
expect(flagDuringTrue).toEqual([false]);
|
|
500
|
+
expect(flagDuringFalse).toEqual([false]);
|
|
501
|
+
expect(CodeGenState.inDeclarationInit).toBe(true);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it("does not affect condition generation", () => {
|
|
505
|
+
CodeGenState.inDeclarationInit = true;
|
|
506
|
+
|
|
507
|
+
const condition = createMockOrExpr("x > 0");
|
|
508
|
+
const trueExpr = createMockOrExpr("a");
|
|
509
|
+
const falseExpr = createMockOrExpr("b");
|
|
510
|
+
const ctx = createMockTernaryContext([condition, trueExpr, falseExpr]);
|
|
511
|
+
|
|
512
|
+
const input = createMockInput();
|
|
513
|
+
const state = createMockState();
|
|
514
|
+
|
|
515
|
+
let flagDuringCondition = false;
|
|
516
|
+
const orchestrator = createMockOrchestrator();
|
|
517
|
+
let callCount = 0;
|
|
518
|
+
(
|
|
519
|
+
orchestrator.generateOrExpr as ReturnType<typeof vi.fn>
|
|
520
|
+
).mockImplementation((ctx: Parser.OrExpressionContext) => {
|
|
521
|
+
callCount++;
|
|
522
|
+
if (callCount === 1) {
|
|
523
|
+
flagDuringCondition = CodeGenState.inDeclarationInit;
|
|
524
|
+
}
|
|
525
|
+
return ctx.getText();
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
expressionGenerators.generateTernaryExpr(
|
|
529
|
+
ctx,
|
|
530
|
+
input,
|
|
531
|
+
state,
|
|
532
|
+
orchestrator,
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
expect(flagDuringCondition).toBe(true);
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
|
|
458
539
|
describe("effects", () => {
|
|
459
540
|
it("returns empty effects array for non-ternary", () => {
|
|
460
541
|
const orExpr = createMockOrExpr("value");
|
|
@@ -60,6 +60,13 @@ interface IFromASTDeps {
|
|
|
60
60
|
* When the C typedef has `const T*`, this preserves const on the generated param.
|
|
61
61
|
*/
|
|
62
62
|
forceConst?: boolean;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Issue #995: Check if a type is an opaque handle (incomplete struct typedef).
|
|
66
|
+
* Opaque handles should not get auto-const because they must be passed to
|
|
67
|
+
* C APIs that expect non-const pointers.
|
|
68
|
+
*/
|
|
69
|
+
isOpaqueType?: (typeName: string) => boolean;
|
|
63
70
|
}
|
|
64
71
|
|
|
65
72
|
/**
|
|
@@ -142,6 +149,8 @@ class ParameterInputAdapter {
|
|
|
142
149
|
const isKnownPrimitive = !!deps.typeMap[typeName];
|
|
143
150
|
// Issue #958: C-header typedef struct types need pointer semantics
|
|
144
151
|
const isTypedefStruct = deps.isTypedefStructType(typeName);
|
|
152
|
+
// Issue #995: Detect opaque handles — rule applied in ParameterSignatureBuilder
|
|
153
|
+
const isOpaque = deps.isOpaqueType?.(typeName) ?? false;
|
|
145
154
|
// Issue #895: Don't add auto-const for callback-compatible functions
|
|
146
155
|
// because it would change the signature and break typedef compatibility
|
|
147
156
|
const isAutoConst =
|
|
@@ -171,6 +180,8 @@ class ParameterInputAdapter {
|
|
|
171
180
|
deps.forcePassByReference || isTypedefStruct || undefined,
|
|
172
181
|
// Issue #895: Preserve const from callback typedef signature
|
|
173
182
|
forceConst: deps.forceConst,
|
|
183
|
+
// Issue #995: Pass through opaque handle detection — rule applied in builder
|
|
184
|
+
isOpaqueHandle: isOpaque || undefined,
|
|
174
185
|
};
|
|
175
186
|
}
|
|
176
187
|
|
|
@@ -236,6 +247,8 @@ class ParameterInputAdapter {
|
|
|
236
247
|
isPassByReference: isCallbackPointer ? true : !deps.isPassByValue,
|
|
237
248
|
forcePointerSyntax: isCallbackPointer || undefined,
|
|
238
249
|
forceConst: param.isCallbackConst || undefined,
|
|
250
|
+
// Issue #995: Pass through opaque handle detection — rule applied in builder
|
|
251
|
+
isOpaqueHandle: param.isOpaqueHandle || undefined,
|
|
239
252
|
};
|
|
240
253
|
}
|
|
241
254
|
|
|
@@ -296,14 +309,15 @@ class ParameterInputAdapter {
|
|
|
296
309
|
}
|
|
297
310
|
}
|
|
298
311
|
|
|
299
|
-
|
|
300
|
-
|
|
312
|
+
// ADR-006: Arrays are pass-by-reference and mutable by default.
|
|
313
|
+
// Never apply auto-const to arrays - only explicit const from source code.
|
|
314
|
+
// Auto-const would break compatibility with C APIs expecting mutable pointers.
|
|
301
315
|
return {
|
|
302
316
|
name,
|
|
303
317
|
baseType: typeName,
|
|
304
318
|
mappedType,
|
|
305
319
|
isConst,
|
|
306
|
-
isAutoConst,
|
|
320
|
+
isAutoConst: false,
|
|
307
321
|
isArray: true,
|
|
308
322
|
arrayDimensions: dims,
|
|
309
323
|
isCallback: false,
|
|
@@ -47,6 +47,11 @@ class ParameterSignatureBuilder {
|
|
|
47
47
|
return this._buildStringParam(param);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
// Issue #995: Opaque handles are always pass-by-reference with pointer syntax
|
|
51
|
+
if (param.isOpaqueHandle) {
|
|
52
|
+
return this._buildRefParam(param, refSuffix);
|
|
53
|
+
}
|
|
54
|
+
|
|
50
55
|
// Known struct or known primitive: pass by reference
|
|
51
56
|
if (param.isPassByReference) {
|
|
52
57
|
return this._buildRefParam(param, refSuffix);
|
|
@@ -103,18 +108,21 @@ class ParameterSignatureBuilder {
|
|
|
103
108
|
/**
|
|
104
109
|
* Build pass-by-reference parameter signature.
|
|
105
110
|
* C mode: const Point* p
|
|
106
|
-
* C++ mode: const Point& p (unless forcePointerSyntax)
|
|
111
|
+
* C++ mode: const Point& p (unless forcePointerSyntax or isOpaqueHandle)
|
|
107
112
|
*
|
|
108
113
|
* Issue #895: When forcePointerSyntax is set, always use pointer syntax
|
|
109
114
|
* because C callback typedefs expect pointers, not C++ references.
|
|
115
|
+
* Issue #995: Opaque handles must use pointer syntax because C APIs
|
|
116
|
+
* expect pointers to incomplete struct types (e.g., widget_t*).
|
|
110
117
|
*/
|
|
111
118
|
private static _buildRefParam(
|
|
112
119
|
param: IParameterInput,
|
|
113
120
|
refSuffix: string,
|
|
114
121
|
): string {
|
|
115
122
|
const constPrefix = this._getConstPrefix(param);
|
|
116
|
-
// Issue #895: Override refSuffix for callback-compatible
|
|
117
|
-
const actualSuffix =
|
|
123
|
+
// Issue #895/#995: Override refSuffix for callback-compatible or opaque handle params
|
|
124
|
+
const actualSuffix =
|
|
125
|
+
param.forcePointerSyntax || param.isOpaqueHandle ? "*" : refSuffix;
|
|
118
126
|
return `${constPrefix}${param.mappedType}${actualSuffix} ${param.name}`;
|
|
119
127
|
}
|
|
120
128
|
|
|
@@ -130,10 +138,15 @@ class ParameterSignatureBuilder {
|
|
|
130
138
|
* Get const prefix combining explicit const, auto-const, and forced const.
|
|
131
139
|
* Priority: forceConst > isConst > isAutoConst
|
|
132
140
|
* Issue #895: forceConst preserves const from callback typedef signature.
|
|
141
|
+
* Issue #995: Opaque handles suppress auto-const (they must be passed to
|
|
142
|
+
* C APIs that expect non-const pointers).
|
|
133
143
|
*/
|
|
134
144
|
private static _getConstPrefix(param: IParameterInput): string {
|
|
145
|
+
// Issue #995: Opaque handles never get auto-const — they're passed to C APIs
|
|
146
|
+
// expecting mutable pointers (e.g., LVGL's lv_obj_t)
|
|
147
|
+
const effectiveAutoConst = param.isOpaqueHandle ? false : param.isAutoConst;
|
|
135
148
|
// Any source of const results in "const " prefix
|
|
136
|
-
if (param.forceConst || param.isConst ||
|
|
149
|
+
if (param.forceConst || param.isConst || effectiveAutoConst) {
|
|
137
150
|
return "const ";
|
|
138
151
|
}
|
|
139
152
|
return "";
|