c-next 0.2.3 → 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 +537 -170
- 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/output/codegen/CodeGenerator.ts +31 -5
- 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/__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 +9 -1
- 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
|
@@ -20,7 +20,9 @@ describe("CodeGenerator requireInclude", () => {
|
|
|
20
20
|
it("includes stdint.h for u8 type", async () => {
|
|
21
21
|
const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
|
|
22
22
|
|
|
23
|
-
const result =
|
|
23
|
+
const result = (
|
|
24
|
+
await transpiler.transpile({ kind: "source", source: "u8 value <- 0;" })
|
|
25
|
+
).files[0];
|
|
24
26
|
|
|
25
27
|
expect(result.success).toBe(true);
|
|
26
28
|
expect(result.code).toContain("#include <stdint.h>");
|
|
@@ -29,7 +31,12 @@ describe("CodeGenerator requireInclude", () => {
|
|
|
29
31
|
it("includes stdint.h for u16 type", async () => {
|
|
30
32
|
const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
|
|
31
33
|
|
|
32
|
-
const result =
|
|
34
|
+
const result = (
|
|
35
|
+
await transpiler.transpile({
|
|
36
|
+
kind: "source",
|
|
37
|
+
source: "u16 value <- 0;",
|
|
38
|
+
})
|
|
39
|
+
).files[0];
|
|
33
40
|
|
|
34
41
|
expect(result.success).toBe(true);
|
|
35
42
|
expect(result.code).toContain("#include <stdint.h>");
|
|
@@ -38,7 +45,12 @@ describe("CodeGenerator requireInclude", () => {
|
|
|
38
45
|
it("includes stdint.h for u32 type", async () => {
|
|
39
46
|
const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
|
|
40
47
|
|
|
41
|
-
const result =
|
|
48
|
+
const result = (
|
|
49
|
+
await transpiler.transpile({
|
|
50
|
+
kind: "source",
|
|
51
|
+
source: "u32 value <- 0;",
|
|
52
|
+
})
|
|
53
|
+
).files[0];
|
|
42
54
|
|
|
43
55
|
expect(result.success).toBe(true);
|
|
44
56
|
expect(result.code).toContain("#include <stdint.h>");
|
|
@@ -47,7 +59,12 @@ describe("CodeGenerator requireInclude", () => {
|
|
|
47
59
|
it("includes stdint.h for i32 type", async () => {
|
|
48
60
|
const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
|
|
49
61
|
|
|
50
|
-
const result =
|
|
62
|
+
const result = (
|
|
63
|
+
await transpiler.transpile({
|
|
64
|
+
kind: "source",
|
|
65
|
+
source: "i32 value <- 0;",
|
|
66
|
+
})
|
|
67
|
+
).files[0];
|
|
51
68
|
|
|
52
69
|
expect(result.success).toBe(true);
|
|
53
70
|
expect(result.code).toContain("#include <stdint.h>");
|
|
@@ -56,14 +73,19 @@ describe("CodeGenerator requireInclude", () => {
|
|
|
56
73
|
it("includes stdint.h for bitmap types", async () => {
|
|
57
74
|
const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
|
|
58
75
|
|
|
59
|
-
const result =
|
|
76
|
+
const result = (
|
|
77
|
+
await transpiler.transpile({
|
|
78
|
+
kind: "source",
|
|
79
|
+
source: `
|
|
60
80
|
bitmap8 Flags {
|
|
61
81
|
enabled,
|
|
62
82
|
active,
|
|
63
83
|
reserved[6]
|
|
64
84
|
}
|
|
65
85
|
Flags f <- 0;
|
|
66
|
-
|
|
86
|
+
`,
|
|
87
|
+
})
|
|
88
|
+
).files[0];
|
|
67
89
|
|
|
68
90
|
expect(result.success).toBe(true);
|
|
69
91
|
expect(result.code).toContain("#include <stdint.h>");
|
|
@@ -74,7 +96,12 @@ describe("CodeGenerator requireInclude", () => {
|
|
|
74
96
|
it("includes stdbool.h for bool type", async () => {
|
|
75
97
|
const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
|
|
76
98
|
|
|
77
|
-
const result =
|
|
99
|
+
const result = (
|
|
100
|
+
await transpiler.transpile({
|
|
101
|
+
kind: "source",
|
|
102
|
+
source: "bool flag <- false;",
|
|
103
|
+
})
|
|
104
|
+
).files[0];
|
|
78
105
|
|
|
79
106
|
expect(result.success).toBe(true);
|
|
80
107
|
expect(result.code).toContain("#include <stdbool.h>");
|
|
@@ -85,9 +112,12 @@ describe("CodeGenerator requireInclude", () => {
|
|
|
85
112
|
it("includes string.h for bounded string type", async () => {
|
|
86
113
|
const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
|
|
87
114
|
|
|
88
|
-
const result =
|
|
89
|
-
|
|
90
|
-
|
|
115
|
+
const result = (
|
|
116
|
+
await transpiler.transpile({
|
|
117
|
+
kind: "source",
|
|
118
|
+
source: 'string<32> name <- "test";',
|
|
119
|
+
})
|
|
120
|
+
).files[0];
|
|
91
121
|
|
|
92
122
|
expect(result.success).toBe(true);
|
|
93
123
|
expect(result.code).toContain("#include <string.h>");
|
|
@@ -96,9 +126,12 @@ describe("CodeGenerator requireInclude", () => {
|
|
|
96
126
|
it("includes string.h for const string inference", async () => {
|
|
97
127
|
const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
|
|
98
128
|
|
|
99
|
-
const result =
|
|
100
|
-
|
|
101
|
-
|
|
129
|
+
const result = (
|
|
130
|
+
await transpiler.transpile({
|
|
131
|
+
kind: "source",
|
|
132
|
+
source: 'const string message <- "hello";',
|
|
133
|
+
})
|
|
134
|
+
).files[0];
|
|
102
135
|
|
|
103
136
|
expect(result.success).toBe(true);
|
|
104
137
|
expect(result.code).toContain("#include <string.h>");
|
|
@@ -109,7 +142,12 @@ describe("CodeGenerator requireInclude", () => {
|
|
|
109
142
|
it("generates ISR typedef for ISR type", async () => {
|
|
110
143
|
const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
|
|
111
144
|
|
|
112
|
-
const result =
|
|
145
|
+
const result = (
|
|
146
|
+
await transpiler.transpile({
|
|
147
|
+
kind: "source",
|
|
148
|
+
source: "ISR handler <- null;",
|
|
149
|
+
})
|
|
150
|
+
).files[0];
|
|
113
151
|
|
|
114
152
|
expect(result.success).toBe(true);
|
|
115
153
|
expect(result.code).toContain("typedef void (*ISR)(void)");
|
|
@@ -120,13 +158,18 @@ describe("CodeGenerator requireInclude", () => {
|
|
|
120
158
|
it("generates static assert for float bit indexing write", async () => {
|
|
121
159
|
const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
|
|
122
160
|
|
|
123
|
-
const result =
|
|
161
|
+
const result = (
|
|
162
|
+
await transpiler.transpile({
|
|
163
|
+
kind: "source",
|
|
164
|
+
source: `
|
|
124
165
|
f32 setByte(u8 b) {
|
|
125
166
|
f32 value <- 0.0;
|
|
126
167
|
value[0, 8] <- b;
|
|
127
168
|
return value;
|
|
128
169
|
}
|
|
129
|
-
|
|
170
|
+
`,
|
|
171
|
+
})
|
|
172
|
+
).files[0];
|
|
130
173
|
|
|
131
174
|
expect(result.success).toBe(true);
|
|
132
175
|
expect(result.code).toContain("_Static_assert");
|
|
@@ -136,12 +179,17 @@ describe("CodeGenerator requireInclude", () => {
|
|
|
136
179
|
it("generates static assert for float bit indexing read (no string.h)", async () => {
|
|
137
180
|
const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
|
|
138
181
|
|
|
139
|
-
const result =
|
|
182
|
+
const result = (
|
|
183
|
+
await transpiler.transpile({
|
|
184
|
+
kind: "source",
|
|
185
|
+
source: `
|
|
140
186
|
u8 getByte() {
|
|
141
187
|
f32 value <- 1.0;
|
|
142
188
|
return value[0, 8];
|
|
143
189
|
}
|
|
144
|
-
|
|
190
|
+
`,
|
|
191
|
+
})
|
|
192
|
+
).files[0];
|
|
145
193
|
|
|
146
194
|
expect(result.success).toBe(true);
|
|
147
195
|
expect(result.code).toContain("_Static_assert");
|
|
@@ -155,11 +203,16 @@ describe("CodeGenerator requireInclude", () => {
|
|
|
155
203
|
it("includes limits.h for float-to-int clamp cast", async () => {
|
|
156
204
|
const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
|
|
157
205
|
|
|
158
|
-
const result =
|
|
206
|
+
const result = (
|
|
207
|
+
await transpiler.transpile({
|
|
208
|
+
kind: "source",
|
|
209
|
+
source: `
|
|
159
210
|
i32 convert(f32 value) {
|
|
160
211
|
return (i32)value;
|
|
161
212
|
}
|
|
162
|
-
|
|
213
|
+
`,
|
|
214
|
+
})
|
|
215
|
+
).files[0];
|
|
163
216
|
|
|
164
217
|
expect(result.success).toBe(true);
|
|
165
218
|
expect(result.code).toContain("#include <limits.h>");
|
|
@@ -170,11 +223,16 @@ describe("CodeGenerator requireInclude", () => {
|
|
|
170
223
|
it("includes multiple headers when needed", async () => {
|
|
171
224
|
const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
|
|
172
225
|
|
|
173
|
-
const result =
|
|
226
|
+
const result = (
|
|
227
|
+
await transpiler.transpile({
|
|
228
|
+
kind: "source",
|
|
229
|
+
source: `
|
|
174
230
|
bool check(u32 value, string<16> name) {
|
|
175
231
|
return value > 0;
|
|
176
232
|
}
|
|
177
|
-
|
|
233
|
+
`,
|
|
234
|
+
})
|
|
235
|
+
).files[0];
|
|
178
236
|
|
|
179
237
|
expect(result.success).toBe(true);
|
|
180
238
|
expect(result.code).toContain("#include <stdint.h>");
|
|
@@ -185,7 +243,12 @@ describe("CodeGenerator requireInclude", () => {
|
|
|
185
243
|
it("does not include unused headers", async () => {
|
|
186
244
|
const transpiler = new Transpiler({ inputs: [], noCache: true }, mockFs);
|
|
187
245
|
|
|
188
|
-
const result =
|
|
246
|
+
const result = (
|
|
247
|
+
await transpiler.transpile({
|
|
248
|
+
kind: "source",
|
|
249
|
+
source: "void doNothing() { }",
|
|
250
|
+
})
|
|
251
|
+
).files[0];
|
|
189
252
|
|
|
190
253
|
expect(result.success).toBe(true);
|
|
191
254
|
expect(result.code).not.toContain("#include <stdint.h>");
|
|
@@ -12,7 +12,9 @@ import Transpiler from "../../../Transpiler";
|
|
|
12
12
|
*/
|
|
13
13
|
async function transpileSource(source: string): Promise<string> {
|
|
14
14
|
const transpiler = new Transpiler({ inputs: [] });
|
|
15
|
-
const result =
|
|
15
|
+
const result = (
|
|
16
|
+
await transpiler.transpile({ kind: "source", source: source })
|
|
17
|
+
).files[0];
|
|
16
18
|
if (result.errors && result.errors.length > 0) {
|
|
17
19
|
throw new Error(
|
|
18
20
|
`Transpile failed: ${result.errors.map((e) => e.message).join(", ")}`,
|
|
@@ -1820,7 +1820,7 @@ describe("TypeValidator", () => {
|
|
|
1820
1820
|
// Tests - Do-While Validation (ADR-027)
|
|
1821
1821
|
// ========================================================================
|
|
1822
1822
|
|
|
1823
|
-
describe("
|
|
1823
|
+
describe("validateConditionIsBoolean", () => {
|
|
1824
1824
|
function createFullDoWhileExpression(
|
|
1825
1825
|
text: string,
|
|
1826
1826
|
options?: {
|
|
@@ -1874,36 +1874,44 @@ describe("TypeValidator", () => {
|
|
|
1874
1874
|
const ctx = createFullDoWhileExpression("x < 10", {
|
|
1875
1875
|
hasRelational: true,
|
|
1876
1876
|
});
|
|
1877
|
-
expect(() =>
|
|
1877
|
+
expect(() =>
|
|
1878
|
+
TypeValidator.validateConditionIsBoolean(ctx, "do-while"),
|
|
1879
|
+
).not.toThrow();
|
|
1878
1880
|
});
|
|
1879
1881
|
|
|
1880
1882
|
it("allows conditions with equality operators", () => {
|
|
1881
1883
|
setupState();
|
|
1882
1884
|
const ctx = createFullDoWhileExpression("x = 0", { hasEquality: true });
|
|
1883
|
-
expect(() =>
|
|
1885
|
+
expect(() =>
|
|
1886
|
+
TypeValidator.validateConditionIsBoolean(ctx, "do-while"),
|
|
1887
|
+
).not.toThrow();
|
|
1884
1888
|
});
|
|
1885
1889
|
|
|
1886
1890
|
it("allows conditions with && operator", () => {
|
|
1887
1891
|
setupState();
|
|
1888
1892
|
const ctx = createFullDoWhileExpression("a && b", { hasAnd: true });
|
|
1889
|
-
expect(() =>
|
|
1893
|
+
expect(() =>
|
|
1894
|
+
TypeValidator.validateConditionIsBoolean(ctx, "do-while"),
|
|
1895
|
+
).not.toThrow();
|
|
1890
1896
|
});
|
|
1891
1897
|
|
|
1892
1898
|
it("allows conditions with || operator", () => {
|
|
1893
1899
|
setupState();
|
|
1894
1900
|
const ctx = createFullDoWhileExpression("a || b", { hasOr: true });
|
|
1895
|
-
expect(() =>
|
|
1901
|
+
expect(() =>
|
|
1902
|
+
TypeValidator.validateConditionIsBoolean(ctx, "do-while"),
|
|
1903
|
+
).not.toThrow();
|
|
1896
1904
|
});
|
|
1897
1905
|
|
|
1898
1906
|
it("throws for bare value condition", () => {
|
|
1899
1907
|
setupState();
|
|
1900
1908
|
const ctx = createFullDoWhileExpression("count");
|
|
1901
|
-
expect(() =>
|
|
1902
|
-
"
|
|
1903
|
-
);
|
|
1904
|
-
expect(() =>
|
|
1905
|
-
"do-while
|
|
1906
|
-
);
|
|
1909
|
+
expect(() =>
|
|
1910
|
+
TypeValidator.validateConditionIsBoolean(ctx, "do-while"),
|
|
1911
|
+
).toThrow("E0701");
|
|
1912
|
+
expect(() =>
|
|
1913
|
+
TypeValidator.validateConditionIsBoolean(ctx, "do-while"),
|
|
1914
|
+
).toThrow("do-while condition must be a boolean expression");
|
|
1907
1915
|
});
|
|
1908
1916
|
|
|
1909
1917
|
it("throws for ternary in do-while condition", () => {
|
|
@@ -1914,9 +1922,9 @@ describe("TypeValidator", () => {
|
|
|
1914
1922
|
orExpression: () => [{}, {}], // Multiple orExpressions = ternary
|
|
1915
1923
|
}),
|
|
1916
1924
|
} as unknown as Parser.ExpressionContext;
|
|
1917
|
-
expect(() =>
|
|
1918
|
-
"
|
|
1919
|
-
);
|
|
1925
|
+
expect(() =>
|
|
1926
|
+
TypeValidator.validateConditionIsBoolean(ctx, "do-while"),
|
|
1927
|
+
).toThrow("E0701");
|
|
1920
1928
|
});
|
|
1921
1929
|
|
|
1922
1930
|
it("allows boolean literals", () => {
|
|
@@ -1948,7 +1956,9 @@ describe("TypeValidator", () => {
|
|
|
1948
1956
|
orExpression: antlrArray([orExpr]),
|
|
1949
1957
|
}),
|
|
1950
1958
|
} as unknown as Parser.ExpressionContext;
|
|
1951
|
-
expect(() =>
|
|
1959
|
+
expect(() =>
|
|
1960
|
+
TypeValidator.validateConditionIsBoolean(ctx, "do-while"),
|
|
1961
|
+
).not.toThrow();
|
|
1952
1962
|
});
|
|
1953
1963
|
|
|
1954
1964
|
it("allows negation expressions", () => {
|
|
@@ -1979,7 +1989,9 @@ describe("TypeValidator", () => {
|
|
|
1979
1989
|
orExpression: antlrArray([orExpr]),
|
|
1980
1990
|
}),
|
|
1981
1991
|
} as unknown as Parser.ExpressionContext;
|
|
1982
|
-
expect(() =>
|
|
1992
|
+
expect(() =>
|
|
1993
|
+
TypeValidator.validateConditionIsBoolean(ctx, "do-while"),
|
|
1994
|
+
).not.toThrow();
|
|
1983
1995
|
});
|
|
1984
1996
|
|
|
1985
1997
|
it("allows bool type variables", () => {
|
|
@@ -2016,15 +2028,17 @@ describe("TypeValidator", () => {
|
|
|
2016
2028
|
orExpression: antlrArray([orExpr]),
|
|
2017
2029
|
}),
|
|
2018
2030
|
} as unknown as Parser.ExpressionContext;
|
|
2019
|
-
expect(() =>
|
|
2031
|
+
expect(() =>
|
|
2032
|
+
TypeValidator.validateConditionIsBoolean(ctx, "do-while"),
|
|
2033
|
+
).not.toThrow();
|
|
2020
2034
|
});
|
|
2021
2035
|
|
|
2022
2036
|
it("shows help message in error", () => {
|
|
2023
2037
|
setupState();
|
|
2024
2038
|
const ctx = createFullDoWhileExpression("count");
|
|
2025
|
-
expect(() =>
|
|
2026
|
-
|
|
2027
|
-
);
|
|
2039
|
+
expect(() =>
|
|
2040
|
+
TypeValidator.validateConditionIsBoolean(ctx, "do-while"),
|
|
2041
|
+
).toThrow("help: use explicit comparison: count > 0 or count != 0");
|
|
2028
2042
|
});
|
|
2029
2043
|
|
|
2030
2044
|
it("throws when no andExpression", () => {
|
|
@@ -2039,9 +2053,9 @@ describe("TypeValidator", () => {
|
|
|
2039
2053
|
orExpression: antlrArray([orExpr]),
|
|
2040
2054
|
}),
|
|
2041
2055
|
} as unknown as Parser.ExpressionContext;
|
|
2042
|
-
expect(() =>
|
|
2043
|
-
"
|
|
2044
|
-
);
|
|
2056
|
+
expect(() =>
|
|
2057
|
+
TypeValidator.validateConditionIsBoolean(ctx, "do-while"),
|
|
2058
|
+
).toThrow("E0701");
|
|
2045
2059
|
});
|
|
2046
2060
|
|
|
2047
2061
|
it("throws when no equalityExpression", () => {
|
|
@@ -2059,9 +2073,9 @@ describe("TypeValidator", () => {
|
|
|
2059
2073
|
orExpression: antlrArray([orExpr]),
|
|
2060
2074
|
}),
|
|
2061
2075
|
} as unknown as Parser.ExpressionContext;
|
|
2062
|
-
expect(() =>
|
|
2063
|
-
"
|
|
2064
|
-
);
|
|
2076
|
+
expect(() =>
|
|
2077
|
+
TypeValidator.validateConditionIsBoolean(ctx, "do-while"),
|
|
2078
|
+
).toThrow("E0701");
|
|
2065
2079
|
});
|
|
2066
2080
|
|
|
2067
2081
|
it("throws when no relationalExpression", () => {
|
|
@@ -2082,9 +2096,9 @@ describe("TypeValidator", () => {
|
|
|
2082
2096
|
orExpression: antlrArray([orExpr]),
|
|
2083
2097
|
}),
|
|
2084
2098
|
} as unknown as Parser.ExpressionContext;
|
|
2085
|
-
expect(() =>
|
|
2086
|
-
"
|
|
2087
|
-
);
|
|
2099
|
+
expect(() =>
|
|
2100
|
+
TypeValidator.validateConditionIsBoolean(ctx, "do-while"),
|
|
2101
|
+
).toThrow("E0701");
|
|
2088
2102
|
});
|
|
2089
2103
|
});
|
|
2090
2104
|
|
|
@@ -208,8 +208,11 @@ interface IOrchestrator {
|
|
|
208
208
|
switchExpr: Parser.ExpressionContext,
|
|
209
209
|
): void;
|
|
210
210
|
|
|
211
|
-
/** Validate
|
|
212
|
-
|
|
211
|
+
/** Validate condition is a boolean expression (ADR-027, Issue #884) */
|
|
212
|
+
validateConditionIsBoolean(
|
|
213
|
+
ctx: Parser.ExpressionContext,
|
|
214
|
+
conditionType: string,
|
|
215
|
+
): void;
|
|
213
216
|
|
|
214
217
|
/** Validate no function calls in condition (Issue #254, E0702) */
|
|
215
218
|
validateConditionNoFunctionCall(
|
|
@@ -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)", () => {
|
|
@@ -149,12 +149,20 @@ class FunctionContextManager {
|
|
|
149
149
|
callbacks,
|
|
150
150
|
);
|
|
151
151
|
|
|
152
|
+
// For callback-compatible functions, struct params use by-value semantics
|
|
153
|
+
// (no pointer dereference, dot access instead of arrow in C mode)
|
|
154
|
+
const isCallbackCompat =
|
|
155
|
+
CodeGenState.currentFunctionName !== null &&
|
|
156
|
+
CodeGenState.callbackCompatibleFunctions.has(
|
|
157
|
+
CodeGenState.currentFunctionName,
|
|
158
|
+
);
|
|
159
|
+
|
|
152
160
|
// Register in currentParameters
|
|
153
161
|
const paramInfo = {
|
|
154
162
|
name,
|
|
155
163
|
baseType: typeInfo.typeName,
|
|
156
164
|
isArray,
|
|
157
|
-
isStruct: typeInfo.isStruct,
|
|
165
|
+
isStruct: isCallbackCompat ? false : typeInfo.isStruct,
|
|
158
166
|
isConst,
|
|
159
167
|
isCallback: typeInfo.isCallback,
|
|
160
168
|
isString: typeInfo.isString,
|
|
@@ -284,9 +284,12 @@ class HeaderGeneratorUtils {
|
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
-
// External type header includes
|
|
287
|
+
// External type header includes (skip duplicates of user includes)
|
|
288
|
+
const userIncludeSet = new Set(options.userIncludes ?? []);
|
|
288
289
|
for (const directive of headersToInclude) {
|
|
289
|
-
|
|
290
|
+
if (!userIncludeSet.has(directive)) {
|
|
291
|
+
lines.push(directive);
|
|
292
|
+
}
|
|
290
293
|
}
|
|
291
294
|
|
|
292
295
|
// Add blank line if any includes were added
|
|
@@ -128,6 +128,9 @@ export default class CodeGenState {
|
|
|
128
128
|
/** Callback field types: "Struct.field" -> callbackTypeName */
|
|
129
129
|
static callbackFieldTypes: Map<string, string> = new Map();
|
|
130
130
|
|
|
131
|
+
/** Functions that need C-callback-compatible (by-value) struct parameters */
|
|
132
|
+
static callbackCompatibleFunctions: Set<string> = new Set();
|
|
133
|
+
|
|
131
134
|
// ===========================================================================
|
|
132
135
|
// PASS-BY-VALUE ANALYSIS (Issue #269)
|
|
133
136
|
// ===========================================================================
|
|
@@ -324,6 +327,9 @@ export default class CodeGenState {
|
|
|
324
327
|
this.functionSignatures = new Map();
|
|
325
328
|
this.callbackTypes = new Map();
|
|
326
329
|
this.callbackFieldTypes = new Map();
|
|
330
|
+
// Note: callbackCompatibleFunctions is NOT reset here — it's populated by
|
|
331
|
+
// FunctionCallAnalyzer (which runs before CodeGenerator.generate()) and must
|
|
332
|
+
// persist into code generation. It is cleared at the start of each Transpiler run.
|
|
327
333
|
|
|
328
334
|
// Pass-by-value analysis
|
|
329
335
|
this.modifiedParameters = new Map();
|
|
@@ -3,8 +3,8 @@ import IDiscoveredFile from "../data/types/IDiscoveredFile";
|
|
|
3
3
|
/**
|
|
4
4
|
* A file descriptor for the unified transpilation pipeline.
|
|
5
5
|
*
|
|
6
|
-
* Supports both disk-based files (
|
|
7
|
-
* (
|
|
6
|
+
* Supports both disk-based files (kind: 'files') and in-memory sources
|
|
7
|
+
* (kind: 'source'). The pipeline reads content via:
|
|
8
8
|
* file.source ?? this.fs.readFile(file.path)
|
|
9
9
|
*/
|
|
10
10
|
interface IPipelineFile {
|
|
@@ -4,7 +4,7 @@ import IPipelineFile from "./IPipelineFile";
|
|
|
4
4
|
/**
|
|
5
5
|
* Input to the unified transpilation pipeline (_executePipeline).
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* transpile() constructs this via discoverIncludes() and delegates to the pipeline.
|
|
8
8
|
*/
|
|
9
9
|
interface IPipelineInput {
|
|
10
10
|
/** C-Next files to process (in dependency order) */
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input to the unified transpile() method.
|
|
3
|
+
*
|
|
4
|
+
* Discriminated union:
|
|
5
|
+
* - { kind: 'files' } — CLI mode, discovers from config.inputs, writes to disk
|
|
6
|
+
* - { kind: 'source', ... } — API mode, in-memory source, returns results as strings
|
|
7
|
+
*/
|
|
8
|
+
type TTranspileInput =
|
|
9
|
+
| { readonly kind: "files" }
|
|
10
|
+
| {
|
|
11
|
+
readonly kind: "source";
|
|
12
|
+
readonly source: string;
|
|
13
|
+
readonly workingDir?: string;
|
|
14
|
+
readonly includeDirs?: string[];
|
|
15
|
+
readonly sourcePath?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default TTranspileInput;
|
|
@@ -20,6 +20,28 @@ class TypeConstants {
|
|
|
20
20
|
"float",
|
|
21
21
|
"double",
|
|
22
22
|
];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Unsigned integer types valid as array/bit subscript indexes.
|
|
26
|
+
*
|
|
27
|
+
* Used by:
|
|
28
|
+
* - ArrayIndexTypeAnalyzer: validating subscript index types
|
|
29
|
+
*/
|
|
30
|
+
static readonly UNSIGNED_INDEX_TYPES: readonly string[] = [
|
|
31
|
+
"u8",
|
|
32
|
+
"u16",
|
|
33
|
+
"u32",
|
|
34
|
+
"u64",
|
|
35
|
+
"bool",
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Signed integer types (rejected as subscript indexes).
|
|
40
|
+
*
|
|
41
|
+
* Used by:
|
|
42
|
+
* - ArrayIndexTypeAnalyzer: detecting signed integer subscript indexes
|
|
43
|
+
*/
|
|
44
|
+
static readonly SIGNED_TYPES: readonly string[] = ["i8", "i16", "i32", "i64"];
|
|
23
45
|
}
|
|
24
46
|
|
|
25
47
|
export default TypeConstants;
|