c-next 0.1.61 → 0.1.62
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/grammar/CNext.g4 +3 -17
- package/package.json +1 -1
- package/src/cli/serve/ServeCommand.ts +57 -45
- package/src/lib/__tests__/parseCHeader.mocked.test.ts +145 -0
- package/src/transpiler/Transpiler.ts +602 -613
- package/src/transpiler/__tests__/DualCodePaths.test.ts +4 -0
- package/src/transpiler/__tests__/Transpiler.coverage.test.ts +1 -98
- package/src/transpiler/__tests__/Transpiler.test.ts +3 -3
- package/src/transpiler/data/IncludeTreeWalker.ts +1 -1
- package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +23 -52
- package/src/transpiler/logic/parser/grammar/CNext.interp +1 -3
- package/src/transpiler/logic/parser/grammar/CNextListener.ts +0 -22
- package/src/transpiler/logic/parser/grammar/CNextParser.ts +665 -1084
- package/src/transpiler/logic/parser/grammar/CNextVisitor.ts +0 -14
- package/src/transpiler/logic/symbols/CppSymbolCollector.ts +67 -43
- package/src/transpiler/output/codegen/CodeGenerator.ts +606 -1223
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +1764 -0
- package/src/transpiler/output/codegen/analysis/MemberChainAnalyzer.ts +211 -46
- package/src/transpiler/output/codegen/analysis/StringLengthCounter.ts +3 -3
- package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +191 -238
- package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +60 -15
- package/src/transpiler/output/codegen/assignment/AssignmentContextBuilder.ts +24 -74
- package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +3 -0
- package/src/transpiler/output/codegen/assignment/IAssignmentContext.ts +3 -0
- package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +3 -0
- package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +42 -0
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +76 -2
- package/src/transpiler/output/codegen/generators/IOrchestrator.ts +5 -1
- package/src/transpiler/output/codegen/generators/declarationGenerators/RegisterGenerator.ts +11 -24
- package/src/transpiler/output/codegen/generators/declarationGenerators/RegisterMacroGenerator.ts +64 -0
- package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +16 -10
- package/src/transpiler/output/codegen/generators/declarationGenerators/ScopedRegisterGenerator.ts +18 -27
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +5 -1
- package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +1 -17
- package/src/transpiler/output/codegen/helpers/ArrayAccessHelper.ts +129 -0
- package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +23 -18
- package/src/transpiler/output/codegen/helpers/AssignmentTargetExtractor.ts +17 -45
- package/src/transpiler/output/codegen/helpers/AssignmentValidator.ts +33 -31
- package/src/transpiler/output/codegen/helpers/MemberSeparatorResolver.ts +10 -3
- package/src/transpiler/output/codegen/helpers/SymbolLookupHelper.ts +44 -0
- package/src/transpiler/output/codegen/helpers/__tests__/ArrayAccessHelper.test.ts +479 -0
- package/src/transpiler/output/codegen/helpers/__tests__/MemberSeparatorResolver.test.ts +1 -0
- package/src/transpiler/output/codegen/helpers/__tests__/SymbolLookupHelper.test.ts +201 -0
- package/src/transpiler/output/codegen/helpers/__tests__/TypeGenerationHelper.test.ts +50 -0
- package/src/transpiler/output/codegen/types/IArrayAccessDeps.ts +23 -0
- package/src/transpiler/output/codegen/types/IArrayAccessInfo.ts +26 -0
- package/src/transpiler/output/codegen/types/IMemberSeparatorDeps.ts +7 -0
- package/src/transpiler/output/codegen/utils/CodegenParserUtils.ts +98 -0
- package/src/transpiler/output/codegen/utils/ExpressionUnwrapper.ts +22 -22
- package/src/transpiler/output/codegen/utils/__tests__/CodegenParserUtils.test.ts +228 -0
- package/src/transpiler/types/IFileResult.ts +0 -4
- package/src/transpiler/types/IPipelineFile.ts +27 -0
- package/src/transpiler/types/IPipelineInput.ts +23 -0
- package/src/transpiler/types/TranspilerState.ts +1 -1
- package/src/utils/FormatUtils.ts +28 -2
- package/src/utils/MapUtils.ts +25 -0
- package/src/utils/PostfixAnalysisUtils.ts +48 -0
- package/src/utils/__tests__/FormatUtils.test.ts +42 -0
- package/src/utils/__tests__/MapUtils.test.ts +85 -0
- package/src/utils/constants/OperatorMappings.ts +19 -0
- package/src/transpiler/logic/StandaloneContextBuilder.ts +0 -150
- package/src/transpiler/logic/__tests__/StandaloneContextBuilder.test.ts +0 -647
- package/src/transpiler/types/ITranspileContext.ts +0 -49
- package/src/transpiler/types/ITranspileContribution.ts +0 -32
package/grammar/CNext.g4
CHANGED
|
@@ -248,14 +248,11 @@ assignmentOperator
|
|
|
248
248
|
;
|
|
249
249
|
|
|
250
250
|
// Assignment target with unified postfix chain approach (Issue #387)
|
|
251
|
-
//
|
|
252
|
-
// Bare identifiers fall back to existing memberAccess/arrayAccess rules for compatibility
|
|
251
|
+
// All patterns use postfixTargetOp* for member/array access chains
|
|
253
252
|
assignmentTarget
|
|
254
253
|
: 'global' '.' IDENTIFIER postfixTargetOp* // global.x, global.x[i][j].y, etc.
|
|
255
254
|
| 'this' '.' IDENTIFIER postfixTargetOp* // this.x, this.x[i].y, etc.
|
|
256
|
-
|
|
|
257
|
-
| memberAccess // GPIO7.DR_SET, arr[i].field
|
|
258
|
-
| IDENTIFIER // Simple identifier
|
|
255
|
+
| IDENTIFIER postfixTargetOp* // x, arr[i], obj.field, arr[i].field, etc.
|
|
259
256
|
;
|
|
260
257
|
|
|
261
258
|
// Unified postfix operation for assignment targets (Issue #387)
|
|
@@ -459,18 +456,7 @@ arrayInitializerElement
|
|
|
459
456
|
| arrayInitializer // For nested arrays: [[1,2], [3,4]]
|
|
460
457
|
;
|
|
461
458
|
|
|
462
|
-
memberAccess
|
|
463
|
-
: IDENTIFIER ('.' IDENTIFIER)+ ('[' expression ']')+ // ADR-036: screen.pixels[0][0]
|
|
464
|
-
| IDENTIFIER ('.' IDENTIFIER)+ '[' expression ',' expression ']' // GPIO7.DR[start, width]
|
|
465
|
-
| IDENTIFIER ('.' IDENTIFIER)+ // GPIO7.DR_SET
|
|
466
|
-
| IDENTIFIER ('[' expression ']')+ ('.' IDENTIFIER)+ // arr[i].field1.field2...
|
|
467
|
-
| IDENTIFIER (('[' expression ']') | ('.' IDENTIFIER))+ // arr[i].field[j].member... (any mix)
|
|
468
|
-
;
|
|
469
|
-
|
|
470
|
-
arrayAccess
|
|
471
|
-
: IDENTIFIER '[' expression ']' // Single element/bit
|
|
472
|
-
| IDENTIFIER '[' expression ',' expression ']' // Bit range [start, width]
|
|
473
|
-
;
|
|
459
|
+
// Note: memberAccess and arrayAccess rules removed - unified into assignmentTarget with postfixTargetOp*
|
|
474
460
|
|
|
475
461
|
argumentList
|
|
476
462
|
: expression (',' expression)*
|
package/package.json
CHANGED
|
@@ -35,6 +35,14 @@ interface IMethodResult {
|
|
|
35
35
|
errorMessage?: string;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Result of validating and extracting source parameters.
|
|
40
|
+
*/
|
|
41
|
+
interface ISourceParams {
|
|
42
|
+
source: string;
|
|
43
|
+
filePath: string | undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
38
46
|
/**
|
|
39
47
|
* Options for the serve command
|
|
40
48
|
*/
|
|
@@ -58,9 +66,15 @@ class ServeCommand {
|
|
|
58
66
|
private static readonly methods: Record<string, MethodHandler> = {
|
|
59
67
|
getVersion: ServeCommand.handleGetVersion,
|
|
60
68
|
initialize: ServeCommand.handleInitialize,
|
|
61
|
-
transpile: ServeCommand.
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
transpile: ServeCommand._withSourceValidation(
|
|
70
|
+
ServeCommand._handleTranspile,
|
|
71
|
+
),
|
|
72
|
+
parseSymbols: ServeCommand._withSourceValidation(
|
|
73
|
+
ServeCommand._handleParseSymbols,
|
|
74
|
+
),
|
|
75
|
+
parseCHeader: ServeCommand._withSourceValidation(
|
|
76
|
+
ServeCommand._handleParseCHeader,
|
|
77
|
+
),
|
|
64
78
|
shutdown: ServeCommand.handleShutdown,
|
|
65
79
|
};
|
|
66
80
|
|
|
@@ -104,6 +118,34 @@ class ServeCommand {
|
|
|
104
118
|
}
|
|
105
119
|
}
|
|
106
120
|
|
|
121
|
+
// ========================================================================
|
|
122
|
+
// Parameter Validation Helpers (Issue #707: Reduce code duplication)
|
|
123
|
+
// ========================================================================
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Wrapper that validates source params before calling handler.
|
|
127
|
+
* Eliminates duplicate validation code across handlers.
|
|
128
|
+
*/
|
|
129
|
+
private static _withSourceValidation(
|
|
130
|
+
handler: (params: ISourceParams) => Promise<IMethodResult>,
|
|
131
|
+
): MethodHandler {
|
|
132
|
+
return async (params?: Record<string, unknown>): Promise<IMethodResult> => {
|
|
133
|
+
if (!params || typeof params.source !== "string") {
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
errorCode: JsonRpcHandler.ERROR_INVALID_PARAMS,
|
|
137
|
+
errorMessage: "Missing required param: source",
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const validated: ISourceParams = {
|
|
141
|
+
source: String(params.source),
|
|
142
|
+
filePath:
|
|
143
|
+
typeof params.filePath === "string" ? params.filePath : undefined,
|
|
144
|
+
};
|
|
145
|
+
return handler(validated);
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
107
149
|
/**
|
|
108
150
|
* Handle a single line of input
|
|
109
151
|
*/
|
|
@@ -224,20 +266,12 @@ class ServeCommand {
|
|
|
224
266
|
}
|
|
225
267
|
|
|
226
268
|
/**
|
|
227
|
-
* Handle transpile method
|
|
269
|
+
* Handle transpile method (called via _withSourceValidation wrapper)
|
|
228
270
|
* Uses full Transpiler for include resolution and C++ auto-detection
|
|
229
271
|
*/
|
|
230
|
-
private static async
|
|
231
|
-
params
|
|
272
|
+
private static async _handleTranspile(
|
|
273
|
+
params: ISourceParams,
|
|
232
274
|
): Promise<IMethodResult> {
|
|
233
|
-
if (!params || typeof params.source !== "string") {
|
|
234
|
-
return {
|
|
235
|
-
success: false,
|
|
236
|
-
errorCode: JsonRpcHandler.ERROR_INVALID_PARAMS,
|
|
237
|
-
errorMessage: "Missing required param: source",
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
|
|
241
275
|
if (!ServeCommand.transpiler) {
|
|
242
276
|
return {
|
|
243
277
|
success: false,
|
|
@@ -246,9 +280,7 @@ class ServeCommand {
|
|
|
246
280
|
};
|
|
247
281
|
}
|
|
248
282
|
|
|
249
|
-
const source =
|
|
250
|
-
const filePath =
|
|
251
|
-
typeof params.filePath === "string" ? params.filePath : undefined;
|
|
283
|
+
const { source, filePath } = params;
|
|
252
284
|
|
|
253
285
|
const options = filePath
|
|
254
286
|
? { workingDir: dirname(filePath), sourcePath: filePath }
|
|
@@ -271,24 +303,14 @@ class ServeCommand {
|
|
|
271
303
|
}
|
|
272
304
|
|
|
273
305
|
/**
|
|
274
|
-
* Handle parseSymbols method
|
|
306
|
+
* Handle parseSymbols method (called via _withSourceValidation wrapper)
|
|
275
307
|
* Runs full transpilation for include/C++ detection, then extracts symbols
|
|
276
308
|
* from the parse tree (preserving "extract symbols even with parse errors" behavior)
|
|
277
309
|
*/
|
|
278
|
-
private static async
|
|
279
|
-
params
|
|
310
|
+
private static async _handleParseSymbols(
|
|
311
|
+
params: ISourceParams,
|
|
280
312
|
): Promise<IMethodResult> {
|
|
281
|
-
|
|
282
|
-
return {
|
|
283
|
-
success: false,
|
|
284
|
-
errorCode: JsonRpcHandler.ERROR_INVALID_PARAMS,
|
|
285
|
-
errorMessage: "Missing required param: source",
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const source = String(params.source);
|
|
290
|
-
const filePath =
|
|
291
|
-
typeof params.filePath === "string" ? params.filePath : undefined;
|
|
313
|
+
const { source, filePath } = params;
|
|
292
314
|
|
|
293
315
|
// If transpiler is initialized, run transpileSource to trigger header
|
|
294
316
|
// resolution and C++ detection (results are discarded, we just want
|
|
@@ -314,23 +336,13 @@ class ServeCommand {
|
|
|
314
336
|
}
|
|
315
337
|
|
|
316
338
|
/**
|
|
317
|
-
* Handle parseCHeader method
|
|
339
|
+
* Handle parseCHeader method (called via _withSourceValidation wrapper)
|
|
318
340
|
* Parses C/C++ header files and extracts symbols
|
|
319
341
|
*/
|
|
320
|
-
private static async
|
|
321
|
-
params
|
|
342
|
+
private static async _handleParseCHeader(
|
|
343
|
+
params: ISourceParams,
|
|
322
344
|
): Promise<IMethodResult> {
|
|
323
|
-
|
|
324
|
-
return {
|
|
325
|
-
success: false,
|
|
326
|
-
errorCode: JsonRpcHandler.ERROR_INVALID_PARAMS,
|
|
327
|
-
errorMessage: "Missing required param: source",
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const source = String(params.source);
|
|
332
|
-
const filePath =
|
|
333
|
-
typeof params.filePath === "string" ? params.filePath : undefined;
|
|
345
|
+
const { source, filePath } = params;
|
|
334
346
|
|
|
335
347
|
const result = parseCHeader(source, filePath);
|
|
336
348
|
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for parseCHeader edge cases requiring mocks
|
|
3
|
+
* Separate file to avoid mock pollution with main test file
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
7
|
+
import ESymbolKind from "../../utils/types/ESymbolKind";
|
|
8
|
+
|
|
9
|
+
// Create mock collect function that we can control per test
|
|
10
|
+
const mockCollect = vi.fn();
|
|
11
|
+
|
|
12
|
+
// Mock CSymbolCollector before importing parseCHeader
|
|
13
|
+
vi.mock("../../transpiler/logic/symbols/CSymbolCollector", () => {
|
|
14
|
+
return {
|
|
15
|
+
default: class MockCSymbolCollector {
|
|
16
|
+
collect() {
|
|
17
|
+
return mockCollect();
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Import after mock is set up
|
|
24
|
+
import parseCHeader from "../parseCHeader";
|
|
25
|
+
|
|
26
|
+
describe("parseCHeader mocked scenarios", () => {
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
mockCollect.mockReset();
|
|
29
|
+
// Default to returning empty array
|
|
30
|
+
mockCollect.mockReturnValue([]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("mapSymbolKind default case", () => {
|
|
34
|
+
it("maps Namespace kind to variable (default case)", () => {
|
|
35
|
+
// Return a symbol with Namespace kind (not explicitly handled in switch)
|
|
36
|
+
mockCollect.mockReturnValue([
|
|
37
|
+
{
|
|
38
|
+
name: "TestNamespace",
|
|
39
|
+
kind: ESymbolKind.Namespace,
|
|
40
|
+
type: undefined,
|
|
41
|
+
parent: undefined,
|
|
42
|
+
sourceLine: 1,
|
|
43
|
+
},
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
const result = parseCHeader("// any valid C");
|
|
47
|
+
|
|
48
|
+
expect(result.success).toBe(true);
|
|
49
|
+
expect(result.symbols).toHaveLength(1);
|
|
50
|
+
expect(result.symbols[0].kind).toBe("variable");
|
|
51
|
+
expect(result.symbols[0].name).toBe("TestNamespace");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("maps Class kind to variable (default case)", () => {
|
|
55
|
+
mockCollect.mockReturnValue([
|
|
56
|
+
{
|
|
57
|
+
name: "TestClass",
|
|
58
|
+
kind: ESymbolKind.Class,
|
|
59
|
+
type: "class",
|
|
60
|
+
parent: undefined,
|
|
61
|
+
sourceLine: 5,
|
|
62
|
+
},
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
const result = parseCHeader("// any valid C");
|
|
66
|
+
|
|
67
|
+
expect(result.success).toBe(true);
|
|
68
|
+
expect(result.symbols[0].kind).toBe("variable");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("maps Bitmap kind to variable (default case)", () => {
|
|
72
|
+
mockCollect.mockReturnValue([
|
|
73
|
+
{
|
|
74
|
+
name: "TestBitmap",
|
|
75
|
+
kind: ESymbolKind.Bitmap,
|
|
76
|
+
type: undefined,
|
|
77
|
+
parent: undefined,
|
|
78
|
+
sourceLine: 1,
|
|
79
|
+
},
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
const result = parseCHeader("// any valid C");
|
|
83
|
+
|
|
84
|
+
expect(result.success).toBe(true);
|
|
85
|
+
expect(result.symbols[0].kind).toBe("variable");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("defaults line to 0 when sourceLine is undefined", () => {
|
|
89
|
+
mockCollect.mockReturnValue([
|
|
90
|
+
{
|
|
91
|
+
name: "NoLineSymbol",
|
|
92
|
+
kind: ESymbolKind.Function,
|
|
93
|
+
type: "void",
|
|
94
|
+
parent: undefined,
|
|
95
|
+
sourceLine: undefined, // No source line info
|
|
96
|
+
},
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
const result = parseCHeader("// any valid C");
|
|
100
|
+
|
|
101
|
+
expect(result.success).toBe(true);
|
|
102
|
+
expect(result.symbols[0].line).toBe(0);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("error handling catch block", () => {
|
|
107
|
+
it("returns error result when collector throws Error", () => {
|
|
108
|
+
mockCollect.mockImplementation(() => {
|
|
109
|
+
throw new Error("Collector failed");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const result = parseCHeader("int x;");
|
|
113
|
+
|
|
114
|
+
expect(result.success).toBe(false);
|
|
115
|
+
expect(result.symbols).toHaveLength(0);
|
|
116
|
+
expect(result.errors).toHaveLength(1);
|
|
117
|
+
expect(result.errors[0].message).toBe("Collector failed");
|
|
118
|
+
expect(result.errors[0].severity).toBe("error");
|
|
119
|
+
expect(result.errors[0].line).toBe(1);
|
|
120
|
+
expect(result.errors[0].column).toBe(0);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("handles non-Error exceptions (string throw)", () => {
|
|
124
|
+
mockCollect.mockImplementation(() => {
|
|
125
|
+
throw "String error message";
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const result = parseCHeader("int x;");
|
|
129
|
+
|
|
130
|
+
expect(result.success).toBe(false);
|
|
131
|
+
expect(result.errors[0].message).toBe("String error message");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("handles non-Error exceptions (number throw)", () => {
|
|
135
|
+
mockCollect.mockImplementation(() => {
|
|
136
|
+
throw 42;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const result = parseCHeader("int x;");
|
|
140
|
+
|
|
141
|
+
expect(result.success).toBe(false);
|
|
142
|
+
expect(result.errors[0].message).toBe("42");
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|