c-next 0.2.3 → 0.2.5
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 +59 -57
- package/dist/index.js +859 -198
- package/dist/index.js.map +4 -4
- package/package.json +3 -1
- package/src/cli/Runner.ts +1 -1
- package/src/cli/__tests__/Runner.test.ts +8 -8
- package/src/cli/serve/ServeCommand.ts +29 -9
- package/src/transpiler/Transpiler.ts +105 -200
- package/src/transpiler/__tests__/DualCodePaths.test.ts +117 -68
- package/src/transpiler/__tests__/Transpiler.coverage.test.ts +87 -51
- package/src/transpiler/__tests__/Transpiler.test.ts +150 -48
- package/src/transpiler/__tests__/determineProjectRoot.test.ts +2 -2
- package/src/transpiler/data/IncludeResolver.ts +11 -3
- package/src/transpiler/data/__tests__/IncludeResolver.test.ts +2 -2
- package/src/transpiler/logic/analysis/ArrayIndexTypeAnalyzer.ts +346 -0
- package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +281 -14
- package/src/transpiler/logic/analysis/__tests__/ArrayIndexTypeAnalyzer.test.ts +545 -0
- package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +375 -0
- package/src/transpiler/logic/analysis/runAnalyzers.ts +9 -2
- package/src/transpiler/logic/analysis/types/IArrayIndexTypeError.ts +15 -0
- package/src/transpiler/logic/symbols/TransitiveEnumCollector.ts +1 -1
- package/src/transpiler/logic/symbols/c/__tests__/CResolver.integration.test.ts +18 -0
- package/src/transpiler/logic/symbols/c/index.ts +50 -1
- package/src/transpiler/output/codegen/CodeGenerator.ts +69 -15
- package/src/transpiler/output/codegen/TypeResolver.ts +1 -1
- package/src/transpiler/output/codegen/TypeValidator.ts +10 -7
- package/src/transpiler/output/codegen/__tests__/ExpressionWalker.test.ts +9 -3
- package/src/transpiler/output/codegen/__tests__/RequireInclude.test.ts +86 -23
- package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +3 -1
- package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +43 -29
- package/src/transpiler/output/codegen/generators/IOrchestrator.ts +5 -2
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +15 -3
- package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +1 -1
- package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +12 -3
- package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +4 -4
- package/src/transpiler/output/codegen/helpers/FunctionContextManager.ts +64 -3
- package/src/transpiler/output/codegen/helpers/MemberSeparatorResolver.ts +28 -6
- package/src/transpiler/output/codegen/helpers/ParameterDereferenceResolver.ts +12 -0
- package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +30 -2
- package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +15 -7
- package/src/transpiler/output/codegen/helpers/StringOperationsHelper.ts +1 -1
- package/src/transpiler/output/codegen/helpers/TypedefParamParser.ts +220 -0
- package/src/transpiler/output/codegen/helpers/__tests__/FunctionContextManager.test.ts +5 -5
- package/src/transpiler/output/codegen/helpers/__tests__/MemberSeparatorResolver.test.ts +48 -36
- package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +37 -0
- package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +63 -0
- package/src/transpiler/output/codegen/helpers/__tests__/TypedefParamParser.test.ts +209 -0
- package/src/transpiler/output/codegen/resolution/EnumTypeResolver.ts +1 -1
- package/src/transpiler/output/codegen/resolution/SizeofResolver.ts +1 -1
- package/src/transpiler/output/codegen/types/IParameterInput.ts +13 -0
- package/src/transpiler/output/codegen/types/ISeparatorContext.ts +7 -0
- package/src/transpiler/output/codegen/types/TParameterInfo.ts +12 -0
- package/src/transpiler/output/codegen/utils/CodegenParserUtils.ts +1 -1
- package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +5 -2
- package/src/transpiler/state/CodeGenState.ts +25 -0
- package/src/transpiler/types/IPipelineFile.ts +2 -2
- package/src/transpiler/types/IPipelineInput.ts +1 -1
- package/src/transpiler/types/TTranspileInput.ts +18 -0
- package/src/{transpiler/output/codegen/utils → utils}/ExpressionUnwrapper.ts +1 -1
- package/src/{transpiler/output/codegen/utils → utils}/__tests__/ExpressionUnwrapper.test.ts +2 -2
- package/src/utils/constants/TypeConstants.ts +22 -0
|
@@ -110,6 +110,8 @@ const initializeTrackingState = (
|
|
|
110
110
|
interface IPostfixContext {
|
|
111
111
|
rootIdentifier: string | undefined;
|
|
112
112
|
isStructParam: boolean;
|
|
113
|
+
/** Issue #895: Force pointer semantics for callback-compatible params */
|
|
114
|
+
forcePointerSemantics: boolean;
|
|
113
115
|
input: IGeneratorInput;
|
|
114
116
|
state: IGeneratorState;
|
|
115
117
|
orchestrator: IOrchestrator;
|
|
@@ -149,6 +151,8 @@ const generatePostfixExpression = (
|
|
|
149
151
|
? state.currentParameters.get(rootIdentifier)
|
|
150
152
|
: null;
|
|
151
153
|
const isStructParam = paramInfo?.isStruct ?? false;
|
|
154
|
+
// Issue #895: Callback-compatible params need pointer semantics even in C++ mode
|
|
155
|
+
const forcePointerSemantics = paramInfo?.forcePointerSemantics ?? false;
|
|
152
156
|
|
|
153
157
|
// Issue #579: Check if we have subscript access on a non-array parameter
|
|
154
158
|
const hasSubscriptOps = ops.some((op) => op.expression().length > 0);
|
|
@@ -178,6 +182,7 @@ const generatePostfixExpression = (
|
|
|
178
182
|
const postfixCtx: IPostfixContext = {
|
|
179
183
|
rootIdentifier,
|
|
180
184
|
isStructParam,
|
|
185
|
+
forcePointerSemantics,
|
|
181
186
|
input,
|
|
182
187
|
state,
|
|
183
188
|
orchestrator,
|
|
@@ -303,6 +308,7 @@ const handleMemberOp = (
|
|
|
303
308
|
memberName,
|
|
304
309
|
rootIdentifier: ctx.rootIdentifier,
|
|
305
310
|
isStructParam: ctx.isStructParam,
|
|
311
|
+
forcePointerSemantics: ctx.forcePointerSemantics,
|
|
306
312
|
isGlobalAccess: tracking.isGlobalAccess,
|
|
307
313
|
isCppAccessChain: tracking.isCppAccessChain,
|
|
308
314
|
currentStructType: tracking.currentStructType,
|
|
@@ -1409,6 +1415,8 @@ interface IMemberAccessContext {
|
|
|
1409
1415
|
memberName: string;
|
|
1410
1416
|
rootIdentifier: string | undefined;
|
|
1411
1417
|
isStructParam: boolean;
|
|
1418
|
+
/** Issue #895: Force pointer semantics for callback-compatible params */
|
|
1419
|
+
forcePointerSemantics: boolean;
|
|
1412
1420
|
isGlobalAccess: boolean;
|
|
1413
1421
|
isCppAccessChain: boolean;
|
|
1414
1422
|
currentStructType: string | undefined;
|
|
@@ -1657,9 +1665,13 @@ const tryStructParamAccess = (
|
|
|
1657
1665
|
return null;
|
|
1658
1666
|
}
|
|
1659
1667
|
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1668
|
+
// Issue #895: Force pointer semantics for callback-compatible params
|
|
1669
|
+
// even in C++ mode (use -> instead of .)
|
|
1670
|
+
const structParamSep = ctx.forcePointerSemantics
|
|
1671
|
+
? "->"
|
|
1672
|
+
: memberAccessChain.getStructParamSeparator({
|
|
1673
|
+
cppMode: orchestrator.isCppMode(),
|
|
1674
|
+
});
|
|
1663
1675
|
|
|
1664
1676
|
const output = initializeMemberOutput(ctx);
|
|
1665
1677
|
output.result = `${ctx.result}${structParamSep}${ctx.memberName}`;
|
|
@@ -200,7 +200,7 @@ function createMockOrchestrator(overrides?: {
|
|
|
200
200
|
indent: vi.fn((text) => text),
|
|
201
201
|
validateNoEarlyExits: vi.fn(),
|
|
202
202
|
validateSwitchStatement: vi.fn(),
|
|
203
|
-
|
|
203
|
+
validateConditionIsBoolean: vi.fn(),
|
|
204
204
|
validateConditionNoFunctionCall: vi.fn(),
|
|
205
205
|
validateTernaryConditionNoFunctionCall: vi.fn(),
|
|
206
206
|
generateAssignmentTarget: vi.fn(),
|
|
@@ -118,6 +118,9 @@ const generateIf = (
|
|
|
118
118
|
// Issue #254: Validate no function calls in condition (E0702)
|
|
119
119
|
orchestrator.validateConditionNoFunctionCall(node.expression(), "if");
|
|
120
120
|
|
|
121
|
+
// Issue #884: Validate condition is a boolean expression (E0701)
|
|
122
|
+
orchestrator.validateConditionIsBoolean(node.expression(), "if");
|
|
123
|
+
|
|
121
124
|
// Generate with cache enabled
|
|
122
125
|
const condition = orchestrator.generateExpression(node.expression());
|
|
123
126
|
|
|
@@ -161,6 +164,9 @@ const generateWhile = (
|
|
|
161
164
|
// Issue #254: Validate no function calls in condition (E0702)
|
|
162
165
|
orchestrator.validateConditionNoFunctionCall(node.expression(), "while");
|
|
163
166
|
|
|
167
|
+
// Issue #884: Validate condition is a boolean expression (E0701)
|
|
168
|
+
orchestrator.validateConditionIsBoolean(node.expression(), "while");
|
|
169
|
+
|
|
164
170
|
const condition = orchestrator.generateExpression(node.expression());
|
|
165
171
|
|
|
166
172
|
// Issue #250: Flush any temp vars from condition BEFORE generating body
|
|
@@ -189,12 +195,12 @@ const generateDoWhile = (
|
|
|
189
195
|
): IGeneratorOutput => {
|
|
190
196
|
const effects: TGeneratorEffect[] = [];
|
|
191
197
|
|
|
192
|
-
// Validate the condition is a boolean expression (E0701)
|
|
193
|
-
orchestrator.validateDoWhileCondition(node.expression());
|
|
194
|
-
|
|
195
198
|
// Issue #254: Validate no function calls in condition (E0702)
|
|
196
199
|
orchestrator.validateConditionNoFunctionCall(node.expression(), "do-while");
|
|
197
200
|
|
|
201
|
+
// Issue #884: Validate condition is a boolean expression (E0701)
|
|
202
|
+
orchestrator.validateConditionIsBoolean(node.expression(), "do-while");
|
|
203
|
+
|
|
198
204
|
const body = orchestrator.generateBlock(node.block());
|
|
199
205
|
const condition = orchestrator.generateExpression(node.expression());
|
|
200
206
|
|
|
@@ -311,6 +317,9 @@ const generateFor = (
|
|
|
311
317
|
if (node.expression()) {
|
|
312
318
|
// Issue #254: Validate no function calls in condition (E0702)
|
|
313
319
|
orchestrator.validateConditionNoFunctionCall(node.expression()!, "for");
|
|
320
|
+
|
|
321
|
+
// Issue #884: Validate condition is a boolean expression (E0701)
|
|
322
|
+
orchestrator.validateConditionIsBoolean(node.expression()!, "for");
|
|
314
323
|
condition = orchestrator.generateExpression(node.expression()!);
|
|
315
324
|
}
|
|
316
325
|
|
package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts
CHANGED
|
@@ -364,7 +364,7 @@ function createMockOrchestrator(options?: {
|
|
|
364
364
|
registerLocalVariable: vi.fn(),
|
|
365
365
|
flushPendingTempDeclarations: vi.fn(() => options?.tempDeclarations ?? ""),
|
|
366
366
|
validateConditionNoFunctionCall: vi.fn(),
|
|
367
|
-
|
|
367
|
+
validateConditionIsBoolean: vi.fn(),
|
|
368
368
|
countStringLengthAccesses: vi.fn(() => new Map()),
|
|
369
369
|
countBlockLengthAccesses: vi.fn(),
|
|
370
370
|
setupLengthCache: vi.fn(() => options?.lengthCacheDecls ?? ""),
|
|
@@ -690,15 +690,15 @@ describe("ControlFlowGenerator", () => {
|
|
|
690
690
|
const ctx = createMockDoWhileStatement({ expr });
|
|
691
691
|
const input = createMockInput();
|
|
692
692
|
const state = createMockState();
|
|
693
|
-
const
|
|
693
|
+
const validateConditionIsBoolean = vi.fn();
|
|
694
694
|
const orchestrator = {
|
|
695
695
|
...createMockOrchestrator(),
|
|
696
|
-
|
|
696
|
+
validateConditionIsBoolean,
|
|
697
697
|
} as unknown as IOrchestrator;
|
|
698
698
|
|
|
699
699
|
generateDoWhile(ctx, input, state, orchestrator);
|
|
700
700
|
|
|
701
|
-
expect(
|
|
701
|
+
expect(validateConditionIsBoolean).toHaveBeenCalledWith(expr, "do-while");
|
|
702
702
|
});
|
|
703
703
|
|
|
704
704
|
it("validates no function calls in condition (Issue #254)", () => {
|
|
@@ -15,6 +15,8 @@ import CodeGenState from "../../../state/CodeGenState.js";
|
|
|
15
15
|
import TYPE_WIDTH from "../types/TYPE_WIDTH.js";
|
|
16
16
|
import ArrayDimensionParser from "./ArrayDimensionParser.js";
|
|
17
17
|
import IFunctionContextCallbacks from "../types/IFunctionContextCallbacks.js";
|
|
18
|
+
// Issue #895: Parse typedef signatures to determine pointer vs value params
|
|
19
|
+
import TypedefParamParser from "./TypedefParamParser.js";
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* Result from resolving parameter type information.
|
|
@@ -124,8 +126,9 @@ class FunctionContextManager {
|
|
|
124
126
|
CodeGenState.currentParameters.clear();
|
|
125
127
|
if (!params) return;
|
|
126
128
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
+
const paramList = params.parameter();
|
|
130
|
+
for (let i = 0; i < paramList.length; i++) {
|
|
131
|
+
FunctionContextManager.processParameter(paramList[i], callbacks, i);
|
|
129
132
|
}
|
|
130
133
|
}
|
|
131
134
|
|
|
@@ -135,6 +138,7 @@ class FunctionContextManager {
|
|
|
135
138
|
static processParameter(
|
|
136
139
|
param: Parser.ParameterContext,
|
|
137
140
|
callbacks: IFunctionContextCallbacks,
|
|
141
|
+
paramIndex: number,
|
|
138
142
|
): void {
|
|
139
143
|
const name = param.IDENTIFIER().getText();
|
|
140
144
|
// Check both C-Next style (u8[8] param) and legacy style (u8 param[8])
|
|
@@ -149,15 +153,38 @@ class FunctionContextManager {
|
|
|
149
153
|
callbacks,
|
|
150
154
|
);
|
|
151
155
|
|
|
156
|
+
// Issue #895: For callback-compatible functions, check the typedef signature
|
|
157
|
+
// to determine if the param should be a pointer or value
|
|
158
|
+
const callbackTypedefInfo =
|
|
159
|
+
FunctionContextManager.getCallbackTypedefParamInfo(paramIndex);
|
|
160
|
+
const isCallbackPointerParam =
|
|
161
|
+
callbackTypedefInfo?.shouldBePointer ?? false;
|
|
162
|
+
|
|
163
|
+
// Determine isStruct: for callback-compatible params, both typedef AND type info matter
|
|
164
|
+
// - If typedef says pointer AND it's actually a struct, use -> access (isStruct=true)
|
|
165
|
+
// - If typedef says pointer BUT it's a primitive (like u8), don't treat as struct
|
|
166
|
+
// (primitives use forcePointerSemantics for dereference instead)
|
|
167
|
+
const isStruct = callbackTypedefInfo
|
|
168
|
+
? isCallbackPointerParam && typeInfo.isStruct
|
|
169
|
+
: typeInfo.isStruct;
|
|
170
|
+
|
|
171
|
+
// Issue #895: Primitive types that become pointers need dereferencing when used as values
|
|
172
|
+
// e.g., "u8 buf" becoming "uint8_t* buf" requires "*buf" when accessing the value
|
|
173
|
+
const isCallbackPointerPrimitive =
|
|
174
|
+
isCallbackPointerParam && !typeInfo.isStruct && !isArray;
|
|
175
|
+
|
|
152
176
|
// Register in currentParameters
|
|
153
177
|
const paramInfo = {
|
|
154
178
|
name,
|
|
155
179
|
baseType: typeInfo.typeName,
|
|
156
180
|
isArray,
|
|
157
|
-
isStruct
|
|
181
|
+
isStruct,
|
|
158
182
|
isConst,
|
|
159
183
|
isCallback: typeInfo.isCallback,
|
|
160
184
|
isString: typeInfo.isString,
|
|
185
|
+
isCallbackPointerPrimitive,
|
|
186
|
+
// Issue #895: Callback-compatible params need pointer semantics even in C++ mode
|
|
187
|
+
forcePointerSemantics: isCallbackPointerParam,
|
|
161
188
|
};
|
|
162
189
|
CodeGenState.currentParameters.set(name, paramInfo);
|
|
163
190
|
|
|
@@ -366,6 +393,40 @@ class FunctionContextManager {
|
|
|
366
393
|
return dimensions;
|
|
367
394
|
}
|
|
368
395
|
|
|
396
|
+
/**
|
|
397
|
+
* Issue #895: Get callback typedef parameter info from the C header.
|
|
398
|
+
* Returns null if not callback-compatible or index is invalid.
|
|
399
|
+
*/
|
|
400
|
+
static getCallbackTypedefParamInfo(
|
|
401
|
+
paramIndex: number,
|
|
402
|
+
): { shouldBePointer: boolean; shouldBeConst: boolean } | null {
|
|
403
|
+
if (CodeGenState.currentFunctionName === null) return null;
|
|
404
|
+
|
|
405
|
+
const typedefName = CodeGenState.callbackCompatibleFunctions.get(
|
|
406
|
+
CodeGenState.currentFunctionName,
|
|
407
|
+
);
|
|
408
|
+
if (!typedefName) return null;
|
|
409
|
+
|
|
410
|
+
const typedefType = CodeGenState.getTypedefType(typedefName);
|
|
411
|
+
if (!typedefType) return null;
|
|
412
|
+
|
|
413
|
+
const shouldBePointer = TypedefParamParser.shouldBePointer(
|
|
414
|
+
typedefType,
|
|
415
|
+
paramIndex,
|
|
416
|
+
);
|
|
417
|
+
const shouldBeConst = TypedefParamParser.shouldBeConst(
|
|
418
|
+
typedefType,
|
|
419
|
+
paramIndex,
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
if (shouldBePointer === null) return null;
|
|
423
|
+
|
|
424
|
+
return {
|
|
425
|
+
shouldBePointer,
|
|
426
|
+
shouldBeConst: shouldBeConst ?? false,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
369
430
|
/**
|
|
370
431
|
* Extract string capacity from a string type context.
|
|
371
432
|
*/
|
|
@@ -15,6 +15,19 @@
|
|
|
15
15
|
import ISeparatorContext from "../types/ISeparatorContext";
|
|
16
16
|
import IMemberSeparatorDeps from "../types/IMemberSeparatorDeps";
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Input parameters for building a separator context
|
|
20
|
+
*/
|
|
21
|
+
interface IBuildContextInput {
|
|
22
|
+
firstId: string;
|
|
23
|
+
hasGlobal: boolean;
|
|
24
|
+
hasThis: boolean;
|
|
25
|
+
currentScope: string | null;
|
|
26
|
+
isStructParam: boolean;
|
|
27
|
+
isCppAccess: boolean;
|
|
28
|
+
forcePointerSemantics?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
18
31
|
/**
|
|
19
32
|
* Static utility for resolving member access separators
|
|
20
33
|
*/
|
|
@@ -23,14 +36,18 @@ class MemberSeparatorResolver {
|
|
|
23
36
|
* Build the separator context for a member access chain
|
|
24
37
|
*/
|
|
25
38
|
static buildContext(
|
|
26
|
-
|
|
27
|
-
hasGlobal: boolean,
|
|
28
|
-
hasThis: boolean,
|
|
29
|
-
currentScope: string | null,
|
|
30
|
-
isStructParam: boolean,
|
|
39
|
+
input: IBuildContextInput,
|
|
31
40
|
deps: IMemberSeparatorDeps,
|
|
32
|
-
isCppAccess: boolean,
|
|
33
41
|
): ISeparatorContext {
|
|
42
|
+
const {
|
|
43
|
+
firstId,
|
|
44
|
+
hasGlobal,
|
|
45
|
+
hasThis,
|
|
46
|
+
currentScope,
|
|
47
|
+
isStructParam,
|
|
48
|
+
isCppAccess,
|
|
49
|
+
forcePointerSemantics,
|
|
50
|
+
} = input;
|
|
34
51
|
const isCrossScope =
|
|
35
52
|
hasGlobal &&
|
|
36
53
|
(deps.isKnownScope(firstId) || deps.isKnownRegister(firstId));
|
|
@@ -48,6 +65,7 @@ class MemberSeparatorResolver {
|
|
|
48
65
|
isCppAccess,
|
|
49
66
|
scopedRegName,
|
|
50
67
|
isScopedRegister,
|
|
68
|
+
forcePointerSemantics,
|
|
51
69
|
};
|
|
52
70
|
}
|
|
53
71
|
|
|
@@ -66,7 +84,11 @@ class MemberSeparatorResolver {
|
|
|
66
84
|
}
|
|
67
85
|
|
|
68
86
|
// Struct parameter uses -> in C mode, . in C++ mode
|
|
87
|
+
// Issue #895: forcePointerSemantics overrides C++ mode to use ->
|
|
69
88
|
if (ctx.isStructParam) {
|
|
89
|
+
if (ctx.forcePointerSemantics) {
|
|
90
|
+
return "->";
|
|
91
|
+
}
|
|
70
92
|
return deps.getStructParamSeparator();
|
|
71
93
|
}
|
|
72
94
|
|
|
@@ -28,6 +28,12 @@ class ParameterDereferenceResolver {
|
|
|
28
28
|
paramInfo: TParameterInfo,
|
|
29
29
|
deps: IParameterDereferenceDeps,
|
|
30
30
|
): boolean {
|
|
31
|
+
// Issue #895: Primitive params that became pointers due to callback typedef
|
|
32
|
+
// are NOT pass-by-value - they need dereferencing when used as values
|
|
33
|
+
if (paramInfo.isCallbackPointerPrimitive) {
|
|
34
|
+
return false; // Not pass-by-value → will be dereferenced
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
// ADR-029: Callback parameters are function pointers
|
|
32
38
|
if (paramInfo.isCallback) {
|
|
33
39
|
return true;
|
|
@@ -92,6 +98,12 @@ class ParameterDereferenceResolver {
|
|
|
92
98
|
return id;
|
|
93
99
|
}
|
|
94
100
|
|
|
101
|
+
// Issue #895: Callback-compatible primitives always need dereferencing,
|
|
102
|
+
// even in C++ mode (because they use pointer semantics to match C typedef)
|
|
103
|
+
if (paramInfo.forcePointerSemantics) {
|
|
104
|
+
return `(*${id})`;
|
|
105
|
+
}
|
|
106
|
+
|
|
95
107
|
// Known primitive that is pass-by-reference needs dereference
|
|
96
108
|
// Issue #558/#644: In C++ mode, primitives become references
|
|
97
109
|
return deps.maybeDereference(id);
|
|
@@ -42,6 +42,21 @@ interface IFromASTDeps {
|
|
|
42
42
|
|
|
43
43
|
/** Whether the parameter should use pass-by-value (pre-computed) */
|
|
44
44
|
isPassByValue: boolean;
|
|
45
|
+
|
|
46
|
+
/** Issue #895: Whether the current function is callback-compatible */
|
|
47
|
+
isCallbackCompatible: boolean;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Issue #895: Force pass-by-reference for callback-compatible functions
|
|
51
|
+
* When the typedef signature requires a pointer, this overrides normal logic.
|
|
52
|
+
*/
|
|
53
|
+
forcePassByReference?: boolean;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Issue #895: Force const qualifier from callback typedef signature.
|
|
57
|
+
* When the C typedef has `const T*`, this preserves const on the generated param.
|
|
58
|
+
*/
|
|
59
|
+
forceConst?: boolean;
|
|
45
60
|
}
|
|
46
61
|
|
|
47
62
|
/**
|
|
@@ -122,7 +137,15 @@ class ParameterInputAdapter {
|
|
|
122
137
|
// Determine classification for non-array, non-string types
|
|
123
138
|
const isKnownStruct = deps.isKnownStruct(typeName);
|
|
124
139
|
const isKnownPrimitive = !!deps.typeMap[typeName];
|
|
125
|
-
|
|
140
|
+
// Issue #895: Don't add auto-const for callback-compatible functions
|
|
141
|
+
// because it would change the signature and break typedef compatibility
|
|
142
|
+
const isAutoConst =
|
|
143
|
+
!deps.isCallbackCompatible && !deps.isModified && !isConst;
|
|
144
|
+
|
|
145
|
+
// Issue #895: For callback-compatible functions, force pass-by-reference
|
|
146
|
+
// when the typedef signature requires a pointer (e.g., opaque types)
|
|
147
|
+
const isPassByReference =
|
|
148
|
+
deps.forcePassByReference || isKnownStruct || isKnownPrimitive;
|
|
126
149
|
|
|
127
150
|
return {
|
|
128
151
|
name,
|
|
@@ -134,7 +157,12 @@ class ParameterInputAdapter {
|
|
|
134
157
|
isCallback: false,
|
|
135
158
|
isString: false,
|
|
136
159
|
isPassByValue: deps.isPassByValue,
|
|
137
|
-
isPassByReference
|
|
160
|
+
isPassByReference,
|
|
161
|
+
// Issue #895: Force pointer syntax in C++ mode for callback-compatible functions
|
|
162
|
+
// because C callback typedefs expect pointers, not C++ references
|
|
163
|
+
forcePointerSyntax: deps.forcePassByReference,
|
|
164
|
+
// Issue #895: Preserve const from callback typedef signature
|
|
165
|
+
forceConst: deps.forceConst,
|
|
138
166
|
};
|
|
139
167
|
}
|
|
140
168
|
|
|
@@ -103,14 +103,19 @@ class ParameterSignatureBuilder {
|
|
|
103
103
|
/**
|
|
104
104
|
* Build pass-by-reference parameter signature.
|
|
105
105
|
* C mode: const Point* p
|
|
106
|
-
* C++ mode: const Point& p
|
|
106
|
+
* C++ mode: const Point& p (unless forcePointerSyntax)
|
|
107
|
+
*
|
|
108
|
+
* Issue #895: When forcePointerSyntax is set, always use pointer syntax
|
|
109
|
+
* because C callback typedefs expect pointers, not C++ references.
|
|
107
110
|
*/
|
|
108
111
|
private static _buildRefParam(
|
|
109
112
|
param: IParameterInput,
|
|
110
113
|
refSuffix: string,
|
|
111
114
|
): string {
|
|
112
115
|
const constPrefix = this._getConstPrefix(param);
|
|
113
|
-
|
|
116
|
+
// Issue #895: Override refSuffix for callback-compatible functions
|
|
117
|
+
const actualSuffix = param.forcePointerSyntax ? "*" : refSuffix;
|
|
118
|
+
return `${constPrefix}${param.mappedType}${actualSuffix} ${param.name}`;
|
|
114
119
|
}
|
|
115
120
|
|
|
116
121
|
/**
|
|
@@ -122,13 +127,16 @@ class ParameterSignatureBuilder {
|
|
|
122
127
|
}
|
|
123
128
|
|
|
124
129
|
/**
|
|
125
|
-
* Get const prefix combining explicit const
|
|
126
|
-
*
|
|
130
|
+
* Get const prefix combining explicit const, auto-const, and forced const.
|
|
131
|
+
* Priority: forceConst > isConst > isAutoConst
|
|
132
|
+
* Issue #895: forceConst preserves const from callback typedef signature.
|
|
127
133
|
*/
|
|
128
134
|
private static _getConstPrefix(param: IParameterInput): string {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
135
|
+
// Any source of const results in "const " prefix
|
|
136
|
+
if (param.forceConst || param.isConst || param.isAutoConst) {
|
|
137
|
+
return "const ";
|
|
138
|
+
}
|
|
139
|
+
return "";
|
|
132
140
|
}
|
|
133
141
|
}
|
|
134
142
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import * as Parser from "../../../logic/parser/grammar/CNextParser.js";
|
|
12
12
|
import CodeGenState from "../../../state/CodeGenState.js";
|
|
13
13
|
import StringUtils from "../../../../utils/StringUtils.js";
|
|
14
|
-
import ExpressionUnwrapper from "
|
|
14
|
+
import ExpressionUnwrapper from "../../../../utils/ExpressionUnwrapper";
|
|
15
15
|
|
|
16
16
|
/** Regex for identifying valid C/C++ identifiers */
|
|
17
17
|
const IDENTIFIER_REGEX = /^[a-zA-Z_]\w*$/;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypedefParamParser - Parses C function pointer typedef signatures
|
|
3
|
+
*
|
|
4
|
+
* Extracts parameter types from typedef strings like:
|
|
5
|
+
* "void (*)(widget_t *, const rect_t *, uint8_t *)"
|
|
6
|
+
* "void (*)(Point p)"
|
|
7
|
+
*
|
|
8
|
+
* Used by Issue #895 to determine if callback params should be pointers or values.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parsed parameter info from a typedef.
|
|
13
|
+
*/
|
|
14
|
+
interface ITypedefParam {
|
|
15
|
+
/** Full type string (e.g., "widget_t *", "const rect_t *", "uint8_t *") */
|
|
16
|
+
type: string;
|
|
17
|
+
/** Whether this is a pointer type */
|
|
18
|
+
isPointer: boolean;
|
|
19
|
+
/** Whether this has const qualifier */
|
|
20
|
+
isConst: boolean;
|
|
21
|
+
/** Base type without pointer/const (e.g., "widget_t", "rect_t", "uint8_t") */
|
|
22
|
+
baseType: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parse result for a typedef signature.
|
|
27
|
+
*/
|
|
28
|
+
interface ITypedefParseResult {
|
|
29
|
+
/** Return type */
|
|
30
|
+
returnType: string;
|
|
31
|
+
/** Parsed parameters */
|
|
32
|
+
params: ITypedefParam[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
class TypedefParamParser {
|
|
36
|
+
/**
|
|
37
|
+
* Parse a function pointer typedef type string.
|
|
38
|
+
*
|
|
39
|
+
* @param typedefType - The type string, e.g., "void (*)(widget_t *, const rect_t *, uint8_t *)"
|
|
40
|
+
* @returns Parsed result with return type and parameters, or null if parsing fails
|
|
41
|
+
*/
|
|
42
|
+
static parse(typedefType: string): ITypedefParseResult | null {
|
|
43
|
+
// Expected format: "return_type (*)(param1, param2, ...)"
|
|
44
|
+
// Find the (*) marker first
|
|
45
|
+
const funcPtrIndex = typedefType.indexOf("(*)");
|
|
46
|
+
if (funcPtrIndex === -1) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const returnType = typedefType.substring(0, funcPtrIndex).trim();
|
|
51
|
+
|
|
52
|
+
// Find the opening paren after (*)
|
|
53
|
+
const afterFuncPtr = typedefType.substring(funcPtrIndex + 3).trim();
|
|
54
|
+
if (!afterFuncPtr.startsWith("(")) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Extract params by finding the matching closing paren
|
|
59
|
+
const paramsStr = TypedefParamParser.extractParenContent(afterFuncPtr);
|
|
60
|
+
if (paramsStr === null) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Handle void or empty params
|
|
65
|
+
if (!paramsStr || paramsStr === "void") {
|
|
66
|
+
return { returnType, params: [] };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Split by comma, handling nested parentheses
|
|
70
|
+
const paramStrings = TypedefParamParser.splitParams(paramsStr);
|
|
71
|
+
const params = paramStrings.map((p) => TypedefParamParser.parseParam(p));
|
|
72
|
+
|
|
73
|
+
return { returnType, params };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Extract content between matching parentheses, handling arbitrary nesting.
|
|
78
|
+
* @param str - String starting with '('
|
|
79
|
+
* @returns Content between outer parens, or null if no match
|
|
80
|
+
*/
|
|
81
|
+
private static extractParenContent(str: string): string | null {
|
|
82
|
+
if (!str.startsWith("(")) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let depth = 0;
|
|
87
|
+
for (let i = 0; i < str.length; i++) {
|
|
88
|
+
if (str[i] === "(") {
|
|
89
|
+
depth++;
|
|
90
|
+
} else if (str[i] === ")") {
|
|
91
|
+
depth--;
|
|
92
|
+
if (depth === 0) {
|
|
93
|
+
// Found matching close paren - return content between
|
|
94
|
+
return str.substring(1, i);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// No matching close paren found
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Split parameter string by commas, respecting nested parentheses.
|
|
105
|
+
*/
|
|
106
|
+
private static splitParams(paramsStr: string): string[] {
|
|
107
|
+
const params: string[] = [];
|
|
108
|
+
let current = "";
|
|
109
|
+
let depth = 0;
|
|
110
|
+
|
|
111
|
+
for (const char of paramsStr) {
|
|
112
|
+
if (char === "(") {
|
|
113
|
+
depth++;
|
|
114
|
+
current += char;
|
|
115
|
+
} else if (char === ")") {
|
|
116
|
+
depth--;
|
|
117
|
+
current += char;
|
|
118
|
+
} else if (char === "," && depth === 0) {
|
|
119
|
+
params.push(current.trim());
|
|
120
|
+
current = "";
|
|
121
|
+
} else {
|
|
122
|
+
current += char;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (current.trim()) {
|
|
127
|
+
params.push(current.trim());
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return params;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Parse a single parameter type string.
|
|
135
|
+
*/
|
|
136
|
+
private static parseParam(paramStr: string): ITypedefParam {
|
|
137
|
+
const trimmed = paramStr.trim();
|
|
138
|
+
|
|
139
|
+
// Check for pointer
|
|
140
|
+
const isPointer = trimmed.includes("*");
|
|
141
|
+
|
|
142
|
+
// Check for const - handles both "const " (with space) and merged forms
|
|
143
|
+
// C grammar getText() strips spaces, so "const rect_t*" may appear merged
|
|
144
|
+
const isConst = /\bconst\b/.test(trimmed) || trimmed.startsWith("const");
|
|
145
|
+
|
|
146
|
+
// Extract base type (remove const, *, and param name if present)
|
|
147
|
+
let baseType = trimmed
|
|
148
|
+
.replaceAll(/\bconst\b/g, "") // Remove const (with word boundary)
|
|
149
|
+
.replace(/^const/, "") // Remove const at start (no space case) - only once
|
|
150
|
+
.replaceAll("*", "") // Remove pointers
|
|
151
|
+
.replaceAll(/\s+/g, " ") // Normalize whitespace
|
|
152
|
+
.trim();
|
|
153
|
+
|
|
154
|
+
// Remove trailing param name if present (e.g., "rect_t area" -> "rect_t")
|
|
155
|
+
// Only remove if there are multiple words (space-separated)
|
|
156
|
+
if (baseType.includes(" ")) {
|
|
157
|
+
baseType = baseType.replace(/\s+\w+$/, "");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Handle struct keyword
|
|
161
|
+
if (baseType.startsWith("struct ")) {
|
|
162
|
+
baseType = baseType.substring(7);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
type: trimmed,
|
|
167
|
+
isPointer,
|
|
168
|
+
isConst,
|
|
169
|
+
baseType,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get the parameter info at a given index, or null if not found.
|
|
175
|
+
*/
|
|
176
|
+
private static getParamAt(
|
|
177
|
+
typedefType: string,
|
|
178
|
+
paramIndex: number,
|
|
179
|
+
): ITypedefParam | null {
|
|
180
|
+
const parsed = TypedefParamParser.parse(typedefType);
|
|
181
|
+
if (!parsed || paramIndex >= parsed.params.length) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
return parsed.params[paramIndex];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Check if a parameter at a given index should be a pointer based on the typedef.
|
|
189
|
+
*
|
|
190
|
+
* @param typedefType - The typedef type string
|
|
191
|
+
* @param paramIndex - The parameter index (0-based)
|
|
192
|
+
* @returns true if the param should be a pointer, false for value, null if unknown
|
|
193
|
+
*/
|
|
194
|
+
static shouldBePointer(
|
|
195
|
+
typedefType: string,
|
|
196
|
+
paramIndex: number,
|
|
197
|
+
): boolean | null {
|
|
198
|
+
return (
|
|
199
|
+
TypedefParamParser.getParamAt(typedefType, paramIndex)?.isPointer ?? null
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Check if a parameter at a given index should be const based on the typedef.
|
|
205
|
+
*
|
|
206
|
+
* @param typedefType - The typedef type string
|
|
207
|
+
* @param paramIndex - The parameter index (0-based)
|
|
208
|
+
* @returns true if the param should be const, false otherwise, null if unknown
|
|
209
|
+
*/
|
|
210
|
+
static shouldBeConst(
|
|
211
|
+
typedefType: string,
|
|
212
|
+
paramIndex: number,
|
|
213
|
+
): boolean | null {
|
|
214
|
+
return (
|
|
215
|
+
TypedefParamParser.getParamAt(typedefType, paramIndex)?.isConst ?? null
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export default TypedefParamParser;
|