c-next 0.2.17 → 0.2.18
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 +2 -2
- package/dist/index.js +7645 -5941
- package/dist/index.js.map +4 -4
- package/grammar/CNext.g4 +8 -0
- package/package.json +1 -3
- package/src/transpiler/Transpiler.ts +286 -26
- package/src/transpiler/__tests__/compileCommandsDiscovery.integration.test.ts +94 -0
- package/src/transpiler/__tests__/externalSymbolRecovery.integration.test.ts +215 -0
- package/src/transpiler/logic/__tests__/detectAssemblySyntax.test.ts +116 -0
- package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +8 -0
- package/src/transpiler/logic/analysis/MixedTypeCategoryAnalyzer.ts +408 -0
- package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +16 -97
- package/src/transpiler/logic/analysis/ReturnPathAnalyzer.ts +171 -0
- package/src/transpiler/logic/analysis/__tests__/MixedTypeCategoryAnalyzer.test.ts +346 -0
- package/src/transpiler/logic/analysis/__tests__/ReturnPathAnalyzer.test.ts +232 -0
- package/src/transpiler/logic/analysis/runAnalyzers.ts +23 -2
- package/src/transpiler/logic/analysis/types/IMixedTypeCategoryError.ts +17 -0
- package/src/transpiler/logic/analysis/types/IReturnPathError.ts +12 -0
- package/src/transpiler/logic/detectAssemblySyntax.ts +80 -0
- package/src/transpiler/logic/parser/grammar/CNext.interp +4 -1
- package/src/transpiler/logic/parser/grammar/CNext.tokens +169 -167
- package/src/transpiler/logic/parser/grammar/CNextLexer.interp +4 -1
- package/src/transpiler/logic/parser/grammar/CNextLexer.tokens +169 -167
- package/src/transpiler/logic/parser/grammar/CNextLexer.ts +545 -541
- package/src/transpiler/logic/parser/grammar/CNextListener.ts +11 -0
- package/src/transpiler/logic/parser/grammar/CNextParser.ts +1257 -1185
- package/src/transpiler/logic/parser/grammar/CNextVisitor.ts +7 -0
- package/src/transpiler/logic/preprocessor/CompileCommandsReader.ts +332 -0
- package/src/transpiler/logic/preprocessor/ExternalDeclarationOracle.ts +202 -0
- package/src/transpiler/logic/preprocessor/Preprocessor.ts +24 -6
- package/src/transpiler/logic/preprocessor/ToolchainDetector.ts +42 -1
- package/src/transpiler/logic/preprocessor/__tests__/CompileCommandsReader.test.ts +216 -0
- package/src/transpiler/logic/preprocessor/__tests__/ExternalDeclarationOracle.test.ts +153 -0
- package/src/transpiler/logic/preprocessor/__tests__/Preprocessor.test.ts +43 -18
- package/src/transpiler/logic/preprocessor/__tests__/ToolchainDetector.crossCompiler.test.ts +75 -0
- package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/dependent.h +7 -0
- package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/predecessor.h +5 -0
- package/src/transpiler/logic/preprocessor/types/ICompileCommandsResult.ts +14 -0
- package/src/transpiler/logic/preprocessor/types/IPreprocessOptions.ts +17 -0
- package/src/transpiler/logic/symbols/SymbolTable.ts +45 -0
- package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +49 -0
- package/src/transpiler/output/MisraSuppressionUtils.ts +52 -0
- package/src/transpiler/output/__tests__/MisraSuppressionUtils.test.ts +67 -0
- package/src/transpiler/output/codegen/CodeGenerator.ts +45 -4
- package/src/transpiler/output/codegen/TypeResolver.ts +148 -0
- package/src/transpiler/output/codegen/TypeValidator.ts +179 -81
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +13 -1
- package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +93 -0
- package/src/transpiler/output/codegen/__tests__/TypeValidator.alwaysTrueLoop.test.ts +91 -0
- package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +86 -14
- package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +13 -0
- package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +1 -1
- package/src/transpiler/output/codegen/assignment/handlers/AccessPatternHandlers.ts +20 -12
- package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +424 -22
- package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +31 -18
- package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +7 -8
- package/src/transpiler/output/codegen/assignment/handlers/RegisterHandlers.ts +6 -8
- package/src/transpiler/output/codegen/assignment/handlers/RegisterUtils.ts +12 -10
- package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +3 -7
- package/src/transpiler/output/codegen/assignment/handlers/SpecialHandlers.ts +7 -9
- package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +12 -10
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +479 -11
- package/src/transpiler/output/codegen/generators/IOrchestrator.ts +3 -0
- package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +28 -15
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +26 -13
- package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +129 -1
- package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +64 -8
- package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +8 -5
- package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +30 -2
- package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +16 -13
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +19 -0
- package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +57 -11
- package/src/transpiler/output/codegen/subscript/SubscriptClassifier.ts +30 -11
- package/src/transpiler/output/codegen/subscript/TSubscriptKind.ts +1 -1
- package/src/transpiler/output/codegen/subscript/__tests__/SubscriptClassifier.test.ts +38 -6
- package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +65 -24
- package/src/transpiler/state/CodeGenState.ts +20 -16
- package/src/transpiler/types/ICachedFileEntry.ts +7 -0
- package/src/utils/cache/CacheManager.ts +13 -2
- package/src/utils/cache/__tests__/CacheManager.test.ts +18 -1
- package/src/utils/constants/TypeConstants.ts +13 -0
|
@@ -772,6 +772,55 @@ describe("SymbolTable", () => {
|
|
|
772
772
|
expect(symbolTable.isOpaqueType("handle_t")).toBe(true);
|
|
773
773
|
expect(symbolTable.getAllOpaqueTypes()).toHaveLength(2);
|
|
774
774
|
});
|
|
775
|
+
|
|
776
|
+
it("clearStructTagHasBody restores opacity for a phantom body (#985)", () => {
|
|
777
|
+
symbolTable.markOpaqueType("obj_t");
|
|
778
|
+
symbolTable.registerStructTagAlias("_obj", "obj_t");
|
|
779
|
+
symbolTable.markStructTagHasBody("_obj"); // phantom body from a blob parse
|
|
780
|
+
expect(symbolTable.isOpaqueType("obj_t")).toBe(false);
|
|
781
|
+
|
|
782
|
+
symbolTable.clearStructTagHasBody("_obj");
|
|
783
|
+
expect(symbolTable.isOpaqueType("obj_t")).toBe(true);
|
|
784
|
+
expect(symbolTable.getAllStructTagsWithBodies()).not.toContain("_obj");
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
it("clearStructTagHasBody is a no-op when the tag has no body", () => {
|
|
788
|
+
// Must not throw or spuriously mutate when the tag was never marked.
|
|
789
|
+
expect(() => symbolTable.clearStructTagHasBody("_never")).not.toThrow();
|
|
790
|
+
expect(symbolTable.getAllStructTagsWithBodies()).not.toContain("_never");
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
it("getStructTagForTypedef returns the aliased tag or undefined", () => {
|
|
794
|
+
symbolTable.registerStructTagAlias("_lv_obj_t", "lv_obj_t");
|
|
795
|
+
expect(symbolTable.getStructTagForTypedef("lv_obj_t")).toBe("_lv_obj_t");
|
|
796
|
+
expect(symbolTable.getStructTagForTypedef("unknown_t")).toBeUndefined();
|
|
797
|
+
});
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
// ========================================================================
|
|
801
|
+
// External Declaration Names (Issue #985 macro recovery)
|
|
802
|
+
// ========================================================================
|
|
803
|
+
|
|
804
|
+
describe("External Declaration Names", () => {
|
|
805
|
+
it("registers and looks up recovered function-like macro names", () => {
|
|
806
|
+
expect(symbolTable.hasExternalDeclaration("pdMS_TO_TICKS")).toBe(false);
|
|
807
|
+
symbolTable.addExternalDeclarationNames(
|
|
808
|
+
new Set(["pdMS_TO_TICKS", "portTICK_PERIOD_MS"]),
|
|
809
|
+
);
|
|
810
|
+
expect(symbolTable.hasExternalDeclaration("pdMS_TO_TICKS")).toBe(true);
|
|
811
|
+
expect(symbolTable.hasExternalDeclaration("portTICK_PERIOD_MS")).toBe(
|
|
812
|
+
true,
|
|
813
|
+
);
|
|
814
|
+
expect(symbolTable.hasExternalDeclaration("not_recovered")).toBe(false);
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
it("accumulates across calls and tolerates an empty set", () => {
|
|
818
|
+
symbolTable.addExternalDeclarationNames(new Set(["A"]));
|
|
819
|
+
symbolTable.addExternalDeclarationNames(new Set());
|
|
820
|
+
symbolTable.addExternalDeclarationNames(new Set(["B"]));
|
|
821
|
+
expect(symbolTable.hasExternalDeclaration("A")).toBe(true);
|
|
822
|
+
expect(symbolTable.hasExternalDeclaration("B")).toBe(true);
|
|
823
|
+
});
|
|
775
824
|
});
|
|
776
825
|
|
|
777
826
|
// ========================================================================
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MISRA Suppression Utilities
|
|
3
|
+
*
|
|
4
|
+
* Issue #850: Shared helpers for emitting MISRA inline suppression comments.
|
|
5
|
+
* Used by both CodeGenerator (for .c files) and HeaderGeneratorUtils (for .h files).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Headers that violate MISRA C:2012 rules and need inline suppression.
|
|
10
|
+
* Maps header name to the MISRA rule it violates.
|
|
11
|
+
*/
|
|
12
|
+
const MISRA_BANNED_HEADERS: ReadonlyMap<string, string> = new Map([
|
|
13
|
+
// MISRA Rule 21.6: Standard library I/O functions shall not be used
|
|
14
|
+
["stdio.h", "misra-c2012-21.6"],
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Regex to extract header name from angle-bracket includes.
|
|
19
|
+
* Uses possessive matching via atomic group simulation to avoid backtracking.
|
|
20
|
+
* Matches: #include <header.h> -> captures "header.h"
|
|
21
|
+
*/
|
|
22
|
+
const ANGLE_BRACKET_INCLUDE_REGEX = /<([^<>]+)>/;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Check if an include directive needs MISRA suppression.
|
|
26
|
+
* @param includeText The full include directive (e.g., "#include <stdio.h>")
|
|
27
|
+
* @returns true if suppression is needed
|
|
28
|
+
*/
|
|
29
|
+
function needsMisraSuppression(includeText: string): boolean {
|
|
30
|
+
const match = ANGLE_BRACKET_INCLUDE_REGEX.exec(includeText);
|
|
31
|
+
if (!match) return false;
|
|
32
|
+
return MISRA_BANNED_HEADERS.has(match[1]);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get the MISRA suppression comment for an include directive.
|
|
37
|
+
* @param includeText The full include directive (e.g., "#include <stdio.h>")
|
|
38
|
+
* @returns The suppression comment, or null if not needed
|
|
39
|
+
*/
|
|
40
|
+
function getMisraSuppressionComment(includeText: string): string | null {
|
|
41
|
+
const match = ANGLE_BRACKET_INCLUDE_REGEX.exec(includeText);
|
|
42
|
+
if (!match) return null;
|
|
43
|
+
const rule = MISRA_BANNED_HEADERS.get(match[1]);
|
|
44
|
+
return rule ? `// cppcheck-suppress ${rule}` : null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const MisraSuppressionUtils = {
|
|
48
|
+
needsMisraSuppression,
|
|
49
|
+
getMisraSuppressionComment,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default MisraSuppressionUtils;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import MisraSuppressionUtils from "../MisraSuppressionUtils";
|
|
3
|
+
|
|
4
|
+
describe("MisraSuppressionUtils", () => {
|
|
5
|
+
describe("needsMisraSuppression", () => {
|
|
6
|
+
it("returns true for stdio.h", () => {
|
|
7
|
+
expect(
|
|
8
|
+
MisraSuppressionUtils.needsMisraSuppression("#include <stdio.h>"),
|
|
9
|
+
).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("returns false for other system headers", () => {
|
|
13
|
+
expect(
|
|
14
|
+
MisraSuppressionUtils.needsMisraSuppression("#include <stdint.h>"),
|
|
15
|
+
).toBe(false);
|
|
16
|
+
expect(
|
|
17
|
+
MisraSuppressionUtils.needsMisraSuppression("#include <string.h>"),
|
|
18
|
+
).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("returns false for quote includes", () => {
|
|
22
|
+
expect(
|
|
23
|
+
MisraSuppressionUtils.needsMisraSuppression('#include "stdio.h"'),
|
|
24
|
+
).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("returns false for non-include text", () => {
|
|
28
|
+
expect(MisraSuppressionUtils.needsMisraSuppression("void foo();")).toBe(
|
|
29
|
+
false,
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("getMisraSuppressionComment", () => {
|
|
35
|
+
it("returns suppression comment for stdio.h", () => {
|
|
36
|
+
expect(
|
|
37
|
+
MisraSuppressionUtils.getMisraSuppressionComment("#include <stdio.h>"),
|
|
38
|
+
).toBe("// cppcheck-suppress misra-c2012-21.6");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("returns null for other system headers", () => {
|
|
42
|
+
expect(
|
|
43
|
+
MisraSuppressionUtils.getMisraSuppressionComment("#include <stdint.h>"),
|
|
44
|
+
).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("returns null for quote includes", () => {
|
|
48
|
+
expect(
|
|
49
|
+
MisraSuppressionUtils.getMisraSuppressionComment('#include "stdio.h"'),
|
|
50
|
+
).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("returns null for non-include text", () => {
|
|
54
|
+
expect(
|
|
55
|
+
MisraSuppressionUtils.getMisraSuppressionComment("void foo();"),
|
|
56
|
+
).toBeNull();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("handles whitespace in include directives", () => {
|
|
60
|
+
expect(
|
|
61
|
+
MisraSuppressionUtils.getMisraSuppressionComment(
|
|
62
|
+
"#include <stdio.h> ",
|
|
63
|
+
),
|
|
64
|
+
).toBe("// cppcheck-suppress misra-c2012-21.6");
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -148,6 +148,7 @@ import EnumTypeResolver from "./resolution/EnumTypeResolver";
|
|
|
148
148
|
import ScopeResolver from "./resolution/ScopeResolver";
|
|
149
149
|
// Issue #797: Centralized C-style name generation
|
|
150
150
|
import QualifiedNameGenerator from "./utils/QualifiedNameGenerator";
|
|
151
|
+
import MisraSuppressionUtils from "../MisraSuppressionUtils";
|
|
151
152
|
|
|
152
153
|
const {
|
|
153
154
|
generateOverflowHelpers: helperGenerateOverflowHelpers,
|
|
@@ -299,6 +300,10 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
299
300
|
controlFlowGenerators.generateDoWhile,
|
|
300
301
|
);
|
|
301
302
|
this.registry.registerStatement("for", controlFlowGenerators.generateFor);
|
|
303
|
+
this.registry.registerStatement(
|
|
304
|
+
"forever",
|
|
305
|
+
controlFlowGenerators.generateForever,
|
|
306
|
+
);
|
|
302
307
|
this.registry.registerStatement("switch", switchGenerators.generateSwitch);
|
|
303
308
|
this.registry.registerStatement("critical", generateCriticalStatement);
|
|
304
309
|
|
|
@@ -1057,6 +1062,8 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
1057
1062
|
result = this.generateDoWhile(ctx.doWhileStatement()!);
|
|
1058
1063
|
} else if (ctx.forStatement()) {
|
|
1059
1064
|
result = this.generateFor(ctx.forStatement()!);
|
|
1065
|
+
} else if (ctx.foreverStatement()) {
|
|
1066
|
+
result = this.generateForever(ctx.foreverStatement()!);
|
|
1060
1067
|
} else if (ctx.switchStatement()) {
|
|
1061
1068
|
result = this.generateSwitch(ctx.switchStatement()!);
|
|
1062
1069
|
} else if (ctx.returnStatement()) {
|
|
@@ -1122,6 +1129,14 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
1122
1129
|
TypeValidator.validateConditionIsBoolean(ctx, conditionType);
|
|
1123
1130
|
}
|
|
1124
1131
|
|
|
1132
|
+
/**
|
|
1133
|
+
* ADR-113 / #1075: reject an always-true literal loop condition (E0707).
|
|
1134
|
+
* Part of IOrchestrator interface.
|
|
1135
|
+
*/
|
|
1136
|
+
validateLoopConditionNotAlwaysTrue(ctx: Parser.ExpressionContext): void {
|
|
1137
|
+
TypeValidator.validateLoopConditionNotAlwaysTrue(ctx);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1125
1140
|
/**
|
|
1126
1141
|
* Issue #254: Validate no function calls in condition (E0702).
|
|
1127
1142
|
* Part of IOrchestrator interface (ADR-053 A3).
|
|
@@ -1164,8 +1179,6 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
1164
1179
|
|
|
1165
1180
|
// Issue #779: Resolve bare scope member identifiers before postfix chain processing
|
|
1166
1181
|
// This ensures scope members get their prefix even with array/member access.
|
|
1167
|
-
// Skip parameters - they don't need scope resolution and shouldn't be dereferenced
|
|
1168
|
-
// when used with array indexing (buf[idx] is valid C for pointer params).
|
|
1169
1182
|
// Also skip known registers - they should be handled by the postfix chain builder
|
|
1170
1183
|
// to enable proper register validation (requiring global. when shadowed).
|
|
1171
1184
|
let resolvedIdentifier = identifier ?? "";
|
|
@@ -1174,7 +1187,24 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
1174
1187
|
const isLocalVariable = CodeGenState.localVariables.has(identifier);
|
|
1175
1188
|
const isKnownRegister =
|
|
1176
1189
|
CodeGenState.symbols?.knownRegisters.has(identifier);
|
|
1177
|
-
|
|
1190
|
+
// Issue #1100: Parameters with postfix ops (array/bit subscript, member
|
|
1191
|
+
// access) must resolve through the same dereference logic as a bare
|
|
1192
|
+
// parameter reference (ParameterDereferenceResolver), not skip it.
|
|
1193
|
+
// For array/struct/string/etc. parameters this is a no-op (they're
|
|
1194
|
+
// already pointer-like, matching the prior behavior verbatim — e.g.
|
|
1195
|
+
// `buf[idx]` stays `buf[idx]`). For a scalar parameter that became a
|
|
1196
|
+
// pointer because it's modified elsewhere in the function, a bit
|
|
1197
|
+
// access (`v[4] <- true`) now correctly dereferences to `(*v)[4]`
|
|
1198
|
+
// (which AssignmentContextBuilder reduces to base identifier `(*v)`)
|
|
1199
|
+
// instead of assigning through the raw pointer.
|
|
1200
|
+
if (isParameter) {
|
|
1201
|
+
const paramInfo = CodeGenState.currentParameters.get(identifier)!;
|
|
1202
|
+
resolvedIdentifier = ParameterDereferenceResolver.resolve(
|
|
1203
|
+
identifier,
|
|
1204
|
+
paramInfo,
|
|
1205
|
+
this._buildParameterDereferenceDeps(),
|
|
1206
|
+
);
|
|
1207
|
+
} else if (!isLocalVariable && !isKnownRegister) {
|
|
1178
1208
|
const resolved = TypeValidator.resolveBareIdentifier(
|
|
1179
1209
|
identifier,
|
|
1180
1210
|
false, // not local
|
|
@@ -2362,7 +2392,14 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
2362
2392
|
includePaths,
|
|
2363
2393
|
);
|
|
2364
2394
|
|
|
2365
|
-
|
|
2395
|
+
// Issue #850: Add MISRA suppression for banned headers
|
|
2396
|
+
const includeText = includeDir.getText();
|
|
2397
|
+
const suppression =
|
|
2398
|
+
MisraSuppressionUtils.getMisraSuppressionComment(includeText);
|
|
2399
|
+
if (suppression) {
|
|
2400
|
+
output.push(suppression);
|
|
2401
|
+
}
|
|
2402
|
+
output.push(this.transformIncludeDirective(includeText));
|
|
2366
2403
|
}
|
|
2367
2404
|
|
|
2368
2405
|
if (tree.includeDirective().length > 0) {
|
|
@@ -4519,6 +4556,10 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
4519
4556
|
return this.invokeStatement("for", ctx);
|
|
4520
4557
|
}
|
|
4521
4558
|
|
|
4559
|
+
private generateForever(ctx: Parser.ForeverStatementContext): string {
|
|
4560
|
+
return this.invokeStatement("forever", ctx);
|
|
4561
|
+
}
|
|
4562
|
+
|
|
4522
4563
|
private generateReturn(ctx: Parser.ReturnStatementContext): string {
|
|
4523
4564
|
return this.invokeStatement("return", ctx);
|
|
4524
4565
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* TypeResolver - Handles type inference, classification, and validation
|
|
3
3
|
* Static class that reads from CodeGenState directly.
|
|
4
4
|
*/
|
|
5
|
+
import { ParserRuleContext } from "antlr4ng";
|
|
5
6
|
import * as Parser from "../../logic/parser/grammar/CNextParser";
|
|
6
7
|
import CodeGenState from "../../state/CodeGenState";
|
|
7
8
|
import INTEGER_TYPES from "./types/INTEGER_TYPES";
|
|
@@ -230,6 +231,153 @@ class TypeResolver {
|
|
|
230
231
|
return null;
|
|
231
232
|
}
|
|
232
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Resolve the C-Next integer type of an expression, including composite
|
|
236
|
+
* arithmetic/bitwise expressions that `getExpressionType` leaves unresolved.
|
|
237
|
+
*
|
|
238
|
+
* MISRA C:2012 Rule 10.4 (enforced by MixedTypeCategoryAnalyzer) guarantees a
|
|
239
|
+
* binary operator's operands share an essential type category, so a composite
|
|
240
|
+
* integer expression's category is uniform; its essential width is the widest
|
|
241
|
+
* integer operand. This is what lets slice-assignment serialize an arithmetic
|
|
242
|
+
* source (e.g. `a + b`) MISRA Rule 10.8-clean instead of guessing a width.
|
|
243
|
+
*
|
|
244
|
+
* Returns null when no integer-typed variable leaf can be resolved (e.g. a
|
|
245
|
+
* struct-field or function-call composite — left for a later pass).
|
|
246
|
+
*/
|
|
247
|
+
static getIntegerExpressionType(
|
|
248
|
+
ctx: Parser.ExpressionContext,
|
|
249
|
+
): string | null {
|
|
250
|
+
const direct = TypeResolver.getExpressionType(ctx);
|
|
251
|
+
if (direct !== null) return direct;
|
|
252
|
+
return TypeResolver.resolveCompositeIntegerType(ctx);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Combine the leaf VALUE operands of a composite expression into a single
|
|
257
|
+
* C-Next type: the (uniform, per Rule 10.4) category at the widest width.
|
|
258
|
+
*
|
|
259
|
+
* Operands are typed by their value (Issue #1085 review) — an array index
|
|
260
|
+
* (`arr[i]`), bit offset (`x[off, w]`) or struct member name is NOT a value
|
|
261
|
+
* operand and must not contribute to the width. A bit-extraction contributes
|
|
262
|
+
* its EXTRACTED width, not the variable's full width (typing `a + b[0, 32]`
|
|
263
|
+
* as u64 would cast the composite to a wider type — MISRA Rule 10.8).
|
|
264
|
+
*/
|
|
265
|
+
private static resolveCompositeIntegerType(
|
|
266
|
+
ctx: Parser.ExpressionContext,
|
|
267
|
+
): string | null {
|
|
268
|
+
let category: "i" | "u" | null = null;
|
|
269
|
+
let width = 0;
|
|
270
|
+
|
|
271
|
+
for (const operand of TypeResolver.collectOperandPostfixes(ctx)) {
|
|
272
|
+
const operandType = TypeResolver.typeOperandPostfix(operand);
|
|
273
|
+
const match = operandType
|
|
274
|
+
? /^([iu])(8|16|32|64)$/.exec(operandType)
|
|
275
|
+
: null;
|
|
276
|
+
if (!match) continue;
|
|
277
|
+
category ??= match[1] as "i" | "u";
|
|
278
|
+
width = Math.max(width, Number.parseInt(match[2], 10));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return category && width > 0 ? `${category}${width}` : null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Type one leaf operand of a composite by its VALUE type. A bit-extraction
|
|
286
|
+
* `x[start, width]` yields an unsigned value of the extracted width; a simple
|
|
287
|
+
* function call `name(...)` yields its declared return type; everything else
|
|
288
|
+
* (variable, array element, struct field, member chain) defers to
|
|
289
|
+
* getPostfixExpressionType. Returns null for an operand it cannot classify
|
|
290
|
+
* (e.g. a literal, which is contextually typed).
|
|
291
|
+
*/
|
|
292
|
+
private static typeOperandPostfix(
|
|
293
|
+
postfix: Parser.PostfixExpressionContext,
|
|
294
|
+
): string | null {
|
|
295
|
+
const extractionWidth = TypeResolver.bitExtractionWidth(postfix);
|
|
296
|
+
if (extractionWidth !== null) {
|
|
297
|
+
return TypeResolver.unsignedTypeForBits(extractionWidth);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const direct = TypeResolver.getPostfixExpressionType(postfix);
|
|
301
|
+
if (direct !== null) return direct;
|
|
302
|
+
|
|
303
|
+
return TypeResolver.callReturnType(postfix);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Collect the leaf operand postfix expressions of a composite WITHOUT
|
|
308
|
+
* descending into a postfix's own internals — so an array index (`arr[i]`) or
|
|
309
|
+
* bit offset (`x[off, w]`) variable is never mistaken for a value operand.
|
|
310
|
+
*/
|
|
311
|
+
private static collectOperandPostfixes(
|
|
312
|
+
node: ParserRuleContext,
|
|
313
|
+
): Parser.PostfixExpressionContext[] {
|
|
314
|
+
if (node instanceof Parser.PostfixExpressionContext) return [node];
|
|
315
|
+
const operands: Parser.PostfixExpressionContext[] = [];
|
|
316
|
+
for (let i = 0; i < node.getChildCount(); i += 1) {
|
|
317
|
+
const child = node.getChild(i);
|
|
318
|
+
if (child instanceof ParserRuleContext) {
|
|
319
|
+
operands.push(...TypeResolver.collectOperandPostfixes(child));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return operands;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* If a postfix expression's terminal suffix is a bit-range extraction
|
|
327
|
+
* `[start, width]` with a compile-time-constant width, return that width in
|
|
328
|
+
* bits; else null.
|
|
329
|
+
*/
|
|
330
|
+
private static bitExtractionWidth(
|
|
331
|
+
postfix: Parser.PostfixExpressionContext,
|
|
332
|
+
): number | null {
|
|
333
|
+
const ops = postfix.postfixOp();
|
|
334
|
+
const last = ops.at(-1);
|
|
335
|
+
if (last?.expression().length !== 2) return null;
|
|
336
|
+
const widthExpr = last.expression()[1];
|
|
337
|
+
|
|
338
|
+
// Resolve the width through the constant evaluator — the same path the slice
|
|
339
|
+
// offset/length use — so a named const or any-base literal width
|
|
340
|
+
// (`b[0, WIDTH]`, `b[0, 0b100000]`) is sized at its real width rather than
|
|
341
|
+
// dropped, which would mis-type a composite slice source (Issue #1085 review).
|
|
342
|
+
const evaluated = CodeGenState.generator?.tryEvaluateConstant(widthExpr);
|
|
343
|
+
if (evaluated !== undefined) {
|
|
344
|
+
return evaluated > 0 ? evaluated : null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Fallback for contexts with no generator (e.g. isolated unit tests): accept
|
|
348
|
+
// a plain decimal/hex literal width directly.
|
|
349
|
+
const widthText = widthExpr.getText();
|
|
350
|
+
if (!/^(0x[0-9a-fA-F]+|\d+)$/.test(widthText)) return null;
|
|
351
|
+
const value = Number.parseInt(
|
|
352
|
+
widthText,
|
|
353
|
+
widthText.startsWith("0x") ? 16 : 10,
|
|
354
|
+
);
|
|
355
|
+
return Number.isNaN(value) || value <= 0 ? null : value;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** Smallest standard unsigned C-Next type holding `bits` bits, or null if >64. */
|
|
359
|
+
private static unsignedTypeForBits(bits: number): string | null {
|
|
360
|
+
if (bits <= 8) return "u8";
|
|
361
|
+
if (bits <= 16) return "u16";
|
|
362
|
+
if (bits <= 32) return "u32";
|
|
363
|
+
if (bits <= 64) return "u64";
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* If a postfix expression is a simple function call `name(...)`, return the
|
|
369
|
+
* function's declared return type — a call operand's width comes from its
|
|
370
|
+
* return type, not from being ignored (Issue #1085 review).
|
|
371
|
+
*/
|
|
372
|
+
private static callReturnType(
|
|
373
|
+
postfix: Parser.PostfixExpressionContext,
|
|
374
|
+
): string | null {
|
|
375
|
+
const ops = postfix.postfixOp();
|
|
376
|
+
if (ops.length !== 1 || !ops[0].getText().startsWith("(")) return null;
|
|
377
|
+
const name = postfix.primaryExpression()?.IDENTIFIER()?.getText();
|
|
378
|
+
return name ? (CodeGenState.getFunctionReturnType(name) ?? null) : null;
|
|
379
|
+
}
|
|
380
|
+
|
|
233
381
|
/**
|
|
234
382
|
* ADR-024: Get the type of a postfix expression.
|
|
235
383
|
* Tracks InternalTypeInfo (baseType + isArray) through the suffix chain
|