c-next 0.2.9 → 0.2.10
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/dist/index.js +814 -206
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/src/transpiler/Transpiler.ts +97 -7
- package/src/transpiler/__tests__/Transpiler.coverage.test.ts +170 -0
- package/src/transpiler/__tests__/needsConditionalPreprocessing.test.ts +246 -0
- package/src/transpiler/logic/analysis/ArrayIndexTypeAnalyzer.ts +12 -1
- package/src/transpiler/logic/analysis/__tests__/ArrayIndexTypeAnalyzer.test.ts +172 -0
- package/src/transpiler/logic/symbols/SymbolTable.ts +84 -0
- package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +43 -0
- package/src/transpiler/logic/symbols/__tests__/TransitiveEnumCollector.test.ts +1 -0
- package/src/transpiler/logic/symbols/c/__tests__/CResolver.integration.test.ts +98 -1
- package/src/transpiler/logic/symbols/c/collectors/FunctionCollector.ts +23 -5
- package/src/transpiler/logic/symbols/c/collectors/StructCollector.ts +69 -2
- package/src/transpiler/logic/symbols/c/index.ts +85 -30
- package/src/transpiler/logic/symbols/c/utils/DeclaratorUtils.ts +18 -0
- package/src/transpiler/logic/symbols/cnext/__tests__/TSymbolInfoAdapter.test.ts +90 -0
- package/src/transpiler/logic/symbols/cnext/adapters/TSymbolInfoAdapter.ts +40 -39
- package/src/transpiler/output/codegen/CodeGenerator.ts +23 -1
- package/src/transpiler/output/codegen/TypeResolver.ts +14 -0
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +19 -14
- package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +7 -7
- package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +1 -0
- package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +27 -4
- package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +6 -0
- package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +73 -0
- package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +4 -0
- package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +92 -0
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +5 -2
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/handlerTestUtils.ts +1 -0
- package/src/transpiler/output/codegen/generators/IOrchestrator.ts +14 -0
- package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +17 -2
- package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +49 -0
- package/src/transpiler/output/codegen/generators/expressions/AccessExprGenerator.ts +17 -5
- package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +5 -0
- package/src/transpiler/output/codegen/generators/expressions/LiteralGenerator.ts +16 -0
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +41 -5
- package/src/transpiler/output/codegen/generators/expressions/UnaryExprGenerator.ts +25 -1
- package/src/transpiler/output/codegen/generators/statements/AtomicGenerator.ts +2 -17
- package/src/transpiler/output/codegen/helpers/ArrayAccessHelper.ts +3 -0
- package/src/transpiler/output/codegen/helpers/BitRangeHelper.ts +19 -3
- package/src/transpiler/output/codegen/helpers/NarrowingCastHelper.ts +191 -0
- package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +35 -4
- package/src/transpiler/output/codegen/helpers/__tests__/ArrayAccessHelper.test.ts +131 -1
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +1 -0
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentValidator.test.ts +1 -0
- package/src/transpiler/output/codegen/helpers/__tests__/BitRangeHelper.test.ts +53 -2
- package/src/transpiler/output/codegen/helpers/__tests__/FunctionContextManager.test.ts +1 -0
- package/src/transpiler/output/codegen/helpers/__tests__/NarrowingCastHelper.test.ts +159 -0
- package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +1 -0
- package/src/transpiler/output/codegen/types/COMPOUND_TO_BINARY.ts +21 -0
- package/src/transpiler/output/codegen/types/IArrayAccessInfo.ts +2 -0
- package/src/transpiler/state/CodeGenState.ts +49 -0
- package/src/transpiler/state/__tests__/CodeGenState.test.ts +53 -0
- package/src/transpiler/state/__tests__/TranspilerState.test.ts +1 -0
- package/src/transpiler/types/ICachedFileEntry.ts +2 -0
- package/src/transpiler/types/ICodeGenSymbols.ts +8 -0
- package/src/utils/BitUtils.ts +33 -4
- package/src/utils/cache/CacheManager.ts +9 -1
- package/src/utils/cache/__tests__/CacheManager.test.ts +1 -1
package/package.json
CHANGED
|
@@ -214,7 +214,8 @@ class Transpiler {
|
|
|
214
214
|
result: ITranspilerResult,
|
|
215
215
|
): Promise<void> {
|
|
216
216
|
// Stage 2: Collect symbols from C/C++ headers and build analyzer context
|
|
217
|
-
|
|
217
|
+
// Issue #945: Now async for preprocessing support
|
|
218
|
+
await this._collectAllHeaderSymbols(input.headerFiles, result);
|
|
218
219
|
CodeGenState.buildExternalStructFields();
|
|
219
220
|
|
|
220
221
|
// Stage 3: Collect symbols from C-Next files
|
|
@@ -365,6 +366,16 @@ class Transpiler {
|
|
|
365
366
|
);
|
|
366
367
|
}
|
|
367
368
|
|
|
369
|
+
// Issue #948: Merge opaque types from C/C++ headers
|
|
370
|
+
// These are forward-declared struct types that need pointer semantics
|
|
371
|
+
const externalOpaqueTypes = CodeGenState.symbolTable.getAllOpaqueTypes();
|
|
372
|
+
if (externalOpaqueTypes.length > 0) {
|
|
373
|
+
symbolInfo = TSymbolInfoAdapter.mergeOpaqueTypes(
|
|
374
|
+
symbolInfo,
|
|
375
|
+
externalOpaqueTypes,
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
368
379
|
// Make symbols available to analyzers (CodeGenerator.generate() sets this too)
|
|
369
380
|
CodeGenState.symbols = symbolInfo;
|
|
370
381
|
|
|
@@ -596,14 +607,15 @@ class Transpiler {
|
|
|
596
607
|
|
|
597
608
|
/**
|
|
598
609
|
* Stage 2: Collect symbols from all C/C++ headers
|
|
610
|
+
* Issue #945: Made async for preprocessing support.
|
|
599
611
|
*/
|
|
600
|
-
private _collectAllHeaderSymbols(
|
|
612
|
+
private async _collectAllHeaderSymbols(
|
|
601
613
|
headerFiles: IDiscoveredFile[],
|
|
602
614
|
result: ITranspilerResult,
|
|
603
|
-
): void {
|
|
615
|
+
): Promise<void> {
|
|
604
616
|
for (const file of headerFiles) {
|
|
605
617
|
try {
|
|
606
|
-
this.doCollectHeaderSymbols(file);
|
|
618
|
+
await this.doCollectHeaderSymbols(file);
|
|
607
619
|
result.filesProcessed++;
|
|
608
620
|
} catch (err) {
|
|
609
621
|
this.warnings.push(`Failed to process header ${file.path}: ${err}`);
|
|
@@ -989,9 +1001,10 @@ class Transpiler {
|
|
|
989
1001
|
/**
|
|
990
1002
|
* Stage 2: Collect symbols from a single C/C++ header
|
|
991
1003
|
* Issue #592: Recursive include processing moved to IncludeResolver.resolveHeadersTransitively()
|
|
1004
|
+
* Issue #945: Added preprocessing support for conditional compilation
|
|
992
1005
|
* SonarCloud S3776: Refactored to use helper methods for reduced complexity.
|
|
993
1006
|
*/
|
|
994
|
-
private doCollectHeaderSymbols(file: IDiscoveredFile): void {
|
|
1007
|
+
private async doCollectHeaderSymbols(file: IDiscoveredFile): Promise<void> {
|
|
995
1008
|
// Track as processed (for cycle detection)
|
|
996
1009
|
const absolutePath = resolve(file.path);
|
|
997
1010
|
this.state.markHeaderProcessed(absolutePath);
|
|
@@ -1001,8 +1014,8 @@ class Transpiler {
|
|
|
1001
1014
|
return; // Cache hit - skip full parsing
|
|
1002
1015
|
}
|
|
1003
1016
|
|
|
1004
|
-
//
|
|
1005
|
-
const content = this.
|
|
1017
|
+
// Issue #945: Preprocess header to evaluate #if/#ifdef directives
|
|
1018
|
+
const content = await this.getHeaderContent(file);
|
|
1006
1019
|
this.parseHeaderFile(file, content);
|
|
1007
1020
|
|
|
1008
1021
|
// Debug: Show symbols found
|
|
@@ -1043,12 +1056,89 @@ class Transpiler {
|
|
|
1043
1056
|
);
|
|
1044
1057
|
CodeGenState.symbolTable.restoreEnumBitWidths(cached.enumBitWidth);
|
|
1045
1058
|
|
|
1059
|
+
// Issue #948: Restore opaque types (forward-declared structs)
|
|
1060
|
+
CodeGenState.symbolTable.restoreOpaqueTypes(cached.opaqueTypes);
|
|
1061
|
+
|
|
1046
1062
|
// Issue #211: Still check for C++ syntax even on cache hit
|
|
1047
1063
|
this.detectCppFromFileType(file);
|
|
1048
1064
|
|
|
1049
1065
|
return true;
|
|
1050
1066
|
}
|
|
1051
1067
|
|
|
1068
|
+
/**
|
|
1069
|
+
* Get header content, optionally preprocessed.
|
|
1070
|
+
* Issue #945: Evaluates #if/#ifdef directives using system preprocessor.
|
|
1071
|
+
*
|
|
1072
|
+
* Only preprocesses when necessary to avoid side effects from full expansion.
|
|
1073
|
+
* Preprocessing is needed when the file has conditional compilation patterns
|
|
1074
|
+
* like #if MACRO != 0 that require expression evaluation.
|
|
1075
|
+
*/
|
|
1076
|
+
private async getHeaderContent(file: IDiscoveredFile): Promise<string> {
|
|
1077
|
+
const rawContent = this.fs.readFile(file.path);
|
|
1078
|
+
|
|
1079
|
+
// Check if preprocessing is disabled
|
|
1080
|
+
if (this.config.preprocess === false) {
|
|
1081
|
+
return rawContent;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// Check if preprocessing is available
|
|
1085
|
+
if (!this.preprocessor.isAvailable()) {
|
|
1086
|
+
return rawContent;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Issue #945: Only preprocess if file has conditional compilation patterns
|
|
1090
|
+
// that require expression evaluation (e.g., #if MACRO != 0, #if MACRO == 1)
|
|
1091
|
+
// Simple #ifdef/#ifndef patterns are already handled by the parser
|
|
1092
|
+
if (!this.needsConditionalPreprocessing(rawContent)) {
|
|
1093
|
+
return rawContent;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// Preprocess the header file
|
|
1097
|
+
const result = await this.preprocessor.preprocess(file.path, {
|
|
1098
|
+
defines: this.config.defines,
|
|
1099
|
+
includePaths: this.config.includeDirs,
|
|
1100
|
+
keepLineDirectives: false, // We don't need line mappings for symbol collection
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
if (!result.success) {
|
|
1104
|
+
// Log warning but fall back to raw content
|
|
1105
|
+
this.warnings.push(
|
|
1106
|
+
`Preprocessing failed for ${file.path}: ${result.error}. Using raw content.`,
|
|
1107
|
+
);
|
|
1108
|
+
return rawContent;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
return result.content;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
/**
|
|
1115
|
+
* Check if a header file needs conditional preprocessing.
|
|
1116
|
+
* Issue #945: Only preprocess files with #if expressions that need evaluation.
|
|
1117
|
+
*/
|
|
1118
|
+
private needsConditionalPreprocessing(content: string): boolean {
|
|
1119
|
+
// Patterns that require the preprocessor for expression evaluation:
|
|
1120
|
+
// - #if MACRO != 0
|
|
1121
|
+
// - #if MACRO == 1
|
|
1122
|
+
// - #if MACRO > 0
|
|
1123
|
+
// - #if MACRO (bare macro as truthy check)
|
|
1124
|
+
// - #elif MACRO != 0
|
|
1125
|
+
// - #if defined(X) && MACRO
|
|
1126
|
+
// - etc.
|
|
1127
|
+
//
|
|
1128
|
+
// Simple patterns handled by the parser without preprocessing:
|
|
1129
|
+
// - #ifdef MACRO
|
|
1130
|
+
// - #ifndef MACRO
|
|
1131
|
+
// - #if defined(MACRO) (single defined check)
|
|
1132
|
+
// - #if 1
|
|
1133
|
+
// - #if 0
|
|
1134
|
+
//
|
|
1135
|
+
// Look for #if/#elif followed by an expression (not just defined() or 0/1)
|
|
1136
|
+
// Also match bare macro names used as truthy checks (common in config headers)
|
|
1137
|
+
const ifExpressionPattern =
|
|
1138
|
+
/#(?:if|elif)\s+(?!defined\s*\()(?![01]\s*(?:$|\n|\/\*|\/\/))\w+/m;
|
|
1139
|
+
return ifExpressionPattern.test(content);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1052
1142
|
/**
|
|
1053
1143
|
* Restore cached symbols to the symbol table.
|
|
1054
1144
|
* ADR-055 Phase 7: Converts ISerializedSymbol[] from cache to typed symbols.
|
|
@@ -1239,4 +1239,174 @@ describe("Transpiler coverage integration tests", () => {
|
|
|
1239
1239
|
expect(result.outputFiles.some((f) => f.endsWith(".c"))).toBe(true);
|
|
1240
1240
|
expect(result.outputFiles.some((f) => f.endsWith(".h"))).toBe(true);
|
|
1241
1241
|
});
|
|
1242
|
+
|
|
1243
|
+
// ==========================================================================
|
|
1244
|
+
// Issue #945: getHeaderContent preprocessing tests
|
|
1245
|
+
// ==========================================================================
|
|
1246
|
+
|
|
1247
|
+
it("skips preprocessing when preprocess config is false", async () => {
|
|
1248
|
+
const srcDir = join(testDir, "src");
|
|
1249
|
+
const includeDir = join(testDir, "include");
|
|
1250
|
+
mkdirSync(srcDir, { recursive: true });
|
|
1251
|
+
mkdirSync(includeDir, { recursive: true });
|
|
1252
|
+
|
|
1253
|
+
// Create a header with conditional that would trigger preprocessing
|
|
1254
|
+
writeFileSync(
|
|
1255
|
+
join(includeDir, "config.h"),
|
|
1256
|
+
`
|
|
1257
|
+
#if FEATURE_ENABLED != 0
|
|
1258
|
+
void enabled_func(void);
|
|
1259
|
+
#endif
|
|
1260
|
+
void always_available(void);
|
|
1261
|
+
`,
|
|
1262
|
+
);
|
|
1263
|
+
|
|
1264
|
+
writeFileSync(
|
|
1265
|
+
join(srcDir, "main.cnx"),
|
|
1266
|
+
`
|
|
1267
|
+
#include "config.h"
|
|
1268
|
+
void test() { always_available(); }
|
|
1269
|
+
`,
|
|
1270
|
+
);
|
|
1271
|
+
|
|
1272
|
+
const transpiler = new Transpiler({
|
|
1273
|
+
inputs: [join(srcDir, "main.cnx")],
|
|
1274
|
+
includeDirs: [includeDir],
|
|
1275
|
+
preprocess: false, // Explicitly disable preprocessing
|
|
1276
|
+
noCache: true,
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
const result = await transpiler.transpile({ kind: "files" });
|
|
1280
|
+
expect(result.success).toBe(true);
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
it("handles header without conditional patterns (no preprocessing needed)", async () => {
|
|
1284
|
+
const srcDir = join(testDir, "src");
|
|
1285
|
+
const includeDir = join(testDir, "include");
|
|
1286
|
+
mkdirSync(srcDir, { recursive: true });
|
|
1287
|
+
mkdirSync(includeDir, { recursive: true });
|
|
1288
|
+
|
|
1289
|
+
// Create a simple header without #if MACRO patterns
|
|
1290
|
+
writeFileSync(
|
|
1291
|
+
join(includeDir, "simple.h"),
|
|
1292
|
+
`
|
|
1293
|
+
#ifndef SIMPLE_H
|
|
1294
|
+
#define SIMPLE_H
|
|
1295
|
+
void simple_func(void);
|
|
1296
|
+
#endif
|
|
1297
|
+
`,
|
|
1298
|
+
);
|
|
1299
|
+
|
|
1300
|
+
writeFileSync(
|
|
1301
|
+
join(srcDir, "main.cnx"),
|
|
1302
|
+
`
|
|
1303
|
+
#include "simple.h"
|
|
1304
|
+
void test() { simple_func(); }
|
|
1305
|
+
`,
|
|
1306
|
+
);
|
|
1307
|
+
|
|
1308
|
+
const transpiler = new Transpiler({
|
|
1309
|
+
inputs: [join(srcDir, "main.cnx")],
|
|
1310
|
+
includeDirs: [includeDir],
|
|
1311
|
+
noCache: true,
|
|
1312
|
+
});
|
|
1313
|
+
|
|
1314
|
+
const result = await transpiler.transpile({ kind: "files" });
|
|
1315
|
+
expect(result.success).toBe(true);
|
|
1316
|
+
});
|
|
1317
|
+
|
|
1318
|
+
it("preprocesses header with conditional patterns when preprocessor available", async () => {
|
|
1319
|
+
const srcDir = join(testDir, "src");
|
|
1320
|
+
const includeDir = join(testDir, "include");
|
|
1321
|
+
mkdirSync(srcDir, { recursive: true });
|
|
1322
|
+
mkdirSync(includeDir, { recursive: true });
|
|
1323
|
+
|
|
1324
|
+
// Create a config header that defines the feature
|
|
1325
|
+
writeFileSync(
|
|
1326
|
+
join(includeDir, "config.h"),
|
|
1327
|
+
`
|
|
1328
|
+
#ifndef CONFIG_H
|
|
1329
|
+
#define CONFIG_H
|
|
1330
|
+
#define MY_FEATURE 1
|
|
1331
|
+
#endif
|
|
1332
|
+
`,
|
|
1333
|
+
);
|
|
1334
|
+
|
|
1335
|
+
// Create a header with #if MACRO != 0 pattern that needs preprocessing
|
|
1336
|
+
writeFileSync(
|
|
1337
|
+
join(includeDir, "feature.h"),
|
|
1338
|
+
`
|
|
1339
|
+
#ifndef FEATURE_H
|
|
1340
|
+
#define FEATURE_H
|
|
1341
|
+
#include "config.h"
|
|
1342
|
+
|
|
1343
|
+
void always_available(void);
|
|
1344
|
+
|
|
1345
|
+
#if MY_FEATURE != 0
|
|
1346
|
+
void feature_func(void);
|
|
1347
|
+
#endif
|
|
1348
|
+
#endif
|
|
1349
|
+
`,
|
|
1350
|
+
);
|
|
1351
|
+
|
|
1352
|
+
writeFileSync(
|
|
1353
|
+
join(srcDir, "main.cnx"),
|
|
1354
|
+
`
|
|
1355
|
+
#include "feature.h"
|
|
1356
|
+
void test() { always_available(); }
|
|
1357
|
+
`,
|
|
1358
|
+
);
|
|
1359
|
+
|
|
1360
|
+
const transpiler = new Transpiler({
|
|
1361
|
+
inputs: [join(srcDir, "main.cnx")],
|
|
1362
|
+
includeDirs: [includeDir],
|
|
1363
|
+
noCache: true,
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
const result = await transpiler.transpile({ kind: "files" });
|
|
1367
|
+
expect(result.success).toBe(true);
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
it("falls back to raw content when preprocessing fails (invalid include)", async () => {
|
|
1371
|
+
const srcDir = join(testDir, "src");
|
|
1372
|
+
const includeDir = join(testDir, "include");
|
|
1373
|
+
mkdirSync(srcDir, { recursive: true });
|
|
1374
|
+
mkdirSync(includeDir, { recursive: true });
|
|
1375
|
+
|
|
1376
|
+
// Create a header that includes a non-existent file (will cause preprocessing to fail)
|
|
1377
|
+
writeFileSync(
|
|
1378
|
+
join(includeDir, "broken.h"),
|
|
1379
|
+
`
|
|
1380
|
+
#ifndef BROKEN_H
|
|
1381
|
+
#define BROKEN_H
|
|
1382
|
+
#include "nonexistent_config.h"
|
|
1383
|
+
|
|
1384
|
+
#if SOME_MACRO != 0
|
|
1385
|
+
void some_func(void);
|
|
1386
|
+
#endif
|
|
1387
|
+
|
|
1388
|
+
void available_func(void);
|
|
1389
|
+
#endif
|
|
1390
|
+
`,
|
|
1391
|
+
);
|
|
1392
|
+
|
|
1393
|
+
writeFileSync(
|
|
1394
|
+
join(srcDir, "main.cnx"),
|
|
1395
|
+
`
|
|
1396
|
+
#include "broken.h"
|
|
1397
|
+
void test() { available_func(); }
|
|
1398
|
+
`,
|
|
1399
|
+
);
|
|
1400
|
+
|
|
1401
|
+
const transpiler = new Transpiler({
|
|
1402
|
+
inputs: [join(srcDir, "main.cnx")],
|
|
1403
|
+
includeDirs: [includeDir],
|
|
1404
|
+
noCache: true,
|
|
1405
|
+
});
|
|
1406
|
+
|
|
1407
|
+
// Should succeed even if preprocessing fails (falls back to raw content)
|
|
1408
|
+
const result = await transpiler.transpile({ kind: "files" });
|
|
1409
|
+
// The transpiler should still succeed and add a warning
|
|
1410
|
+
expect(result.success).toBe(true);
|
|
1411
|
+
});
|
|
1242
1412
|
});
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for needsConditionalPreprocessing detection logic
|
|
3
|
+
* Issue #945: Tests the regex pattern that determines when header files
|
|
4
|
+
* need preprocessing for conditional compilation evaluation.
|
|
5
|
+
*
|
|
6
|
+
* Since needsConditionalPreprocessing is a private method, we test it
|
|
7
|
+
* via a subclass that exposes it for testing purposes.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect } from "vitest";
|
|
11
|
+
import Transpiler from "../Transpiler";
|
|
12
|
+
import MockFileSystem from "./MockFileSystem";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Test subclass that exposes the private needsConditionalPreprocessing method
|
|
16
|
+
*/
|
|
17
|
+
class TestableTranspiler extends Transpiler {
|
|
18
|
+
testNeedsConditionalPreprocessing(content: string): boolean {
|
|
19
|
+
// Access private method via type casting
|
|
20
|
+
return (
|
|
21
|
+
this as unknown as {
|
|
22
|
+
needsConditionalPreprocessing: (s: string) => boolean;
|
|
23
|
+
}
|
|
24
|
+
).needsConditionalPreprocessing(content);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe("needsConditionalPreprocessing", () => {
|
|
29
|
+
let transpiler: TestableTranspiler;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
transpiler = new TestableTranspiler(
|
|
33
|
+
{ inputs: [], noCache: true },
|
|
34
|
+
new MockFileSystem(),
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("patterns that SHOULD trigger preprocessing", () => {
|
|
39
|
+
it("detects #if MACRO != 0", () => {
|
|
40
|
+
const content = `
|
|
41
|
+
#if FEATURE_LABEL != 0
|
|
42
|
+
void create_label(void);
|
|
43
|
+
#endif
|
|
44
|
+
`;
|
|
45
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("detects #if MACRO == 1", () => {
|
|
49
|
+
const content = `
|
|
50
|
+
#if DEBUG_ENABLED == 1
|
|
51
|
+
void debug_log(const char* msg);
|
|
52
|
+
#endif
|
|
53
|
+
`;
|
|
54
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("detects #if MACRO > 0", () => {
|
|
58
|
+
const content = `
|
|
59
|
+
#if LOG_LEVEL > 0
|
|
60
|
+
void log_debug(const char* msg);
|
|
61
|
+
#endif
|
|
62
|
+
`;
|
|
63
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("detects bare #if MACRO (truthy check)", () => {
|
|
67
|
+
const content = `
|
|
68
|
+
#if FEATURE_ENABLED
|
|
69
|
+
void do_feature(void);
|
|
70
|
+
#endif
|
|
71
|
+
`;
|
|
72
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("detects #elif MACRO != 0", () => {
|
|
76
|
+
const content = `
|
|
77
|
+
#if FEATURE_A
|
|
78
|
+
void feature_a(void);
|
|
79
|
+
#elif FEATURE_B != 0
|
|
80
|
+
void feature_b(void);
|
|
81
|
+
#endif
|
|
82
|
+
`;
|
|
83
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("detects #elif with bare macro", () => {
|
|
87
|
+
const content = `
|
|
88
|
+
#ifdef FEATURE_A
|
|
89
|
+
void feature_a(void);
|
|
90
|
+
#elif FEATURE_B
|
|
91
|
+
void feature_b(void);
|
|
92
|
+
#endif
|
|
93
|
+
`;
|
|
94
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("detects #if with arithmetic expression", () => {
|
|
98
|
+
const content = `
|
|
99
|
+
#if MAJOR_VERSION >= 2
|
|
100
|
+
void new_api(void);
|
|
101
|
+
#endif
|
|
102
|
+
`;
|
|
103
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("does not trigger for #if defined(X) && macro (starts with defined)", () => {
|
|
107
|
+
// The regex is conservative: if the line starts with defined(),
|
|
108
|
+
// we assume the parser can handle it. Complex expressions like
|
|
109
|
+
// defined(X) && MACRO > 1 are rare enough that this trade-off is acceptable.
|
|
110
|
+
const content = `
|
|
111
|
+
#if defined(FEATURE_X) && FEATURE_LEVEL > 1
|
|
112
|
+
void advanced_feature(void);
|
|
113
|
+
#endif
|
|
114
|
+
`;
|
|
115
|
+
// Current regex doesn't match this because it starts with defined()
|
|
116
|
+
// This is a known limitation, but acceptable for most real-world headers
|
|
117
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("patterns that should NOT trigger preprocessing", () => {
|
|
122
|
+
it("does not trigger for #ifdef MACRO", () => {
|
|
123
|
+
const content = `
|
|
124
|
+
#ifdef FEATURE_ENABLED
|
|
125
|
+
void do_feature(void);
|
|
126
|
+
#endif
|
|
127
|
+
`;
|
|
128
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("does not trigger for #ifndef MACRO", () => {
|
|
132
|
+
const content = `
|
|
133
|
+
#ifndef HEADER_GUARD_H
|
|
134
|
+
#define HEADER_GUARD_H
|
|
135
|
+
void foo(void);
|
|
136
|
+
#endif
|
|
137
|
+
`;
|
|
138
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("does not trigger for #if defined(MACRO)", () => {
|
|
142
|
+
const content = `
|
|
143
|
+
#if defined(FEATURE_X)
|
|
144
|
+
void feature_x(void);
|
|
145
|
+
#endif
|
|
146
|
+
`;
|
|
147
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("does not trigger for #if 0", () => {
|
|
151
|
+
const content = `
|
|
152
|
+
#if 0
|
|
153
|
+
// disabled code
|
|
154
|
+
void old_api(void);
|
|
155
|
+
#endif
|
|
156
|
+
`;
|
|
157
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(false);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("does not trigger for #if 1", () => {
|
|
161
|
+
const content = `
|
|
162
|
+
#if 1
|
|
163
|
+
void always_enabled(void);
|
|
164
|
+
#endif
|
|
165
|
+
`;
|
|
166
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(false);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("does not trigger for plain declarations without conditionals", () => {
|
|
170
|
+
const content = `
|
|
171
|
+
void foo(void);
|
|
172
|
+
void bar(int x);
|
|
173
|
+
typedef struct widget_s widget_t;
|
|
174
|
+
`;
|
|
175
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(false);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("does not trigger for #if 0 with comment", () => {
|
|
179
|
+
const content = `
|
|
180
|
+
#if 0 // disabled
|
|
181
|
+
void disabled(void);
|
|
182
|
+
#endif
|
|
183
|
+
`;
|
|
184
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(false);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("does not trigger for #if 1 with comment", () => {
|
|
188
|
+
const content = `
|
|
189
|
+
#if 1 /* always on */
|
|
190
|
+
void enabled(void);
|
|
191
|
+
#endif
|
|
192
|
+
`;
|
|
193
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(false);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe("edge cases", () => {
|
|
198
|
+
it("handles mixed ifdef and if MACRO patterns", () => {
|
|
199
|
+
const content = `
|
|
200
|
+
#ifdef PLATFORM_LINUX
|
|
201
|
+
void linux_init(void);
|
|
202
|
+
#endif
|
|
203
|
+
|
|
204
|
+
#if DEBUG_LEVEL != 0
|
|
205
|
+
void debug_init(void);
|
|
206
|
+
#endif
|
|
207
|
+
`;
|
|
208
|
+
// Should trigger because of the #if DEBUG_LEVEL != 0 pattern
|
|
209
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("handles LVGL-style config patterns", () => {
|
|
213
|
+
// Real-world pattern from LVGL headers
|
|
214
|
+
const content = `
|
|
215
|
+
#if LV_USE_LABEL != 0
|
|
216
|
+
lv_obj_t* lv_label_create(lv_obj_t* parent);
|
|
217
|
+
#endif
|
|
218
|
+
`;
|
|
219
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("handles FreeRTOS-style config patterns", () => {
|
|
223
|
+
// Real-world pattern from FreeRTOS headers
|
|
224
|
+
const content = `
|
|
225
|
+
#if configUSE_MUTEXES == 1
|
|
226
|
+
SemaphoreHandle_t xSemaphoreCreateMutex(void);
|
|
227
|
+
#endif
|
|
228
|
+
`;
|
|
229
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("handles whitespace variations in #if", () => {
|
|
233
|
+
const content = `
|
|
234
|
+
#if FEATURE_X
|
|
235
|
+
void feature(void);
|
|
236
|
+
#endif
|
|
237
|
+
`;
|
|
238
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("handles tab in #if directive", () => {
|
|
242
|
+
const content = "#if\tFEATURE_X\nvoid feature(void);\n#endif";
|
|
243
|
+
expect(transpiler.testNeedsConditionalPreprocessing(content)).toBe(true);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
});
|
|
@@ -127,6 +127,11 @@ class IndexTypeListener extends CNextListener {
|
|
|
127
127
|
continue;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
// Enum types are valid indices (ADR-054: transpile to unsigned constants)
|
|
131
|
+
if (CodeGenState.isKnownEnum(resolvedType)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
130
135
|
// Other non-integer types (e.g., string, struct) - E0852
|
|
131
136
|
const { line, column } = ParserUtils.getPosition(ctx);
|
|
132
137
|
this.analyzer.addError(line, column, "E0852", resolvedType);
|
|
@@ -277,7 +282,13 @@ class IndexTypeListener extends CNextListener {
|
|
|
277
282
|
if (TypeConstants.SIGNED_TYPES.includes(currentType)) {
|
|
278
283
|
return "bool";
|
|
279
284
|
}
|
|
280
|
-
// Array element type — strip array
|
|
285
|
+
// Array element type — strip rightmost array dimension
|
|
286
|
+
// e.g., "u8[8]" → "u8", "u8[8][4]" → "u8[8]", "u8[CONST]" → "u8"
|
|
287
|
+
const strippedType = currentType.replace(/\[[^\]]*\]$/, "");
|
|
288
|
+
if (strippedType !== currentType) {
|
|
289
|
+
return strippedType;
|
|
290
|
+
}
|
|
291
|
+
// Not an array type (e.g., struct), return as-is
|
|
281
292
|
return currentType;
|
|
282
293
|
}
|
|
283
294
|
|