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.
Files changed (81) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +7645 -5941
  3. package/dist/index.js.map +4 -4
  4. package/grammar/CNext.g4 +8 -0
  5. package/package.json +1 -3
  6. package/src/transpiler/Transpiler.ts +286 -26
  7. package/src/transpiler/__tests__/compileCommandsDiscovery.integration.test.ts +94 -0
  8. package/src/transpiler/__tests__/externalSymbolRecovery.integration.test.ts +215 -0
  9. package/src/transpiler/logic/__tests__/detectAssemblySyntax.test.ts +116 -0
  10. package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +8 -0
  11. package/src/transpiler/logic/analysis/MixedTypeCategoryAnalyzer.ts +408 -0
  12. package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +16 -97
  13. package/src/transpiler/logic/analysis/ReturnPathAnalyzer.ts +171 -0
  14. package/src/transpiler/logic/analysis/__tests__/MixedTypeCategoryAnalyzer.test.ts +346 -0
  15. package/src/transpiler/logic/analysis/__tests__/ReturnPathAnalyzer.test.ts +232 -0
  16. package/src/transpiler/logic/analysis/runAnalyzers.ts +23 -2
  17. package/src/transpiler/logic/analysis/types/IMixedTypeCategoryError.ts +17 -0
  18. package/src/transpiler/logic/analysis/types/IReturnPathError.ts +12 -0
  19. package/src/transpiler/logic/detectAssemblySyntax.ts +80 -0
  20. package/src/transpiler/logic/parser/grammar/CNext.interp +4 -1
  21. package/src/transpiler/logic/parser/grammar/CNext.tokens +169 -167
  22. package/src/transpiler/logic/parser/grammar/CNextLexer.interp +4 -1
  23. package/src/transpiler/logic/parser/grammar/CNextLexer.tokens +169 -167
  24. package/src/transpiler/logic/parser/grammar/CNextLexer.ts +545 -541
  25. package/src/transpiler/logic/parser/grammar/CNextListener.ts +11 -0
  26. package/src/transpiler/logic/parser/grammar/CNextParser.ts +1257 -1185
  27. package/src/transpiler/logic/parser/grammar/CNextVisitor.ts +7 -0
  28. package/src/transpiler/logic/preprocessor/CompileCommandsReader.ts +332 -0
  29. package/src/transpiler/logic/preprocessor/ExternalDeclarationOracle.ts +202 -0
  30. package/src/transpiler/logic/preprocessor/Preprocessor.ts +24 -6
  31. package/src/transpiler/logic/preprocessor/ToolchainDetector.ts +42 -1
  32. package/src/transpiler/logic/preprocessor/__tests__/CompileCommandsReader.test.ts +216 -0
  33. package/src/transpiler/logic/preprocessor/__tests__/ExternalDeclarationOracle.test.ts +153 -0
  34. package/src/transpiler/logic/preprocessor/__tests__/Preprocessor.test.ts +43 -18
  35. package/src/transpiler/logic/preprocessor/__tests__/ToolchainDetector.crossCompiler.test.ts +75 -0
  36. package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/dependent.h +7 -0
  37. package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/predecessor.h +5 -0
  38. package/src/transpiler/logic/preprocessor/types/ICompileCommandsResult.ts +14 -0
  39. package/src/transpiler/logic/preprocessor/types/IPreprocessOptions.ts +17 -0
  40. package/src/transpiler/logic/symbols/SymbolTable.ts +45 -0
  41. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +49 -0
  42. package/src/transpiler/output/MisraSuppressionUtils.ts +52 -0
  43. package/src/transpiler/output/__tests__/MisraSuppressionUtils.test.ts +67 -0
  44. package/src/transpiler/output/codegen/CodeGenerator.ts +45 -4
  45. package/src/transpiler/output/codegen/TypeResolver.ts +148 -0
  46. package/src/transpiler/output/codegen/TypeValidator.ts +179 -81
  47. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +13 -1
  48. package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +93 -0
  49. package/src/transpiler/output/codegen/__tests__/TypeValidator.alwaysTrueLoop.test.ts +91 -0
  50. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +86 -14
  51. package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +13 -0
  52. package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +1 -1
  53. package/src/transpiler/output/codegen/assignment/handlers/AccessPatternHandlers.ts +20 -12
  54. package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +424 -22
  55. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +31 -18
  56. package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +7 -8
  57. package/src/transpiler/output/codegen/assignment/handlers/RegisterHandlers.ts +6 -8
  58. package/src/transpiler/output/codegen/assignment/handlers/RegisterUtils.ts +12 -10
  59. package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +3 -7
  60. package/src/transpiler/output/codegen/assignment/handlers/SpecialHandlers.ts +7 -9
  61. package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +12 -10
  62. package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +479 -11
  63. package/src/transpiler/output/codegen/generators/IOrchestrator.ts +3 -0
  64. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +28 -15
  65. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +26 -13
  66. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +129 -1
  67. package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +64 -8
  68. package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +8 -5
  69. package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +30 -2
  70. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +16 -13
  71. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +19 -0
  72. package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +57 -11
  73. package/src/transpiler/output/codegen/subscript/SubscriptClassifier.ts +30 -11
  74. package/src/transpiler/output/codegen/subscript/TSubscriptKind.ts +1 -1
  75. package/src/transpiler/output/codegen/subscript/__tests__/SubscriptClassifier.test.ts +38 -6
  76. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +65 -24
  77. package/src/transpiler/state/CodeGenState.ts +20 -16
  78. package/src/transpiler/types/ICachedFileEntry.ts +7 -0
  79. package/src/utils/cache/CacheManager.ts +13 -2
  80. package/src/utils/cache/__tests__/CacheManager.test.ts +18 -1
  81. package/src/utils/constants/TypeConstants.ts +13 -0
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Integration test for Issue #985 external-symbol recovery.
3
+ *
4
+ * Drives the real Transpiler over a fixture that forces the recovery path:
5
+ * widget.h refuses standalone inclusion (its #error fires unless guard.h ran
6
+ * first), its API is emitted by a guard.h macro and decorated with a trailing
7
+ * attribute macro, and it declares a pointer-parameter function plus an opaque
8
+ * handle. This exercises Transpiler._collectExternalDeclarations and its
9
+ * helpers, ExternalDeclarationOracle, and the downstream codegen that adds `&`
10
+ * for a struct passed to a pointer param and `*` for an opaque handle.
11
+ *
12
+ * Requires a C preprocessor toolchain; skips gracefully when none is available.
13
+ */
14
+ import { describe, it, expect, beforeAll, afterAll } from "vitest";
15
+ import { mkdtempSync, writeFileSync, rmSync, readFileSync } from "node:fs";
16
+ import { tmpdir } from "node:os";
17
+ import { join } from "node:path";
18
+ import Transpiler from "../Transpiler";
19
+ import CodeGenState from "../state/CodeGenState";
20
+ import Preprocessor from "../logic/preprocessor/Preprocessor";
21
+
22
+ const GUARD_H = `#define WIDGET_GUARD 1
23
+ #define WIDGET_FEATURE 1
24
+ #define USE_PTHREAD 0
25
+ #define PRIVILEGED
26
+ #define WIDGET_SCALE(x) ((x) + 1)
27
+ #define DECLARE_WIDGET_API \\
28
+ typedef struct _widget_t widget_t; \\
29
+ typedef struct { int mode; } widget_cfg_t; \\
30
+ extern const widget_cfg_t widget_default_cfg; \\
31
+ int widget_install(const widget_cfg_t *config) PRIVILEGED; \\
32
+ void widget_apply(const widget_cfg_t *cfg) PRIVILEGED; \\
33
+ widget_t *widget_create(void) PRIVILEGED;
34
+ `;
35
+
36
+ // A header cnext DISCOVERS (it walks #includes unconditionally) but that cannot
37
+ // preprocess standalone — its <no-such-header> mirrors lvgl's OSAL headers
38
+ // probing <semaphore.h>. Its standalone failure sets anyHeaderPreprocessFailed,
39
+ // which is what triggers the external-declaration recovery pass. It is guarded
40
+ // out of the recovery TU (USE_PTHREAD is 0), so the union still preprocesses.
41
+ const PTHREAD_IMPL_H = `#include <cnext_no_such_header_zzz.h>\n`;
42
+
43
+ const WIDGET_H = `#ifndef WIDGET_GUARD
44
+ #error "include guard.h before widget.h"
45
+ #endif
46
+ #if USE_PTHREAD
47
+ #include "pthread_impl.h"
48
+ #endif
49
+ #if WIDGET_FEATURE
50
+ DECLARE_WIDGET_API
51
+ #endif
52
+ `;
53
+
54
+ const MAIN_CNX = `#include "guard.h"
55
+ #include "widget.h"
56
+
57
+ scope Demo {
58
+ widget_t handle;
59
+
60
+ void build() {
61
+ widget_cfg_t cfg <- {mode: 1};
62
+ i32 err <- global.widget_install(cfg);
63
+ global.widget_apply(widget_default_cfg);
64
+ i32 scaled <- WIDGET_SCALE(4);
65
+ handle <- global.widget_create();
66
+ }
67
+ }
68
+ `;
69
+
70
+ describe("external-symbol recovery (integration)", () => {
71
+ let dir: string;
72
+ const available = new Preprocessor().isAvailable();
73
+
74
+ beforeAll(() => {
75
+ dir = mkdtempSync(join(tmpdir(), "cnext-recovery-"));
76
+ writeFileSync(join(dir, "guard.h"), GUARD_H);
77
+ writeFileSync(join(dir, "widget.h"), WIDGET_H);
78
+ writeFileSync(join(dir, "pthread_impl.h"), PTHREAD_IMPL_H);
79
+ writeFileSync(join(dir, "main.cnx"), MAIN_CNX);
80
+ });
81
+
82
+ afterAll(() => {
83
+ if (dir) rmSync(dir, { recursive: true, force: true });
84
+ });
85
+
86
+ it("recovers full signatures so codegen adds & and pointer handles", async () => {
87
+ if (!available) return; // no toolchain in this env
88
+
89
+ const transpiler = new Transpiler({
90
+ input: join(dir, "main.cnx"),
91
+ includeDirs: [dir],
92
+ outDir: join(dir, "out"),
93
+ cppRequired: true,
94
+ noCache: true,
95
+ });
96
+
97
+ const result = await transpiler.transpile({ kind: "files" });
98
+
99
+ const unresolved = result.errors.find(
100
+ (e) =>
101
+ e.message.includes("widget_install") ||
102
+ e.message.includes("widget_create") ||
103
+ // Function-like macro recovered by name (no declaration to parse).
104
+ e.message.includes("WIDGET_SCALE"),
105
+ );
106
+ expect(unresolved).toBeUndefined();
107
+ expect(result.success).toBe(true);
108
+
109
+ const generated = result.outputFiles.find((f) => f.endsWith("main.cpp"));
110
+ expect(generated).toBeDefined();
111
+ const code = readFileSync(generated!, "utf8");
112
+
113
+ // Local struct arg passed to a pointer param -> address-of.
114
+ expect(code).toMatch(/widget_install\(\s*&/);
115
+ // Extern C global (struct value) passed to a pointer param -> address-of,
116
+ // resolved via the C symbol table fallback (#985).
117
+ expect(code).toMatch(/widget_apply\(\s*&widget_default_cfg/);
118
+ // Opaque handle -> pointer field.
119
+ expect(code).toMatch(/widget_t\s*\*\s*\w*handle/);
120
+ });
121
+
122
+ it("folds a recovered struct into externalStructFields so it stays subject to init-completeness checking", async () => {
123
+ if (!available) return;
124
+
125
+ // widget_cfg_t only becomes known through #985 recovery (its typedef is
126
+ // emitted by the DECLARE_WIDGET_API macro inside the un-standalone header).
127
+ // The external-struct snapshot InitializationAnalyzer consults must include
128
+ // it — otherwise recovered structs escape init checking that cleanly
129
+ // preprocessed structs get. Regression: the snapshot must be taken AFTER the
130
+ // recovery pass, not before it.
131
+ const transpiler = new Transpiler({
132
+ input: join(dir, "main.cnx"),
133
+ includeDirs: [dir],
134
+ outDir: join(dir, "out"),
135
+ cppRequired: true,
136
+ noCache: true,
137
+ });
138
+
139
+ const result = await transpiler.transpile({ kind: "files" });
140
+ expect(result.success).toBe(true);
141
+
142
+ const external = CodeGenState.getExternalStructFields();
143
+ expect(external.has("widget_cfg_t")).toBe(true);
144
+ expect([...(external.get("widget_cfg_t") ?? [])]).toContain("mode");
145
+ });
146
+
147
+ it("re-applies recovery on a warm header cache (Issue #985 regression)", async () => {
148
+ if (!available) return;
149
+
150
+ // Fresh project dir with a project marker so on-disk caching activates
151
+ // (determineProjectRoot needs a marker like cnext.config.json). The first
152
+ // run populates .cnx/ with the DEGRADED header symbols (cached before the
153
+ // recovery pass runs); the second run restores them from cache. The
154
+ // recovery gate must survive caching so warm builds re-apply the fix.
155
+ const cacheDir = mkdtempSync(join(tmpdir(), "cnext-recovery-cache-"));
156
+ try {
157
+ writeFileSync(join(cacheDir, "cnext.config.json"), "{}\n");
158
+ writeFileSync(join(cacheDir, "guard.h"), GUARD_H);
159
+ writeFileSync(join(cacheDir, "widget.h"), WIDGET_H);
160
+ writeFileSync(join(cacheDir, "pthread_impl.h"), PTHREAD_IMPL_H);
161
+ writeFileSync(join(cacheDir, "main.cnx"), MAIN_CNX);
162
+
163
+ // Caching ON (noCache defaults to false).
164
+ const run = async () =>
165
+ new Transpiler({
166
+ input: join(cacheDir, "main.cnx"),
167
+ includeDirs: [cacheDir],
168
+ outDir: join(cacheDir, "out"),
169
+ cppRequired: true,
170
+ }).transpile({ kind: "files" });
171
+
172
+ // Cold cache: populates .cnx/ with the degraded header symbols.
173
+ const cold = await run();
174
+ expect(cold.success).toBe(true);
175
+
176
+ // Warm cache: headers unchanged and restored from cache. The recovery
177
+ // pass must still fire and produce the same corrected codegen — before
178
+ // the fix, the warm build regenerated the by-value / non-compiling output.
179
+ const warm = await run();
180
+ expect(warm.success).toBe(true);
181
+
182
+ const generated = warm.outputFiles.find((f) => f.endsWith("main.cpp"));
183
+ expect(generated).toBeDefined();
184
+ const code = readFileSync(generated!, "utf8");
185
+ expect(code).toMatch(/widget_install\(\s*&/);
186
+ expect(code).toMatch(/widget_apply\(\s*&widget_default_cfg/);
187
+ expect(code).toMatch(/widget_t\s*\*\s*\w*handle/);
188
+ } finally {
189
+ rmSync(cacheDir, { recursive: true, force: true });
190
+ }
191
+ });
192
+
193
+ it("does nothing when no header failed to preprocess (clean project)", async () => {
194
+ if (!available) return;
195
+
196
+ // A .cnx with no C includes never trips anyHeaderPreprocessFailed, so the
197
+ // recovery pass is a no-op and transpilation still succeeds.
198
+ const cleanDir = mkdtempSync(join(tmpdir(), "cnext-clean-"));
199
+ try {
200
+ writeFileSync(
201
+ join(cleanDir, "main.cnx"),
202
+ `scope Demo {\n void run() {\n i32 x <- 1;\n }\n}\n`,
203
+ );
204
+ const transpiler = new Transpiler({
205
+ input: join(cleanDir, "main.cnx"),
206
+ outDir: join(cleanDir, "out"),
207
+ noCache: true,
208
+ });
209
+ const result = await transpiler.transpile({ kind: "files" });
210
+ expect(result.success).toBe(true);
211
+ } finally {
212
+ rmSync(cleanDir, { recursive: true, force: true });
213
+ }
214
+ });
215
+ });
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Unit tests for detectAssemblySyntax
3
+ *
4
+ * Some framework headers (e.g. xtensa `coreasm.h`, pulled in transitively by
5
+ * FreeRTOS port headers) are GNU-assembler sources, not C: they `#define
6
+ * _ASMLANGUAGE` and contain `.macro` bodies whose instruction mnemonics (e.g.
7
+ * `loop`) were mis-collected as C symbols, causing false "defined in multiple
8
+ * languages" conflicts against C-Next symbols like Arduino `loop()`.
9
+ * detectAssemblySyntax lets the header pipeline skip such files.
10
+ */
11
+ import { describe, it, expect } from "vitest";
12
+ import detectAssemblySyntax from "../detectAssemblySyntax";
13
+
14
+ describe("detectAssemblySyntax", () => {
15
+ describe("assembler sources - should return true", () => {
16
+ it("detects a GAS .macro block (xtensa coreasm.h shape)", () => {
17
+ const coreasm = [
18
+ "#define _ASMLANGUAGE",
19
+ "\t.macro\tfloop_\tar, startlabel, endlabelref",
20
+ "\tloop\t\\ar, \\endlabelref",
21
+ "\t.endm",
22
+ ].join("\n");
23
+ expect(detectAssemblySyntax(coreasm)).toBe(true);
24
+ });
25
+
26
+ it("detects the _ASMLANGUAGE marker on its own", () => {
27
+ expect(detectAssemblySyntax("#define _ASMLANGUAGE 1\n")).toBe(true);
28
+ });
29
+
30
+ it("detects section/global directives at line start", () => {
31
+ expect(detectAssemblySyntax(" .section .text\n .global _start\n")).toBe(
32
+ true,
33
+ );
34
+ });
35
+
36
+ it("detects .macro regardless of leading whitespace", () => {
37
+ expect(detectAssemblySyntax(" .macro find_ms_setbit ad, as\n")).toBe(
38
+ true,
39
+ );
40
+ });
41
+
42
+ it("detects a spaced '# define _ASMLANGUAGE' directive", () => {
43
+ expect(detectAssemblySyntax("# define _ASMLANGUAGE\n")).toBe(true);
44
+ });
45
+
46
+ it("detects portable assembly markers defined whole-file", () => {
47
+ expect(detectAssemblySyntax("#define __ASSEMBLY__ 1\n")).toBe(true);
48
+ expect(detectAssemblySyntax("#define __ASSEMBLER__\n")).toBe(true);
49
+ });
50
+ });
51
+
52
+ describe("C / C++ headers - should return false", () => {
53
+ it("returns false for a C function named loop", () => {
54
+ expect(
55
+ detectAssemblySyntax("void loop(void);\nint add(int a, int b);"),
56
+ ).toBe(false);
57
+ });
58
+
59
+ it("returns false for structs, enums, typedefs", () => {
60
+ expect(
61
+ detectAssemblySyntax(
62
+ "typedef struct { int x; } Point;\nenum Color { RED, GREEN };",
63
+ ),
64
+ ).toBe(false);
65
+ });
66
+
67
+ it("does not false-positive on member access or identifiers containing 'macro'", () => {
68
+ expect(
69
+ detectAssemblySyntax("int y = p.x;\nint n = cfg.macro_count;"),
70
+ ).toBe(false);
71
+ });
72
+
73
+ it("returns false for a C++ class header", () => {
74
+ expect(
75
+ detectAssemblySyntax("class Foo : public Bar { public: int x; };"),
76
+ ).toBe(false);
77
+ });
78
+
79
+ it("returns false for a C header that guards prototypes with #ifndef _ASMLANGUAGE", () => {
80
+ // xtensa xtruntime.h shape: raw-fallback content still holds the guard
81
+ // line. The C prototypes inside must NOT be dropped as assembler.
82
+ const xtruntime = [
83
+ "#ifndef _ASMLANGUAGE",
84
+ "extern void xthal_window_spill(void);",
85
+ "extern unsigned xthal_get_ccount(void);",
86
+ "#endif",
87
+ ].join("\n");
88
+ expect(detectAssemblySyntax(xtruntime)).toBe(false);
89
+ });
90
+
91
+ it("returns false for a C header guarded by #ifndef __ASSEMBLY__ / #ifdef __ASSEMBLER__", () => {
92
+ // Linux-kernel-style guards fence the C prototypes; on the raw-fallback
93
+ // path the guard lines survive but the file is still a normal C header.
94
+ const guarded = [
95
+ "#ifndef __ASSEMBLY__",
96
+ "extern int kernel_init(void);",
97
+ "#endif",
98
+ "#ifdef __ASSEMBLER__",
99
+ "/* asm-only region */",
100
+ "#endif",
101
+ ].join("\n");
102
+ expect(detectAssemblySyntax(guarded)).toBe(false);
103
+ });
104
+
105
+ it("does not match a longer macro name that merely starts with _ASMLANGUAGE", () => {
106
+ // Exact-token match: `#define _ASMLANGUAGE_CONFIG 1` is not the marker.
107
+ expect(detectAssemblySyntax("#define _ASMLANGUAGE_CONFIG 1\n")).toBe(
108
+ false,
109
+ );
110
+ });
111
+
112
+ it("returns false for empty content", () => {
113
+ expect(detectAssemblySyntax("")).toBe(false);
114
+ });
115
+ });
116
+ });
@@ -1080,6 +1080,14 @@ class FunctionCallAnalyzer {
1080
1080
  }
1081
1081
  }
1082
1082
 
1083
+ // Issue #985 recovery: a function-like MACRO recovered by translation-unit
1084
+ // preprocessing (e.g. FreeRTOS pdMS_TO_TICKS). Recovered functions are
1085
+ // registered as full symbols (found above via getOverloads); macros have no
1086
+ // declaration to parse, so only their name is known.
1087
+ if (this.symbolTable.hasExternalDeclaration(name)) {
1088
+ return true;
1089
+ }
1090
+
1083
1091
  return false;
1084
1092
  }
1085
1093
  }