c-next 0.2.2 → 0.2.4
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 +641 -191
- package/dist/index.js.map +4 -4
- package/package.json +1 -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 +170 -14
- package/src/transpiler/logic/analysis/__tests__/ArrayIndexTypeAnalyzer.test.ts +545 -0
- package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +327 -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/index.ts +50 -1
- package/src/transpiler/logic/symbols/c/utils/DeclaratorUtils.ts +99 -2
- package/src/transpiler/logic/symbols/c/utils/__tests__/DeclaratorUtils.test.ts +128 -0
- package/src/transpiler/output/codegen/CodeGenerator.ts +31 -5
- package/src/transpiler/output/codegen/TypeValidator.ts +10 -7
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +49 -36
- package/src/transpiler/output/codegen/__tests__/ExpressionWalker.test.ts +9 -3
- package/src/transpiler/output/codegen/__tests__/RequireInclude.test.ts +90 -25
- 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 +23 -14
- package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +10 -7
- package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +12 -3
- package/src/transpiler/output/codegen/generators/statements/SwitchGenerator.ts +10 -1
- package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +4 -4
- package/src/transpiler/output/codegen/helpers/ArrayAccessHelper.ts +6 -3
- package/src/transpiler/output/codegen/helpers/FloatBitHelper.ts +36 -22
- package/src/transpiler/output/codegen/helpers/FunctionContextManager.ts +9 -1
- package/src/transpiler/output/codegen/helpers/__tests__/ArrayAccessHelper.test.ts +8 -6
- package/src/transpiler/output/codegen/helpers/__tests__/FloatBitHelper.test.ts +34 -18
- package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +5 -2
- package/src/transpiler/state/CodeGenState.ts +6 -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/utils/constants/TypeConstants.ts +22 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Array Index Type Analyzer
|
|
3
|
+
* Detects signed and floating-point types used as array or bit subscript indexes
|
|
4
|
+
*
|
|
5
|
+
* C-Next requires unsigned integer types for all subscript operations to prevent
|
|
6
|
+
* undefined behavior from negative indexes. This analyzer catches type violations
|
|
7
|
+
* at compile time with clear error messages.
|
|
8
|
+
*
|
|
9
|
+
* Two-pass analysis:
|
|
10
|
+
* 1. Collect variable declarations with their types
|
|
11
|
+
* 2. Validate subscript expressions use unsigned integer types
|
|
12
|
+
*
|
|
13
|
+
* Uses CodeGenState for state-based type resolution (struct fields, function
|
|
14
|
+
* return types, enum detection) to handle complex expressions like arr[x + 1].
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { ParseTreeWalker } from "antlr4ng";
|
|
18
|
+
import { CNextListener } from "../parser/grammar/CNextListener";
|
|
19
|
+
import * as Parser from "../parser/grammar/CNextParser";
|
|
20
|
+
import IArrayIndexTypeError from "./types/IArrayIndexTypeError";
|
|
21
|
+
import LiteralUtils from "../../../utils/LiteralUtils";
|
|
22
|
+
import ParserUtils from "../../../utils/ParserUtils";
|
|
23
|
+
import TypeConstants from "../../../utils/constants/TypeConstants";
|
|
24
|
+
import CodeGenState from "../../state/CodeGenState";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* First pass: Collect variable declarations with their types
|
|
28
|
+
*/
|
|
29
|
+
class VariableTypeCollector extends CNextListener {
|
|
30
|
+
private readonly varTypes: Map<string, string> = new Map();
|
|
31
|
+
|
|
32
|
+
public getVarTypes(): Map<string, string> {
|
|
33
|
+
return this.varTypes;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private trackType(
|
|
37
|
+
typeCtx: Parser.TypeContext | null,
|
|
38
|
+
identifier: { getText(): string } | null,
|
|
39
|
+
): void {
|
|
40
|
+
if (!typeCtx || !identifier) return;
|
|
41
|
+
this.varTypes.set(identifier.getText(), typeCtx.getText());
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
override enterVariableDeclaration = (
|
|
45
|
+
ctx: Parser.VariableDeclarationContext,
|
|
46
|
+
): void => {
|
|
47
|
+
this.trackType(ctx.type(), ctx.IDENTIFIER());
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
override enterParameter = (ctx: Parser.ParameterContext): void => {
|
|
51
|
+
this.trackType(ctx.type(), ctx.IDENTIFIER());
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
override enterForVarDecl = (ctx: Parser.ForVarDeclContext): void => {
|
|
55
|
+
this.trackType(ctx.type(), ctx.IDENTIFIER());
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Second pass: Validate subscript index expressions use unsigned integer types
|
|
61
|
+
*/
|
|
62
|
+
class IndexTypeListener extends CNextListener {
|
|
63
|
+
private readonly analyzer: ArrayIndexTypeAnalyzer;
|
|
64
|
+
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/lines-between-class-members
|
|
66
|
+
private readonly varTypes: Map<string, string>;
|
|
67
|
+
|
|
68
|
+
constructor(analyzer: ArrayIndexTypeAnalyzer, varTypes: Map<string, string>) {
|
|
69
|
+
super();
|
|
70
|
+
this.analyzer = analyzer;
|
|
71
|
+
this.varTypes = varTypes;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Check postfix operations in expressions (RHS: arr[idx], flags[bit])
|
|
76
|
+
*/
|
|
77
|
+
override enterPostfixOp = (ctx: Parser.PostfixOpContext): void => {
|
|
78
|
+
if (!ctx.LBRACKET()) return;
|
|
79
|
+
|
|
80
|
+
const expressions = ctx.expression();
|
|
81
|
+
for (const expr of expressions) {
|
|
82
|
+
this.validateIndexExpression(expr);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check postfix target operations in assignments (LHS: arr[idx] <- val)
|
|
88
|
+
*/
|
|
89
|
+
override enterPostfixTargetOp = (
|
|
90
|
+
ctx: Parser.PostfixTargetOpContext,
|
|
91
|
+
): void => {
|
|
92
|
+
if (!ctx.LBRACKET()) return;
|
|
93
|
+
|
|
94
|
+
const expressions = ctx.expression();
|
|
95
|
+
for (const expr of expressions) {
|
|
96
|
+
this.validateIndexExpression(expr);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Validate that a subscript index expression uses an unsigned integer type.
|
|
102
|
+
* Collects all leaf operands from the expression and checks each one.
|
|
103
|
+
*/
|
|
104
|
+
private validateIndexExpression(ctx: Parser.ExpressionContext): void {
|
|
105
|
+
const operands = this.collectOperands(ctx);
|
|
106
|
+
|
|
107
|
+
for (const operand of operands) {
|
|
108
|
+
const resolvedType = this.resolveOperandType(operand);
|
|
109
|
+
if (!resolvedType) continue;
|
|
110
|
+
|
|
111
|
+
if (TypeConstants.SIGNED_TYPES.includes(resolvedType)) {
|
|
112
|
+
const { line, column } = ParserUtils.getPosition(ctx);
|
|
113
|
+
this.analyzer.addError(line, column, "E0850", resolvedType);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (
|
|
118
|
+
resolvedType === "float literal" ||
|
|
119
|
+
TypeConstants.FLOAT_TYPES.includes(resolvedType)
|
|
120
|
+
) {
|
|
121
|
+
const { line, column } = ParserUtils.getPosition(ctx);
|
|
122
|
+
this.analyzer.addError(line, column, "E0851", resolvedType);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (TypeConstants.UNSIGNED_INDEX_TYPES.includes(resolvedType)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Other non-integer types (e.g., string, struct) - E0852
|
|
131
|
+
const { line, column } = ParserUtils.getPosition(ctx);
|
|
132
|
+
this.analyzer.addError(line, column, "E0852", resolvedType);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Collect all leaf unary expression operands from an expression tree.
|
|
139
|
+
* Handles binary operators at any level by flatMapping through the grammar hierarchy.
|
|
140
|
+
*/
|
|
141
|
+
private collectOperands(
|
|
142
|
+
ctx: Parser.ExpressionContext,
|
|
143
|
+
): Parser.UnaryExpressionContext[] {
|
|
144
|
+
const ternary = ctx.ternaryExpression();
|
|
145
|
+
if (!ternary) return [];
|
|
146
|
+
|
|
147
|
+
const orExpressions = ternary.orExpression();
|
|
148
|
+
if (orExpressions.length === 0) return [];
|
|
149
|
+
|
|
150
|
+
// For ternary (cond ? true : false), skip the condition (index 0)
|
|
151
|
+
// and only check the value branches (indices 1 and 2)
|
|
152
|
+
const valueExpressions =
|
|
153
|
+
orExpressions.length === 3 ? orExpressions.slice(1) : orExpressions;
|
|
154
|
+
|
|
155
|
+
return valueExpressions
|
|
156
|
+
.flatMap((o) => o.andExpression())
|
|
157
|
+
.flatMap((a) => a.equalityExpression())
|
|
158
|
+
.flatMap((e) => e.relationalExpression())
|
|
159
|
+
.flatMap((r) => r.bitwiseOrExpression())
|
|
160
|
+
.flatMap((bo) => bo.bitwiseXorExpression())
|
|
161
|
+
.flatMap((bx) => bx.bitwiseAndExpression())
|
|
162
|
+
.flatMap((ba) => ba.shiftExpression())
|
|
163
|
+
.flatMap((s) => s.additiveExpression())
|
|
164
|
+
.flatMap((a) => a.multiplicativeExpression())
|
|
165
|
+
.flatMap((m) => m.unaryExpression());
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Resolve the type of a unary expression operand.
|
|
170
|
+
* Uses local varTypes first, then falls back to CodeGenState for
|
|
171
|
+
* struct fields, function return types, and enum detection.
|
|
172
|
+
*
|
|
173
|
+
* Returns null if the type cannot be resolved (pass-through).
|
|
174
|
+
*/
|
|
175
|
+
private resolveOperandType(
|
|
176
|
+
operand: Parser.UnaryExpressionContext,
|
|
177
|
+
): string | null {
|
|
178
|
+
const postfixExpr = operand.postfixExpression();
|
|
179
|
+
if (!postfixExpr) return null;
|
|
180
|
+
|
|
181
|
+
const primaryExpr = postfixExpr.primaryExpression();
|
|
182
|
+
if (!primaryExpr) return null;
|
|
183
|
+
|
|
184
|
+
// Resolve base type from primaryExpression
|
|
185
|
+
let currentType = this.resolveBaseType(primaryExpr);
|
|
186
|
+
|
|
187
|
+
// Walk postfix operators to transform the type
|
|
188
|
+
const postfixOps = postfixExpr.postfixOp();
|
|
189
|
+
|
|
190
|
+
// If base type is null but there are postfix ops, use identifier name
|
|
191
|
+
// for function call / member access resolution (e.g., getIndex())
|
|
192
|
+
if (!currentType && postfixOps.length > 0) {
|
|
193
|
+
const identifier = primaryExpr.IDENTIFIER();
|
|
194
|
+
if (identifier) {
|
|
195
|
+
currentType = identifier.getText();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
for (const op of postfixOps) {
|
|
200
|
+
if (!currentType) return null;
|
|
201
|
+
currentType = this.resolvePostfixOpType(currentType, op);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return currentType;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Resolve the base type of a primary expression.
|
|
209
|
+
*/
|
|
210
|
+
private resolveBaseType(
|
|
211
|
+
primaryExpr: Parser.PrimaryExpressionContext,
|
|
212
|
+
): string | null {
|
|
213
|
+
// Check for literal
|
|
214
|
+
const literal = primaryExpr.literal();
|
|
215
|
+
if (literal) {
|
|
216
|
+
if (LiteralUtils.isFloat(literal)) return "float literal";
|
|
217
|
+
// Integer literals are always valid
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Check for parenthesized expression — recurse
|
|
222
|
+
const parenExpr = primaryExpr.expression();
|
|
223
|
+
if (parenExpr) {
|
|
224
|
+
const innerOperands = this.collectOperands(parenExpr);
|
|
225
|
+
for (const innerOp of innerOperands) {
|
|
226
|
+
const innerType = this.resolveOperandType(innerOp);
|
|
227
|
+
if (innerType) return innerType;
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Check for identifier
|
|
233
|
+
const identifier = primaryExpr.IDENTIFIER();
|
|
234
|
+
if (!identifier) return null;
|
|
235
|
+
|
|
236
|
+
const varName = identifier.getText();
|
|
237
|
+
|
|
238
|
+
// Local variables first (params, for-loop vars, function body vars)
|
|
239
|
+
const localType = this.varTypes.get(varName);
|
|
240
|
+
if (localType) return localType;
|
|
241
|
+
|
|
242
|
+
// Fall back to CodeGenState for cross-file variables
|
|
243
|
+
const typeInfo = CodeGenState.getVariableTypeInfo(varName);
|
|
244
|
+
if (typeInfo) return typeInfo.baseType;
|
|
245
|
+
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Resolve the resulting type after applying a postfix operator.
|
|
251
|
+
*/
|
|
252
|
+
private resolvePostfixOpType(
|
|
253
|
+
currentType: string,
|
|
254
|
+
op: Parser.PostfixOpContext,
|
|
255
|
+
): string | null {
|
|
256
|
+
// Dot access (e.g., config.value, EColor.RED)
|
|
257
|
+
if (op.DOT()) {
|
|
258
|
+
const fieldId = op.IDENTIFIER();
|
|
259
|
+
if (!fieldId) return null;
|
|
260
|
+
const fieldName = fieldId.getText();
|
|
261
|
+
|
|
262
|
+
// Check if it's an enum access — always valid
|
|
263
|
+
if (CodeGenState.isKnownEnum(currentType)) return null;
|
|
264
|
+
|
|
265
|
+
// Check struct field type
|
|
266
|
+
const fieldType = CodeGenState.getStructFieldType(currentType, fieldName);
|
|
267
|
+
return fieldType ?? null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Array/bit subscript (e.g., lookup[idx])
|
|
271
|
+
if (op.LBRACKET()) {
|
|
272
|
+
// If current type is an array, result is the element type
|
|
273
|
+
// If current type is an integer, result is "bool" (bit access)
|
|
274
|
+
if (TypeConstants.UNSIGNED_INDEX_TYPES.includes(currentType)) {
|
|
275
|
+
return "bool";
|
|
276
|
+
}
|
|
277
|
+
if (TypeConstants.SIGNED_TYPES.includes(currentType)) {
|
|
278
|
+
return "bool";
|
|
279
|
+
}
|
|
280
|
+
// Array element type — strip array suffix
|
|
281
|
+
return currentType;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Function call (e.g., getIndex())
|
|
285
|
+
if (op.LPAREN()) {
|
|
286
|
+
const returnType = CodeGenState.getFunctionReturnType(currentType);
|
|
287
|
+
return returnType ?? null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Analyzer that detects non-unsigned-integer types used as subscript indexes
|
|
296
|
+
*/
|
|
297
|
+
class ArrayIndexTypeAnalyzer {
|
|
298
|
+
private errors: IArrayIndexTypeError[] = [];
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Analyze the parse tree for invalid subscript index types
|
|
302
|
+
*/
|
|
303
|
+
public analyze(tree: Parser.ProgramContext): IArrayIndexTypeError[] {
|
|
304
|
+
this.errors = [];
|
|
305
|
+
|
|
306
|
+
// First pass: collect variable types
|
|
307
|
+
const collector = new VariableTypeCollector();
|
|
308
|
+
ParseTreeWalker.DEFAULT.walk(collector, tree);
|
|
309
|
+
const varTypes = collector.getVarTypes();
|
|
310
|
+
|
|
311
|
+
// Second pass: validate subscript index expressions
|
|
312
|
+
const listener = new IndexTypeListener(this, varTypes);
|
|
313
|
+
ParseTreeWalker.DEFAULT.walk(listener, tree);
|
|
314
|
+
|
|
315
|
+
return this.errors;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Add an index type error
|
|
320
|
+
*/
|
|
321
|
+
public addError(
|
|
322
|
+
line: number,
|
|
323
|
+
column: number,
|
|
324
|
+
code: string,
|
|
325
|
+
actualType: string,
|
|
326
|
+
): void {
|
|
327
|
+
this.errors.push({
|
|
328
|
+
code,
|
|
329
|
+
line,
|
|
330
|
+
column,
|
|
331
|
+
actualType,
|
|
332
|
+
message: `Subscript index must be an unsigned integer type; got '${actualType}'`,
|
|
333
|
+
helpText:
|
|
334
|
+
"Use an unsigned integer type (u8, u16, u32, u64) for array and bit subscript indexes.",
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Get all detected errors
|
|
340
|
+
*/
|
|
341
|
+
public getErrors(): IArrayIndexTypeError[] {
|
|
342
|
+
return this.errors;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export default ArrayIndexTypeAnalyzer;
|
|
@@ -13,6 +13,7 @@ import * as Parser from "../parser/grammar/CNextParser";
|
|
|
13
13
|
import SymbolTable from "../symbols/SymbolTable";
|
|
14
14
|
import IFunctionCallError from "./types/IFunctionCallError";
|
|
15
15
|
import ParserUtils from "../../../utils/ParserUtils";
|
|
16
|
+
import CodeGenState from "../../state/CodeGenState";
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* C-Next built-in functions
|
|
@@ -215,6 +216,17 @@ class FunctionCallListener extends CNextListener {
|
|
|
215
216
|
// ISR/Callback Variable Tracking (ADR-040)
|
|
216
217
|
// ========================================================================
|
|
217
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Check if a type name represents a callable type (ISR, callback, or C function pointer typedef).
|
|
221
|
+
*/
|
|
222
|
+
private isCallableType(typeName: string): boolean {
|
|
223
|
+
return (
|
|
224
|
+
typeName === "ISR" ||
|
|
225
|
+
this.analyzer.isCallbackType(typeName) ||
|
|
226
|
+
this.analyzer.isCFunctionPointerTypedef(typeName)
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
218
230
|
/**
|
|
219
231
|
* Track ISR-typed variables from variable declarations
|
|
220
232
|
* e.g., `ISR handler <- myFunction;`
|
|
@@ -222,13 +234,9 @@ class FunctionCallListener extends CNextListener {
|
|
|
222
234
|
override enterVariableDeclaration = (
|
|
223
235
|
ctx: Parser.VariableDeclarationContext,
|
|
224
236
|
): void => {
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
// Check if this is an ISR type or a callback type (function-as-type)
|
|
229
|
-
if (typeName === "ISR" || this.analyzer.isCallbackType(typeName)) {
|
|
230
|
-
const varName = ctx.IDENTIFIER().getText();
|
|
231
|
-
this.analyzer.defineCallableVariable(varName);
|
|
237
|
+
const typeName = ctx.type().getText();
|
|
238
|
+
if (this.isCallableType(typeName)) {
|
|
239
|
+
this.analyzer.defineCallableVariable(ctx.IDENTIFIER().getText());
|
|
232
240
|
}
|
|
233
241
|
};
|
|
234
242
|
|
|
@@ -237,13 +245,9 @@ class FunctionCallListener extends CNextListener {
|
|
|
237
245
|
* e.g., `void execute(ISR handler) { handler(); }`
|
|
238
246
|
*/
|
|
239
247
|
override enterParameter = (ctx: Parser.ParameterContext): void => {
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
// Check if this is an ISR type or a callback type (function-as-type)
|
|
244
|
-
if (typeName === "ISR" || this.analyzer.isCallbackType(typeName)) {
|
|
245
|
-
const paramName = ctx.IDENTIFIER().getText();
|
|
246
|
-
this.analyzer.defineCallableVariable(paramName);
|
|
248
|
+
const typeName = ctx.type().getText();
|
|
249
|
+
if (this.isCallableType(typeName)) {
|
|
250
|
+
this.analyzer.defineCallableVariable(ctx.IDENTIFIER().getText());
|
|
247
251
|
}
|
|
248
252
|
};
|
|
249
253
|
|
|
@@ -451,6 +455,7 @@ class FunctionCallAnalyzer {
|
|
|
451
455
|
this.collectIncludes(tree);
|
|
452
456
|
this.collectCallbackTypes(tree);
|
|
453
457
|
this.collectAllLocalFunctions(tree);
|
|
458
|
+
this.collectCallbackCompatibleFunctions(tree);
|
|
454
459
|
|
|
455
460
|
// Second pass: walk tree in order, tracking definitions and checking calls
|
|
456
461
|
const listener = new FunctionCallListener(this);
|
|
@@ -551,6 +556,157 @@ class FunctionCallAnalyzer {
|
|
|
551
556
|
return this.callbackTypes.has(name);
|
|
552
557
|
}
|
|
553
558
|
|
|
559
|
+
/**
|
|
560
|
+
* Check if a type name is a C function pointer typedef.
|
|
561
|
+
* Looks up the type in the symbol table and checks if it's a typedef
|
|
562
|
+
* whose underlying type contains "(*)" indicating a function pointer.
|
|
563
|
+
*/
|
|
564
|
+
public isCFunctionPointerTypedef(typeName: string): boolean {
|
|
565
|
+
if (!this.symbolTable) return false;
|
|
566
|
+
const sym = this.symbolTable.getCSymbol(typeName);
|
|
567
|
+
if (sym?.kind !== "type") return false;
|
|
568
|
+
// ICTypedefSymbol has a `type` field with the underlying C type string
|
|
569
|
+
return (
|
|
570
|
+
"type" in sym && typeof sym.type === "string" && sym.type.includes("(*)")
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Detect functions assigned to C function pointer typedefs.
|
|
576
|
+
* When `PointCallback cb <- my_handler;` is found and PointCallback
|
|
577
|
+
* is a C function pointer typedef, mark my_handler as callback-compatible.
|
|
578
|
+
*/
|
|
579
|
+
private collectCallbackCompatibleFunctions(
|
|
580
|
+
tree: Parser.ProgramContext,
|
|
581
|
+
): void {
|
|
582
|
+
for (const decl of tree.declaration()) {
|
|
583
|
+
const funcDecl = decl.functionDeclaration();
|
|
584
|
+
if (!funcDecl) continue;
|
|
585
|
+
|
|
586
|
+
const block = funcDecl.block();
|
|
587
|
+
if (!block) continue;
|
|
588
|
+
|
|
589
|
+
this.scanBlockForCallbackAssignments(block);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Recursively scan all statements in a block for callback typedef assignments.
|
|
595
|
+
*/
|
|
596
|
+
private scanBlockForCallbackAssignments(block: Parser.BlockContext): void {
|
|
597
|
+
for (const stmt of block.statement()) {
|
|
598
|
+
this.scanStatementForCallbackAssignments(stmt);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Scan a single statement for callback typedef assignments,
|
|
604
|
+
* recursing into nested blocks (if/while/for/do-while/switch/critical).
|
|
605
|
+
*/
|
|
606
|
+
private scanStatementForCallbackAssignments(
|
|
607
|
+
stmt: Parser.StatementContext,
|
|
608
|
+
): void {
|
|
609
|
+
// Check variable declarations for callback assignments
|
|
610
|
+
const varDecl = stmt.variableDeclaration();
|
|
611
|
+
if (varDecl) {
|
|
612
|
+
this.checkVarDeclForCallbackAssignment(varDecl);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Recurse into nested blocks/statements
|
|
617
|
+
const ifStmt = stmt.ifStatement();
|
|
618
|
+
if (ifStmt) {
|
|
619
|
+
for (const child of ifStmt.statement()) {
|
|
620
|
+
this.scanStatementForCallbackAssignments(child);
|
|
621
|
+
}
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const whileStmt = stmt.whileStatement();
|
|
626
|
+
if (whileStmt) {
|
|
627
|
+
this.scanStatementForCallbackAssignments(whileStmt.statement());
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const forStmt = stmt.forStatement();
|
|
632
|
+
if (forStmt) {
|
|
633
|
+
this.scanStatementForCallbackAssignments(forStmt.statement());
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const doWhileStmt = stmt.doWhileStatement();
|
|
638
|
+
if (doWhileStmt) {
|
|
639
|
+
this.scanBlockForCallbackAssignments(doWhileStmt.block());
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const switchStmt = stmt.switchStatement();
|
|
644
|
+
if (switchStmt) {
|
|
645
|
+
for (const caseCtx of switchStmt.switchCase()) {
|
|
646
|
+
this.scanBlockForCallbackAssignments(caseCtx.block());
|
|
647
|
+
}
|
|
648
|
+
const defaultCtx = switchStmt.defaultCase();
|
|
649
|
+
if (defaultCtx) {
|
|
650
|
+
this.scanBlockForCallbackAssignments(defaultCtx.block());
|
|
651
|
+
}
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const criticalStmt = stmt.criticalStatement();
|
|
656
|
+
if (criticalStmt) {
|
|
657
|
+
this.scanBlockForCallbackAssignments(criticalStmt.block());
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// A statement can itself be a block
|
|
662
|
+
const nestedBlock = stmt.block();
|
|
663
|
+
if (nestedBlock) {
|
|
664
|
+
this.scanBlockForCallbackAssignments(nestedBlock);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Check if a variable declaration assigns a function to a C callback typedef.
|
|
670
|
+
*/
|
|
671
|
+
private checkVarDeclForCallbackAssignment(
|
|
672
|
+
varDecl: Parser.VariableDeclarationContext,
|
|
673
|
+
): void {
|
|
674
|
+
const typeName = varDecl.type().getText();
|
|
675
|
+
if (!this.isCFunctionPointerTypedef(typeName)) return;
|
|
676
|
+
|
|
677
|
+
const expr = varDecl.expression();
|
|
678
|
+
if (!expr) return;
|
|
679
|
+
|
|
680
|
+
const funcRef = this.extractFunctionReference(expr);
|
|
681
|
+
if (!funcRef) return;
|
|
682
|
+
|
|
683
|
+
// Scope-qualified names use dot in source (MyScope.handler) but
|
|
684
|
+
// allLocalFunctions stores them with underscore (MyScope_handler)
|
|
685
|
+
const lookupName = funcRef.includes(".")
|
|
686
|
+
? funcRef.replace(".", "_")
|
|
687
|
+
: funcRef;
|
|
688
|
+
|
|
689
|
+
if (this.allLocalFunctions.has(lookupName)) {
|
|
690
|
+
CodeGenState.callbackCompatibleFunctions.add(lookupName);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Extract a function reference from an expression context.
|
|
696
|
+
* Matches bare identifiers (e.g., "my_handler") and qualified scope
|
|
697
|
+
* names (e.g., "MyScope.handler").
|
|
698
|
+
* Returns null if the expression is not a function reference.
|
|
699
|
+
*/
|
|
700
|
+
private extractFunctionReference(
|
|
701
|
+
expr: Parser.ExpressionContext,
|
|
702
|
+
): string | null {
|
|
703
|
+
const text = expr.getText();
|
|
704
|
+
if (/^\w+(\.\w+)?$/.test(text)) {
|
|
705
|
+
return text;
|
|
706
|
+
}
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
|
|
554
710
|
/**
|
|
555
711
|
* ADR-040: Register a variable that holds a callable (ISR or callback)
|
|
556
712
|
*/
|