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.
Files changed (39) hide show
  1. package/README.md +59 -57
  2. package/dist/index.js +537 -170
  3. package/dist/index.js.map +4 -4
  4. package/package.json +1 -1
  5. package/src/cli/Runner.ts +1 -1
  6. package/src/cli/__tests__/Runner.test.ts +8 -8
  7. package/src/cli/serve/ServeCommand.ts +29 -9
  8. package/src/transpiler/Transpiler.ts +105 -200
  9. package/src/transpiler/__tests__/DualCodePaths.test.ts +117 -68
  10. package/src/transpiler/__tests__/Transpiler.coverage.test.ts +87 -51
  11. package/src/transpiler/__tests__/Transpiler.test.ts +150 -48
  12. package/src/transpiler/__tests__/determineProjectRoot.test.ts +2 -2
  13. package/src/transpiler/data/IncludeResolver.ts +11 -3
  14. package/src/transpiler/data/__tests__/IncludeResolver.test.ts +2 -2
  15. package/src/transpiler/logic/analysis/ArrayIndexTypeAnalyzer.ts +346 -0
  16. package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +170 -14
  17. package/src/transpiler/logic/analysis/__tests__/ArrayIndexTypeAnalyzer.test.ts +545 -0
  18. package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +327 -0
  19. package/src/transpiler/logic/analysis/runAnalyzers.ts +9 -2
  20. package/src/transpiler/logic/analysis/types/IArrayIndexTypeError.ts +15 -0
  21. package/src/transpiler/logic/symbols/TransitiveEnumCollector.ts +1 -1
  22. package/src/transpiler/logic/symbols/c/index.ts +50 -1
  23. package/src/transpiler/output/codegen/CodeGenerator.ts +31 -5
  24. package/src/transpiler/output/codegen/TypeValidator.ts +10 -7
  25. package/src/transpiler/output/codegen/__tests__/ExpressionWalker.test.ts +9 -3
  26. package/src/transpiler/output/codegen/__tests__/RequireInclude.test.ts +86 -23
  27. package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +3 -1
  28. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +43 -29
  29. package/src/transpiler/output/codegen/generators/IOrchestrator.ts +5 -2
  30. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +1 -1
  31. package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +12 -3
  32. package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +4 -4
  33. package/src/transpiler/output/codegen/helpers/FunctionContextManager.ts +9 -1
  34. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +5 -2
  35. package/src/transpiler/state/CodeGenState.ts +6 -0
  36. package/src/transpiler/types/IPipelineFile.ts +2 -2
  37. package/src/transpiler/types/IPipelineInput.ts +1 -1
  38. package/src/transpiler/types/TTranspileInput.ts +18 -0
  39. 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 = await transpiler.transpileSource("u8 value <- 0;");
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 = await transpiler.transpileSource("u16 value <- 0;");
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 = await transpiler.transpileSource("u32 value <- 0;");
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 = await transpiler.transpileSource("i32 value <- 0;");
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 = await transpiler.transpileSource(`
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 = await transpiler.transpileSource("bool flag <- false;");
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 = await transpiler.transpileSource(
89
- 'string<32> name <- "test";',
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 = await transpiler.transpileSource(
100
- 'const string message <- "hello";',
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 = await transpiler.transpileSource("ISR handler <- null;");
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 = await transpiler.transpileSource(`
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 = await transpiler.transpileSource(`
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 = await transpiler.transpileSource(`
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 = await transpiler.transpileSource(`
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 = await transpiler.transpileSource("void doNothing() { }");
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 = await transpiler.transpileSource(source);
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("validateDoWhileCondition", () => {
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(() => TypeValidator.validateDoWhileCondition(ctx)).not.toThrow();
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(() => TypeValidator.validateDoWhileCondition(ctx)).not.toThrow();
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(() => TypeValidator.validateDoWhileCondition(ctx)).not.toThrow();
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(() => TypeValidator.validateDoWhileCondition(ctx)).not.toThrow();
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(() => TypeValidator.validateDoWhileCondition(ctx)).toThrow(
1902
- "E0701",
1903
- );
1904
- expect(() => TypeValidator.validateDoWhileCondition(ctx)).toThrow(
1905
- "do-while condition must be a boolean expression",
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(() => TypeValidator.validateDoWhileCondition(ctx)).toThrow(
1918
- "E0701",
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(() => TypeValidator.validateDoWhileCondition(ctx)).not.toThrow();
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(() => TypeValidator.validateDoWhileCondition(ctx)).not.toThrow();
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(() => TypeValidator.validateDoWhileCondition(ctx)).not.toThrow();
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(() => TypeValidator.validateDoWhileCondition(ctx)).toThrow(
2026
- "help: use explicit comparison: count > 0 or count != 0",
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(() => TypeValidator.validateDoWhileCondition(ctx)).toThrow(
2043
- "E0701",
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(() => TypeValidator.validateDoWhileCondition(ctx)).toThrow(
2063
- "E0701",
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(() => TypeValidator.validateDoWhileCondition(ctx)).toThrow(
2086
- "E0701",
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 do-while condition (ADR-027) */
212
- validateDoWhileCondition(ctx: Parser.ExpressionContext): void;
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
- validateDoWhileCondition: vi.fn(),
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
 
@@ -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
- validateDoWhileCondition: vi.fn(),
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 validateDoWhileCondition = vi.fn();
693
+ const validateConditionIsBoolean = vi.fn();
694
694
  const orchestrator = {
695
695
  ...createMockOrchestrator(),
696
- validateDoWhileCondition,
696
+ validateConditionIsBoolean,
697
697
  } as unknown as IOrchestrator;
698
698
 
699
699
  generateDoWhile(ctx, input, state, orchestrator);
700
700
 
701
- expect(validateDoWhileCondition).toHaveBeenCalledWith(expr);
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
- lines.push(directive);
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 (from run()) and in-memory sources
7
- * (from transpileSource()). The pipeline reads content via:
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
- * Both run() and transpileSource() construct this and delegate to the pipeline.
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;