c-next 0.1.8 → 0.1.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/README.md +36 -14
- package/package.json +6 -4
- package/src/analysis/ParameterNamingAnalyzer.ts +121 -0
- package/src/analysis/types/IParameterNamingError.ts +22 -0
- package/src/codegen/CodeGenerator.ts +89 -13
- package/src/codegen/HeaderGenerator.ts +64 -61
- package/src/codegen/SymbolCollector.ts +190 -0
- package/src/codegen/generators/GeneratorRegistry.ts +15 -6
- package/src/codegen/generators/ISymbolInfo.ts +17 -0
- package/src/codegen/generators/declarationGenerators/ScopeGenerator.ts +84 -3
- package/src/codegen/generators/expressions/BinaryExprGenerator.ts +118 -19
- package/src/codegen/generators/expressions/CallExprGenerator.ts +6 -3
- package/src/codegen/generators/support/HelperGenerator.ts +49 -17
- package/src/codegen/headerGenerators/IHeaderTypeInput.ts +32 -0
- package/src/codegen/headerGenerators/generateBitmapHeader.test.ts +212 -0
- package/src/codegen/headerGenerators/generateBitmapHeader.ts +58 -0
- package/src/codegen/headerGenerators/generateEnumHeader.test.ts +167 -0
- package/src/codegen/headerGenerators/generateEnumHeader.ts +53 -0
- package/src/codegen/headerGenerators/generateStructHeader.test.ts +177 -0
- package/src/codegen/headerGenerators/generateStructHeader.ts +60 -0
- package/src/codegen/headerGenerators/index.ts +18 -0
- package/src/codegen/headerGenerators/mapType.test.ts +94 -0
- package/src/codegen/headerGenerators/mapType.ts +63 -0
- package/src/codegen/types/ICodeGeneratorOptions.ts +5 -0
- package/src/pipeline/Pipeline.ts +17 -0
- package/src/pipeline/runAnalyzers.ts +25 -7
- package/src/symbols/CNextSymbolCollector.ts +57 -10
- package/src/symbols/SymbolTable.test.ts +201 -0
- package/src/symbols/SymbolTable.ts +38 -9
package/README.md
CHANGED
|
@@ -303,36 +303,38 @@ GPIO7.DR_SET[LED_BIT] <- true; // Generates: GPIO7_DR_SET = (1 << LED_BIT);
|
|
|
303
303
|
|
|
304
304
|
### Slice Assignment for Memory Operations
|
|
305
305
|
|
|
306
|
-
Multi-byte copying with
|
|
306
|
+
Multi-byte copying with compile-time validated `memcpy` generation (Issue #234):
|
|
307
307
|
|
|
308
308
|
```cnx
|
|
309
309
|
u8 buffer[256];
|
|
310
310
|
u32 magic <- 0x12345678;
|
|
311
|
-
u8 length <- 4;
|
|
312
|
-
u32 offset <- 0;
|
|
313
311
|
|
|
314
|
-
// Copy 4 bytes from value into buffer at offset
|
|
315
|
-
buffer[
|
|
312
|
+
// Copy 4 bytes from value into buffer at offset 0
|
|
313
|
+
buffer[0, 4] <- magic;
|
|
314
|
+
|
|
315
|
+
// Named offsets using const variables
|
|
316
|
+
const u32 HEADER_OFFSET <- 0;
|
|
317
|
+
const u32 DATA_OFFSET <- 8;
|
|
318
|
+
buffer[HEADER_OFFSET, 4] <- magic;
|
|
319
|
+
buffer[DATA_OFFSET, 8] <- timestamp;
|
|
316
320
|
```
|
|
317
321
|
|
|
318
|
-
Transpiles to
|
|
322
|
+
Transpiles to direct memcpy (bounds validated at compile time):
|
|
319
323
|
|
|
320
324
|
```c
|
|
321
325
|
uint8_t buffer[256] = {0};
|
|
322
326
|
uint32_t magic = 0x12345678;
|
|
323
|
-
uint8_t length = 4;
|
|
324
|
-
uint32_t offset = 0;
|
|
325
327
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
328
|
+
memcpy(&buffer[0], &magic, 4);
|
|
329
|
+
memcpy(&buffer[8], ×tamp, 8);
|
|
329
330
|
```
|
|
330
331
|
|
|
331
332
|
**Key Features:**
|
|
332
333
|
|
|
333
|
-
-
|
|
334
|
-
-
|
|
335
|
-
-
|
|
334
|
+
- **Compile-time bounds checking** prevents buffer overflows at compile time
|
|
335
|
+
- Offset and length must be compile-time constants (literals or `const` variables)
|
|
336
|
+
- Silent runtime failures are now compile-time errors
|
|
337
|
+
- Works with struct fields: `buffer[0, 4] <- config.magic`
|
|
336
338
|
- Distinct from bit operations: array slices use `memcpy`, scalar bit ranges use bit manipulation
|
|
337
339
|
|
|
338
340
|
### Scopes (ADR-016)
|
|
@@ -773,6 +775,26 @@ npm run coverage:grammar:check # Grammar coverage with threshold check (CI)
|
|
|
773
775
|
|
|
774
776
|
**Note:** C-Next runs directly via `tsx` without a build step. The `typecheck` command validates types only and does not generate any output files.
|
|
775
777
|
|
|
778
|
+
### Formatting C-Next Files
|
|
779
|
+
|
|
780
|
+
The project includes a Prettier plugin for formatting `.cnx` files with consistent style (4-space indentation, same-line braces).
|
|
781
|
+
|
|
782
|
+
```bash
|
|
783
|
+
# Format a single file
|
|
784
|
+
npx prettier --plugin ./prettier-plugin/dist/index.js --write myfile.cnx
|
|
785
|
+
|
|
786
|
+
# Format all .cnx files in tests/
|
|
787
|
+
npx prettier --plugin ./prettier-plugin/dist/index.js --write "tests/**/*.cnx"
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
To build the plugin from source (after making changes):
|
|
791
|
+
|
|
792
|
+
```bash
|
|
793
|
+
cd prettier-plugin
|
|
794
|
+
npm install
|
|
795
|
+
npm run build
|
|
796
|
+
```
|
|
797
|
+
|
|
776
798
|
## Contributing
|
|
777
799
|
|
|
778
800
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for the complete development workflow, testing requirements, and PR process.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c-next",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"bin": {
|
|
@@ -34,7 +34,10 @@
|
|
|
34
34
|
"coverage:ids": "tsx scripts/coverage-checker.ts ids",
|
|
35
35
|
"coverage:grammar": "tsx scripts/grammar-coverage.ts report",
|
|
36
36
|
"coverage:grammar:check": "tsx scripts/grammar-coverage.ts check",
|
|
37
|
-
"coverage:grammar:console": "tsx scripts/grammar-coverage.ts console"
|
|
37
|
+
"coverage:grammar:console": "tsx scripts/grammar-coverage.ts console",
|
|
38
|
+
"unit": "vitest run",
|
|
39
|
+
"unit:watch": "vitest",
|
|
40
|
+
"test:all": "npm run unit && npm run test:q"
|
|
38
41
|
},
|
|
39
42
|
"keywords": [
|
|
40
43
|
"c",
|
|
@@ -67,11 +70,10 @@
|
|
|
67
70
|
"@types/node": "^25.0.3",
|
|
68
71
|
"antlr4ng-cli": "^2.0.0",
|
|
69
72
|
"husky": "^9.1.7",
|
|
70
|
-
"jest": "^30.2.0",
|
|
71
73
|
"lint-staged": "^16.2.7",
|
|
72
74
|
"oxlint": "^1.39.0",
|
|
73
75
|
"prettier": "^3.7.4",
|
|
74
|
-
"
|
|
76
|
+
"vitest": "^3.0.0"
|
|
75
77
|
},
|
|
76
78
|
"dependencies": {
|
|
77
79
|
"antlr4ng": "^3.0.16",
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parameter Naming Analyzer
|
|
3
|
+
* Issue #227: Validates that function parameters don't use reserved naming patterns
|
|
4
|
+
*
|
|
5
|
+
* Parameters cannot start with their containing function's name followed by underscore.
|
|
6
|
+
* This naming pattern is reserved for scope-level variables and would bypass the
|
|
7
|
+
* conflict detection heuristic in SymbolTable.detectConflict().
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { ParseTreeWalker } from "antlr4ng";
|
|
11
|
+
import { CNextListener } from "../parser/grammar/CNextListener";
|
|
12
|
+
import * as Parser from "../parser/grammar/CNextParser";
|
|
13
|
+
import IParameterNamingError from "./types/IParameterNamingError";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Pure function to check if a parameter name uses a reserved pattern
|
|
17
|
+
*
|
|
18
|
+
* @param parameterName - The parameter name to check
|
|
19
|
+
* @param functionName - The name of the containing function (without scope prefix)
|
|
20
|
+
* @returns true if the parameter name is problematic
|
|
21
|
+
*/
|
|
22
|
+
function isReservedParameterName(
|
|
23
|
+
parameterName: string,
|
|
24
|
+
functionName: string,
|
|
25
|
+
): boolean {
|
|
26
|
+
return parameterName.startsWith(functionName + "_");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Pure function to create the error message
|
|
31
|
+
*
|
|
32
|
+
* @param parameterName - The parameter name
|
|
33
|
+
* @param functionName - The containing function name
|
|
34
|
+
* @returns The formatted error message
|
|
35
|
+
*/
|
|
36
|
+
function formatParameterNamingError(
|
|
37
|
+
parameterName: string,
|
|
38
|
+
functionName: string,
|
|
39
|
+
): string {
|
|
40
|
+
return (
|
|
41
|
+
`Parameter '${parameterName}' cannot start with function name prefix ` +
|
|
42
|
+
`'${functionName}_'. This naming pattern is reserved for scope-level variables.`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Listener that walks the parse tree to find parameter naming violations
|
|
48
|
+
*/
|
|
49
|
+
class ParameterNamingListener extends CNextListener {
|
|
50
|
+
private analyzer: ParameterNamingAnalyzer;
|
|
51
|
+
|
|
52
|
+
constructor(analyzer: ParameterNamingAnalyzer) {
|
|
53
|
+
super();
|
|
54
|
+
this.analyzer = analyzer;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
override enterFunctionDeclaration = (
|
|
58
|
+
ctx: Parser.FunctionDeclarationContext,
|
|
59
|
+
): void => {
|
|
60
|
+
const functionName = ctx.IDENTIFIER().getText();
|
|
61
|
+
const paramList = ctx.parameterList();
|
|
62
|
+
|
|
63
|
+
if (paramList) {
|
|
64
|
+
for (const param of paramList.parameter()) {
|
|
65
|
+
const paramIdentifier = param.IDENTIFIER();
|
|
66
|
+
const paramName = paramIdentifier.getText();
|
|
67
|
+
// Use the identifier's position for more precise error location
|
|
68
|
+
const line = paramIdentifier.symbol.line;
|
|
69
|
+
const column = paramIdentifier.symbol.column;
|
|
70
|
+
|
|
71
|
+
if (isReservedParameterName(paramName, functionName)) {
|
|
72
|
+
this.analyzer.addError(paramName, functionName, line, column);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Analyzer for parameter naming violations
|
|
81
|
+
*/
|
|
82
|
+
class ParameterNamingAnalyzer {
|
|
83
|
+
private errors: IParameterNamingError[] = [];
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Analyze the parse tree for parameter naming violations
|
|
87
|
+
*
|
|
88
|
+
* @param tree - The parsed program AST
|
|
89
|
+
* @returns Array of errors (empty if all pass)
|
|
90
|
+
*/
|
|
91
|
+
public analyze(tree: Parser.ProgramContext): IParameterNamingError[] {
|
|
92
|
+
this.errors = [];
|
|
93
|
+
|
|
94
|
+
const listener = new ParameterNamingListener(this);
|
|
95
|
+
ParseTreeWalker.DEFAULT.walk(listener, tree);
|
|
96
|
+
|
|
97
|
+
return this.errors;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Add a parameter naming error
|
|
102
|
+
*/
|
|
103
|
+
public addError(
|
|
104
|
+
parameterName: string,
|
|
105
|
+
functionName: string,
|
|
106
|
+
line: number,
|
|
107
|
+
column: number,
|
|
108
|
+
): void {
|
|
109
|
+
this.errors.push({
|
|
110
|
+
code: "E0227",
|
|
111
|
+
parameterName,
|
|
112
|
+
functionName,
|
|
113
|
+
line,
|
|
114
|
+
column,
|
|
115
|
+
message: formatParameterNamingError(parameterName, functionName),
|
|
116
|
+
helpText: `Consider renaming to a name that doesn't start with '${functionName}_'`,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export default ParameterNamingAnalyzer;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error reported when a function parameter has a reserved naming pattern
|
|
3
|
+
* Issue #227: Parameters cannot start with their function name followed by underscore
|
|
4
|
+
*/
|
|
5
|
+
interface IParameterNamingError {
|
|
6
|
+
/** Error code (E0227) - matches issue number */
|
|
7
|
+
code: string;
|
|
8
|
+
/** Name of the parameter */
|
|
9
|
+
parameterName: string;
|
|
10
|
+
/** Name of the containing function */
|
|
11
|
+
functionName: string;
|
|
12
|
+
/** Line number where the parameter is declared */
|
|
13
|
+
line: number;
|
|
14
|
+
/** Column number where the parameter is declared */
|
|
15
|
+
column: number;
|
|
16
|
+
/** Human-readable error message */
|
|
17
|
+
message: string;
|
|
18
|
+
/** Optional help text with suggested fix */
|
|
19
|
+
helpText?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default IParameterNamingError;
|
|
@@ -1131,6 +1131,20 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
1131
1131
|
output.push(" */");
|
|
1132
1132
|
output.push("");
|
|
1133
1133
|
|
|
1134
|
+
// Issue #230: Self-include for extern "C" linkage
|
|
1135
|
+
// When file has public symbols and headers are being generated,
|
|
1136
|
+
// include own header to ensure proper C linkage
|
|
1137
|
+
if (
|
|
1138
|
+
options?.generateHeaders &&
|
|
1139
|
+
this.symbols!.hasPublicSymbols() &&
|
|
1140
|
+
this.sourcePath
|
|
1141
|
+
) {
|
|
1142
|
+
const basename = this.sourcePath.replace(/^.*[\\/]/, "");
|
|
1143
|
+
const headerName = basename.replace(/\.cnx$|\.cnext$/, ".h");
|
|
1144
|
+
output.push(`#include "${headerName}"`);
|
|
1145
|
+
output.push("");
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1134
1148
|
// Pass through #include directives from source
|
|
1135
1149
|
// C-Next does NOT hardcode any libraries - all includes must be explicit
|
|
1136
1150
|
// ADR-043: Comments before first include become file-level comments
|
|
@@ -5603,9 +5617,43 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
5603
5617
|
if (isActualArray || isISRType) {
|
|
5604
5618
|
// Check for slice assignment: array[offset, length] <- value
|
|
5605
5619
|
if (exprs.length === 2) {
|
|
5606
|
-
// Slice assignment -
|
|
5607
|
-
|
|
5608
|
-
const
|
|
5620
|
+
// Issue #234: Slice assignment requires compile-time constant offset and length
|
|
5621
|
+
// to ensure bounds safety at compile time, not runtime
|
|
5622
|
+
const offsetValue = this._tryEvaluateConstant(exprs[0]);
|
|
5623
|
+
const lengthValue = this._tryEvaluateConstant(exprs[1]);
|
|
5624
|
+
|
|
5625
|
+
const line = exprs[0].start?.line ?? arrayAccessCtx.start?.line ?? 0;
|
|
5626
|
+
|
|
5627
|
+
// Issue #234: Reject slice assignment on multi-dimensional arrays
|
|
5628
|
+
// Slice assignment is only valid on 1D arrays (the innermost dimension)
|
|
5629
|
+
// For multi-dimensional arrays like board[4][8], use board[row][offset, length]
|
|
5630
|
+
// (Note: grammar currently doesn't support this - tracked as future work)
|
|
5631
|
+
if (
|
|
5632
|
+
typeInfo?.arrayDimensions &&
|
|
5633
|
+
typeInfo.arrayDimensions.length > 1
|
|
5634
|
+
) {
|
|
5635
|
+
throw new Error(
|
|
5636
|
+
`${line}:0 Error: Slice assignment is only valid on one-dimensional arrays. ` +
|
|
5637
|
+
`'${name}' has ${typeInfo.arrayDimensions.length} dimensions. ` +
|
|
5638
|
+
`Access the innermost dimension first (e.g., ${name}[index][offset, length]).`,
|
|
5639
|
+
);
|
|
5640
|
+
}
|
|
5641
|
+
|
|
5642
|
+
// Validate offset is compile-time constant
|
|
5643
|
+
if (offsetValue === undefined) {
|
|
5644
|
+
throw new Error(
|
|
5645
|
+
`${line}:0 Error: Slice assignment offset must be a compile-time constant. ` +
|
|
5646
|
+
`Runtime offsets are not allowed to ensure bounds safety.`,
|
|
5647
|
+
);
|
|
5648
|
+
}
|
|
5649
|
+
|
|
5650
|
+
// Validate length is compile-time constant
|
|
5651
|
+
if (lengthValue === undefined) {
|
|
5652
|
+
throw new Error(
|
|
5653
|
+
`${line}:0 Error: Slice assignment length must be a compile-time constant. ` +
|
|
5654
|
+
`Runtime lengths are not allowed to ensure bounds safety.`,
|
|
5655
|
+
);
|
|
5656
|
+
}
|
|
5609
5657
|
|
|
5610
5658
|
// Compound operators not supported for slice assignment
|
|
5611
5659
|
if (cOp !== "=") {
|
|
@@ -5614,22 +5662,50 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
5614
5662
|
);
|
|
5615
5663
|
}
|
|
5616
5664
|
|
|
5617
|
-
//
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
// Generate bounds-checked memcpy
|
|
5621
|
-
// if (offset + length <= sizeof(buffer)) { memcpy(&buffer[offset], &value, length); }
|
|
5622
|
-
// Issue #213: For string parameters, use capacity for bounds checking
|
|
5623
|
-
// since sizeof(char*) gives pointer size, not buffer size
|
|
5665
|
+
// Determine buffer capacity for compile-time bounds check
|
|
5666
|
+
let capacity: number;
|
|
5624
5667
|
if (
|
|
5625
5668
|
typeInfo?.isString &&
|
|
5626
5669
|
typeInfo.stringCapacity &&
|
|
5627
5670
|
!typeInfo.isArray
|
|
5628
5671
|
) {
|
|
5629
|
-
|
|
5630
|
-
|
|
5672
|
+
capacity = typeInfo.stringCapacity + 1;
|
|
5673
|
+
} else if (typeInfo?.arrayDimensions && typeInfo.arrayDimensions[0]) {
|
|
5674
|
+
capacity = typeInfo.arrayDimensions[0];
|
|
5675
|
+
} else {
|
|
5676
|
+
// Can't determine capacity at compile time - this shouldn't happen
|
|
5677
|
+
// for properly tracked arrays, but fall back to error
|
|
5678
|
+
throw new Error(
|
|
5679
|
+
`${line}:0 Error: Cannot determine buffer size for '${name}' at compile time.`,
|
|
5680
|
+
);
|
|
5631
5681
|
}
|
|
5632
|
-
|
|
5682
|
+
|
|
5683
|
+
// Issue #234: Compile-time bounds validation
|
|
5684
|
+
if (offsetValue + lengthValue > capacity) {
|
|
5685
|
+
throw new Error(
|
|
5686
|
+
`${line}:0 Error: Slice assignment out of bounds: ` +
|
|
5687
|
+
`offset(${offsetValue}) + length(${lengthValue}) = ${offsetValue + lengthValue} ` +
|
|
5688
|
+
`exceeds buffer capacity(${capacity}) for '${name}'.`,
|
|
5689
|
+
);
|
|
5690
|
+
}
|
|
5691
|
+
|
|
5692
|
+
if (offsetValue < 0) {
|
|
5693
|
+
throw new Error(
|
|
5694
|
+
`${line}:0 Error: Slice assignment offset cannot be negative: ${offsetValue}`,
|
|
5695
|
+
);
|
|
5696
|
+
}
|
|
5697
|
+
|
|
5698
|
+
if (lengthValue <= 0) {
|
|
5699
|
+
throw new Error(
|
|
5700
|
+
`${line}:0 Error: Slice assignment length must be positive: ${lengthValue}`,
|
|
5701
|
+
);
|
|
5702
|
+
}
|
|
5703
|
+
|
|
5704
|
+
// Set flag to include string.h for memcpy
|
|
5705
|
+
this.needsString = true;
|
|
5706
|
+
|
|
5707
|
+
// Generate memcpy without runtime bounds check (already validated at compile time)
|
|
5708
|
+
return `memcpy(&${name}[${offsetValue}], &${value}, ${lengthValue});`;
|
|
5633
5709
|
}
|
|
5634
5710
|
|
|
5635
5711
|
// Normal array element assignment (single index)
|
|
@@ -8,25 +8,13 @@ import ESymbolKind from "../types/ESymbolKind";
|
|
|
8
8
|
import ESourceLanguage from "../types/ESourceLanguage";
|
|
9
9
|
import SymbolTable from "../symbols/SymbolTable";
|
|
10
10
|
import IHeaderOptions from "./types/IHeaderOptions";
|
|
11
|
+
import IHeaderTypeInput from "./headerGenerators/IHeaderTypeInput";
|
|
12
|
+
import typeUtils from "./headerGenerators/mapType";
|
|
13
|
+
import generateEnumHeader from "./headerGenerators/generateEnumHeader";
|
|
14
|
+
import generateStructHeader from "./headerGenerators/generateStructHeader";
|
|
15
|
+
import generateBitmapHeader from "./headerGenerators/generateBitmapHeader";
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
* Maps C-Next types to C types
|
|
14
|
-
*/
|
|
15
|
-
const TYPE_MAP: Record<string, string> = {
|
|
16
|
-
u8: "uint8_t",
|
|
17
|
-
u16: "uint16_t",
|
|
18
|
-
u32: "uint32_t",
|
|
19
|
-
u64: "uint64_t",
|
|
20
|
-
i8: "int8_t",
|
|
21
|
-
i16: "int16_t",
|
|
22
|
-
i32: "int32_t",
|
|
23
|
-
i64: "int64_t",
|
|
24
|
-
f32: "float",
|
|
25
|
-
f64: "double",
|
|
26
|
-
bool: "bool",
|
|
27
|
-
void: "void",
|
|
28
|
-
ISR: "ISR", // ADR-040: Interrupt Service Routine function pointer
|
|
29
|
-
};
|
|
17
|
+
const { TYPE_MAP, mapType } = typeUtils;
|
|
30
18
|
|
|
31
19
|
/**
|
|
32
20
|
* Generates C header files from symbol information
|
|
@@ -34,11 +22,17 @@ const TYPE_MAP: Record<string, string> = {
|
|
|
34
22
|
class HeaderGenerator {
|
|
35
23
|
/**
|
|
36
24
|
* Generate a header file from symbols
|
|
25
|
+
*
|
|
26
|
+
* @param symbols - Array of symbols to include in header
|
|
27
|
+
* @param filename - Output filename (used for include guard)
|
|
28
|
+
* @param options - Header generation options
|
|
29
|
+
* @param typeInput - Optional type information for full definitions (enums, structs, bitmaps)
|
|
37
30
|
*/
|
|
38
31
|
generate(
|
|
39
32
|
symbols: ISymbol[],
|
|
40
33
|
filename: string,
|
|
41
34
|
options: IHeaderOptions = {},
|
|
35
|
+
typeInput?: IHeaderTypeInput,
|
|
42
36
|
): string {
|
|
43
37
|
const lines: string[] = [];
|
|
44
38
|
const guard = this.makeGuard(filename, options.guardPrefix);
|
|
@@ -86,17 +80,22 @@ class HeaderGenerator {
|
|
|
86
80
|
);
|
|
87
81
|
const enums = exportedSymbols.filter((s) => s.kind === ESymbolKind.Enum);
|
|
88
82
|
const types = exportedSymbols.filter((s) => s.kind === ESymbolKind.Type);
|
|
83
|
+
const bitmaps = exportedSymbols.filter(
|
|
84
|
+
(s) => s.kind === ESymbolKind.Bitmap,
|
|
85
|
+
);
|
|
89
86
|
|
|
90
87
|
// Issue #121: Collect external type dependencies from function signatures
|
|
91
88
|
const localStructNames = new Set(structs.map((s) => s.name));
|
|
92
89
|
const localEnumNames = new Set(enums.map((s) => s.name));
|
|
93
90
|
const localTypeNames = new Set(types.map((s) => s.name));
|
|
91
|
+
const localBitmapNames = new Set(bitmaps.map((s) => s.name));
|
|
94
92
|
const externalTypes = this.collectExternalTypes(
|
|
95
93
|
functions,
|
|
96
94
|
variables,
|
|
97
95
|
localStructNames,
|
|
98
96
|
localEnumNames,
|
|
99
97
|
localTypeNames,
|
|
98
|
+
localBitmapNames,
|
|
100
99
|
);
|
|
101
100
|
|
|
102
101
|
// Emit forward declarations for external types
|
|
@@ -110,25 +109,52 @@ class HeaderGenerator {
|
|
|
110
109
|
lines.push("");
|
|
111
110
|
}
|
|
112
111
|
|
|
113
|
-
//
|
|
112
|
+
// Struct definitions or forward declarations
|
|
114
113
|
if (structs.length > 0 || classes.length > 0) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
114
|
+
if (typeInput) {
|
|
115
|
+
lines.push("/* Struct definitions */");
|
|
116
|
+
for (const sym of structs) {
|
|
117
|
+
lines.push(generateStructHeader(sym.name, typeInput));
|
|
118
|
+
}
|
|
119
|
+
for (const sym of classes) {
|
|
120
|
+
lines.push(generateStructHeader(sym.name, typeInput));
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
lines.push("/* Forward declarations */");
|
|
124
|
+
for (const sym of structs) {
|
|
125
|
+
lines.push(`typedef struct ${sym.name} ${sym.name};`);
|
|
126
|
+
}
|
|
127
|
+
for (const sym of classes) {
|
|
128
|
+
lines.push(`typedef struct ${sym.name} ${sym.name};`);
|
|
129
|
+
}
|
|
122
130
|
}
|
|
123
131
|
lines.push("");
|
|
124
132
|
}
|
|
125
133
|
|
|
126
|
-
// Enum
|
|
134
|
+
// Enum definitions or comments
|
|
127
135
|
if (enums.length > 0) {
|
|
128
136
|
lines.push("/* Enumerations */");
|
|
129
137
|
for (const sym of enums) {
|
|
130
|
-
|
|
131
|
-
|
|
138
|
+
if (typeInput) {
|
|
139
|
+
lines.push(generateEnumHeader(sym.name, typeInput));
|
|
140
|
+
} else {
|
|
141
|
+
lines.push(`/* Enum: ${sym.name} (see implementation for values) */`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
lines.push("");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Bitmap definitions (only when typeInput is available)
|
|
148
|
+
if (bitmaps.length > 0) {
|
|
149
|
+
lines.push("/* Bitmaps */");
|
|
150
|
+
for (const sym of bitmaps) {
|
|
151
|
+
if (typeInput) {
|
|
152
|
+
lines.push(generateBitmapHeader(sym.name, typeInput));
|
|
153
|
+
} else {
|
|
154
|
+
lines.push(
|
|
155
|
+
`/* Bitmap: ${sym.name} (see implementation for layout) */`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
132
158
|
}
|
|
133
159
|
lines.push("");
|
|
134
160
|
}
|
|
@@ -138,7 +164,7 @@ class HeaderGenerator {
|
|
|
138
164
|
lines.push("/* Type aliases */");
|
|
139
165
|
for (const sym of types) {
|
|
140
166
|
if (sym.type) {
|
|
141
|
-
const cType =
|
|
167
|
+
const cType = mapType(sym.type);
|
|
142
168
|
lines.push(`typedef ${cType} ${sym.name};`);
|
|
143
169
|
}
|
|
144
170
|
}
|
|
@@ -149,7 +175,7 @@ class HeaderGenerator {
|
|
|
149
175
|
if (variables.length > 0) {
|
|
150
176
|
lines.push("/* External variables */");
|
|
151
177
|
for (const sym of variables) {
|
|
152
|
-
const cType = sym.type ?
|
|
178
|
+
const cType = sym.type ? mapType(sym.type) : "int";
|
|
153
179
|
lines.push(`extern ${cType} ${sym.name};`);
|
|
154
180
|
}
|
|
155
181
|
lines.push("");
|
|
@@ -224,47 +250,20 @@ class HeaderGenerator {
|
|
|
224
250
|
return `${sanitized}_H`;
|
|
225
251
|
}
|
|
226
252
|
|
|
227
|
-
/**
|
|
228
|
-
* Map a C-Next type to C type
|
|
229
|
-
*/
|
|
230
|
-
private mapType(type: string): string {
|
|
231
|
-
// Check direct mapping first
|
|
232
|
-
if (TYPE_MAP[type]) {
|
|
233
|
-
return TYPE_MAP[type];
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Handle pointer types
|
|
237
|
-
if (type.endsWith("*")) {
|
|
238
|
-
const baseType = type.slice(0, -1).trim();
|
|
239
|
-
return `${this.mapType(baseType)}*`;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Handle array types (simplified)
|
|
243
|
-
const arrayMatch = type.match(/^(\w+)\[(\d*)\]$/);
|
|
244
|
-
if (arrayMatch) {
|
|
245
|
-
const baseType = this.mapType(arrayMatch[1]);
|
|
246
|
-
const size = arrayMatch[2] || "";
|
|
247
|
-
return `${baseType}[${size}]`;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// User-defined types pass through
|
|
251
|
-
return type;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
253
|
/**
|
|
255
254
|
* Generate a function prototype from symbol info
|
|
256
255
|
* Handles all parameter edge cases per ADR-006, ADR-029, and ADR-040
|
|
257
256
|
*/
|
|
258
257
|
private generateFunctionPrototype(sym: ISymbol): string | null {
|
|
259
258
|
// Map return type from C-Next to C
|
|
260
|
-
const returnType = sym.type ?
|
|
259
|
+
const returnType = sym.type ? mapType(sym.type) : "void";
|
|
261
260
|
|
|
262
261
|
// Build parameter list with proper C semantics
|
|
263
262
|
let params = "void"; // Default for no parameters
|
|
264
263
|
|
|
265
264
|
if (sym.parameters && sym.parameters.length > 0) {
|
|
266
265
|
const translatedParams = sym.parameters.map((p) => {
|
|
267
|
-
const baseType =
|
|
266
|
+
const baseType = mapType(p.type);
|
|
268
267
|
const constMod = p.isConst ? "const " : "";
|
|
269
268
|
|
|
270
269
|
// Handle array parameters (pass naturally as pointers per C semantics)
|
|
@@ -301,7 +300,7 @@ class HeaderGenerator {
|
|
|
301
300
|
* Issue #121: Collect external type dependencies from function signatures and variables
|
|
302
301
|
* Returns types that are:
|
|
303
302
|
* - Not primitive types (not in TYPE_MAP)
|
|
304
|
-
* - Not locally defined structs, enums, or type aliases
|
|
303
|
+
* - Not locally defined structs, enums, bitmaps, or type aliases
|
|
305
304
|
*/
|
|
306
305
|
private collectExternalTypes(
|
|
307
306
|
functions: ISymbol[],
|
|
@@ -309,6 +308,7 @@ class HeaderGenerator {
|
|
|
309
308
|
localStructs: Set<string>,
|
|
310
309
|
localEnums: Set<string>,
|
|
311
310
|
localTypes: Set<string>,
|
|
311
|
+
localBitmaps: Set<string>,
|
|
312
312
|
): Set<string> {
|
|
313
313
|
const externalTypes = new Set<string>();
|
|
314
314
|
|
|
@@ -328,6 +328,9 @@ class HeaderGenerator {
|
|
|
328
328
|
if (localTypes.has(typeName)) {
|
|
329
329
|
return false;
|
|
330
330
|
}
|
|
331
|
+
if (localBitmaps.has(typeName)) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
331
334
|
|
|
332
335
|
// Skip pointer markers and array brackets
|
|
333
336
|
if (typeName === "" || typeName === "*") {
|