c-next 0.1.0

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 (72) hide show
  1. package/README.md +726 -0
  2. package/bin/cnext.js +5 -0
  3. package/grammar/C.g4 +1112 -0
  4. package/grammar/CNext.g4 +817 -0
  5. package/grammar/CPP14Lexer.g4 +282 -0
  6. package/grammar/CPP14Parser.g4 +1072 -0
  7. package/package.json +85 -0
  8. package/src/analysis/DivisionByZeroAnalyzer.ts +378 -0
  9. package/src/analysis/FunctionCallAnalyzer.ts +526 -0
  10. package/src/analysis/InitializationAnalyzer.ts +725 -0
  11. package/src/analysis/NullCheckAnalyzer.ts +427 -0
  12. package/src/analysis/types/IDivisionByZeroError.ts +25 -0
  13. package/src/analysis/types/IFunctionCallError.ts +17 -0
  14. package/src/analysis/types/IInitializationError.ts +55 -0
  15. package/src/analysis/types/INullCheckError.ts +25 -0
  16. package/src/codegen/CodeGenerator.ts +7945 -0
  17. package/src/codegen/CommentExtractor.ts +240 -0
  18. package/src/codegen/CommentFormatter.ts +155 -0
  19. package/src/codegen/HeaderGenerator.ts +265 -0
  20. package/src/codegen/TypeResolver.ts +365 -0
  21. package/src/codegen/types/ECommentType.ts +10 -0
  22. package/src/codegen/types/IComment.ts +21 -0
  23. package/src/codegen/types/ICommentError.ts +15 -0
  24. package/src/codegen/types/TOverflowBehavior.ts +6 -0
  25. package/src/codegen/types/TParameterInfo.ts +13 -0
  26. package/src/codegen/types/TTypeConstants.ts +94 -0
  27. package/src/codegen/types/TTypeInfo.ts +22 -0
  28. package/src/index.ts +518 -0
  29. package/src/lib/IncludeDiscovery.ts +131 -0
  30. package/src/lib/InputExpansion.ts +121 -0
  31. package/src/lib/PlatformIODetector.ts +162 -0
  32. package/src/lib/transpiler.ts +439 -0
  33. package/src/lib/types/ITranspileResult.ts +80 -0
  34. package/src/parser/c/grammar/C.interp +338 -0
  35. package/src/parser/c/grammar/C.tokens +229 -0
  36. package/src/parser/c/grammar/CLexer.interp +415 -0
  37. package/src/parser/c/grammar/CLexer.tokens +229 -0
  38. package/src/parser/c/grammar/CLexer.ts +750 -0
  39. package/src/parser/c/grammar/CListener.ts +976 -0
  40. package/src/parser/c/grammar/CParser.ts +9663 -0
  41. package/src/parser/c/grammar/CVisitor.ts +626 -0
  42. package/src/parser/cpp/grammar/CPP14Lexer.interp +478 -0
  43. package/src/parser/cpp/grammar/CPP14Lexer.tokens +264 -0
  44. package/src/parser/cpp/grammar/CPP14Lexer.ts +848 -0
  45. package/src/parser/cpp/grammar/CPP14Parser.interp +492 -0
  46. package/src/parser/cpp/grammar/CPP14Parser.tokens +264 -0
  47. package/src/parser/cpp/grammar/CPP14Parser.ts +19961 -0
  48. package/src/parser/cpp/grammar/CPP14ParserListener.ts +2120 -0
  49. package/src/parser/cpp/grammar/CPP14ParserVisitor.ts +1354 -0
  50. package/src/parser/grammar/CNext.interp +340 -0
  51. package/src/parser/grammar/CNext.tokens +214 -0
  52. package/src/parser/grammar/CNextLexer.interp +374 -0
  53. package/src/parser/grammar/CNextLexer.tokens +214 -0
  54. package/src/parser/grammar/CNextLexer.ts +668 -0
  55. package/src/parser/grammar/CNextListener.ts +1020 -0
  56. package/src/parser/grammar/CNextParser.ts +9239 -0
  57. package/src/parser/grammar/CNextVisitor.ts +654 -0
  58. package/src/preprocessor/Preprocessor.ts +301 -0
  59. package/src/preprocessor/ToolchainDetector.ts +225 -0
  60. package/src/preprocessor/types/IPreprocessResult.ts +39 -0
  61. package/src/preprocessor/types/IToolchain.ts +27 -0
  62. package/src/project/FileDiscovery.ts +236 -0
  63. package/src/project/Project.ts +425 -0
  64. package/src/project/types/IProjectConfig.ts +64 -0
  65. package/src/symbols/CNextSymbolCollector.ts +326 -0
  66. package/src/symbols/CSymbolCollector.ts +457 -0
  67. package/src/symbols/CppSymbolCollector.ts +362 -0
  68. package/src/symbols/SymbolTable.ts +312 -0
  69. package/src/symbols/types/IConflict.ts +20 -0
  70. package/src/types/ESourceLanguage.ts +10 -0
  71. package/src/types/ESymbolKind.ts +20 -0
  72. package/src/types/ISymbol.ts +45 -0
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "c-next",
3
+ "version": "0.1.0",
4
+ "description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
5
+ "main": "src/index.ts",
6
+ "bin": {
7
+ "cnext": "./bin/cnext.js"
8
+ },
9
+ "scripts": {
10
+ "antlr": "antlr4ng -Dlanguage=TypeScript -visitor -listener -o src/parser grammar/CNext.g4",
11
+ "antlr:c": "antlr4ng -Dlanguage=TypeScript -visitor -listener -o src/parser/c grammar/C.g4",
12
+ "antlr:cpp:lexer": "antlr4ng -Dlanguage=TypeScript -o src/parser/cpp grammar/CPP14Lexer.g4",
13
+ "antlr:cpp:parser": "antlr4ng -Dlanguage=TypeScript -visitor -listener -o src/parser/cpp -lib src/parser/cpp/grammar grammar/CPP14Parser.g4",
14
+ "antlr:cpp": "npm run antlr:cpp:lexer && npm run antlr:cpp:parser",
15
+ "antlr:all": "npm run antlr && npm run antlr:c && npm run antlr:cpp",
16
+ "start": "tsx src/index.ts",
17
+ "dev": "tsx src/index.ts",
18
+ "test": "tsx scripts/test.js",
19
+ "test:cli": "node scripts/test-cli.js",
20
+ "test:update": "tsx scripts/test.js --update",
21
+ "analyze": "./scripts/static-analysis.sh",
22
+ "clean": "rm -rf src/parser",
23
+ "prettier:check": "prettier --check .",
24
+ "prettier:fix": "prettier --write .",
25
+ "eslint:check": "eslint src/",
26
+ "eslint:fix": "eslint src/ --fix",
27
+ "prepare": "husky",
28
+ "prepublishOnly": "npm run prettier:check && npm run eslint:check && npm test"
29
+ },
30
+ "keywords": [
31
+ "c",
32
+ "embedded",
33
+ "transpiler",
34
+ "safety",
35
+ "misra"
36
+ ],
37
+ "author": "Joshua Austill <jlaustill@gmail.com>",
38
+ "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/jlaustill/c-next.git"
42
+ },
43
+ "bugs": {
44
+ "url": "https://github.com/jlaustill/c-next/issues"
45
+ },
46
+ "homepage": "https://github.com/jlaustill/c-next#readme",
47
+ "engines": {
48
+ "node": ">=16.0.0"
49
+ },
50
+ "files": [
51
+ "bin/",
52
+ "src/",
53
+ "grammar/",
54
+ "README.md",
55
+ "LICENSE"
56
+ ],
57
+ "devDependencies": {
58
+ "@types/node": "^25.0.3",
59
+ "@typescript-eslint/eslint-plugin": "^7.18.0",
60
+ "@typescript-eslint/parser": "^7.18.0",
61
+ "antlr4ng-cli": "^2.0.0",
62
+ "eslint": "^8.57.1",
63
+ "eslint-config-airbnb-typescript": "^18.0.0",
64
+ "eslint-config-prettier": "^10.1.8",
65
+ "eslint-plugin-import": "^2.32.0",
66
+ "husky": "^9.1.7",
67
+ "jest": "^30.2.0",
68
+ "lint-staged": "^16.2.7",
69
+ "prettier": "^3.7.4",
70
+ "ts-jest": "^29.4.6"
71
+ },
72
+ "dependencies": {
73
+ "antlr4ng": "^3.0.16",
74
+ "tsx": "^4.21.0",
75
+ "typescript": "^5.9.3"
76
+ },
77
+ "lint-staged": {
78
+ "*": [
79
+ "prettier --write --ignore-unknown"
80
+ ],
81
+ "*.ts": [
82
+ "eslint --fix"
83
+ ]
84
+ }
85
+ }
@@ -0,0 +1,378 @@
1
+ /**
2
+ * Division By Zero Analyzer
3
+ * Detects division and modulo by zero at compile time (ADR-051)
4
+ *
5
+ * Implemented Phases:
6
+ * ✓ Phase 1: Literal zero detection (10 / 0, 10 % 0)
7
+ * ✓ Phase 3: Const zero detection (const u32 ZERO <- 0; x / ZERO)
8
+ *
9
+ * Future Enhancement (Phase 3+):
10
+ * - Const expression evaluation (const u32 VALUE <- 5 - 5; x / VALUE)
11
+ */
12
+
13
+ import { ParseTreeWalker } from "antlr4ng";
14
+ import { CNextListener } from "../parser/grammar/CNextListener";
15
+ import * as Parser from "../parser/grammar/CNextParser";
16
+ import { IDivisionByZeroError } from "./types/IDivisionByZeroError";
17
+
18
+ /**
19
+ * First pass: Collect const declarations that are zero
20
+ */
21
+ class ConstZeroCollector extends CNextListener {
22
+ private constZeros: Set<string> = new Set();
23
+
24
+ public getConstZeros(): Set<string> {
25
+ return this.constZeros;
26
+ }
27
+
28
+ /**
29
+ * Track const declarations
30
+ * variableDeclaration: atomicModifier? volatileModifier? constModifier? ...
31
+ */
32
+ override enterVariableDeclaration = (
33
+ ctx: Parser.VariableDeclarationContext,
34
+ ): void => {
35
+ // Only process const declarations
36
+ if (!ctx.constModifier()) {
37
+ return;
38
+ }
39
+
40
+ const identifier = ctx.IDENTIFIER();
41
+ if (!identifier) {
42
+ return;
43
+ }
44
+
45
+ const name = identifier.getText();
46
+ const expr = ctx.expression();
47
+ if (!expr) {
48
+ return;
49
+ }
50
+
51
+ // Check if the expression is a literal zero
52
+ if (this.isExpressionZero(expr)) {
53
+ this.constZeros.add(name);
54
+ }
55
+ };
56
+
57
+ /**
58
+ * Check if an expression evaluates to literal zero
59
+ * Navigate: expression -> ternaryExpression -> orExpression -> ... -> literal
60
+ */
61
+ private isExpressionZero(ctx: Parser.ExpressionContext): boolean {
62
+ // expression -> ternaryExpression
63
+ const ternary = ctx.ternaryExpression();
64
+ if (!ternary) return false;
65
+
66
+ // ternaryExpression -> orExpression
67
+ const orExprs = ternary.orExpression();
68
+ if (!orExprs || orExprs.length === 0) return false;
69
+
70
+ const orExpr = orExprs[0];
71
+
72
+ // orExpression -> andExpression
73
+ const andExprs = orExpr.andExpression();
74
+ if (!andExprs || andExprs.length === 0) return false;
75
+
76
+ const andExpr = andExprs[0];
77
+
78
+ // andExpression -> equalityExpression
79
+ const eqExprs = andExpr.equalityExpression();
80
+ if (!eqExprs || eqExprs.length === 0) return false;
81
+
82
+ const eqExpr = eqExprs[0];
83
+
84
+ // equalityExpression -> relationalExpression
85
+ const relExprs = eqExpr.relationalExpression();
86
+ if (!relExprs || relExprs.length === 0) return false;
87
+
88
+ const relExpr = relExprs[0];
89
+
90
+ // relationalExpression -> bitwiseOrExpression
91
+ const bitorExprs = relExpr.bitwiseOrExpression();
92
+ if (!bitorExprs || bitorExprs.length === 0) return false;
93
+
94
+ const bitorExpr = bitorExprs[0];
95
+
96
+ // bitwiseOrExpression -> bitwiseXorExpression
97
+ const bitxorExprs = bitorExpr.bitwiseXorExpression();
98
+ if (!bitxorExprs || bitxorExprs.length === 0) return false;
99
+
100
+ const bitxorExpr = bitxorExprs[0];
101
+
102
+ // bitwiseXorExpression -> bitwiseAndExpression
103
+ const bitandExprs = bitxorExpr.bitwiseAndExpression();
104
+ if (!bitandExprs || bitandExprs.length === 0) return false;
105
+
106
+ const bitandExpr = bitandExprs[0];
107
+
108
+ // bitwiseAndExpression -> shiftExpression
109
+ const shiftExprs = bitandExpr.shiftExpression();
110
+ if (!shiftExprs || shiftExprs.length === 0) return false;
111
+
112
+ const shiftExpr = shiftExprs[0];
113
+
114
+ // shiftExpression -> additiveExpression
115
+ const addExprs = shiftExpr.additiveExpression();
116
+ if (!addExprs || addExprs.length === 0) return false;
117
+
118
+ const addExpr = addExprs[0];
119
+
120
+ // additiveExpression -> multiplicativeExpression
121
+ const multExprs = addExpr.multiplicativeExpression();
122
+ if (!multExprs || multExprs.length === 0) return false;
123
+
124
+ const multExpr = multExprs[0];
125
+
126
+ // multiplicativeExpression -> unaryExpression
127
+ const unaryExprs = multExpr.unaryExpression();
128
+ if (!unaryExprs || unaryExprs.length === 0) return false;
129
+
130
+ const unary = unaryExprs[0];
131
+
132
+ // unaryExpression -> postfixExpression
133
+ const postfix = unary.postfixExpression();
134
+ if (!postfix) return false;
135
+
136
+ // postfixExpression -> primaryExpression
137
+ const primary = postfix.primaryExpression();
138
+ if (!primary) return false;
139
+
140
+ // primaryExpression -> literal
141
+ const literal = primary.literal();
142
+ if (!literal) return false;
143
+
144
+ return this.isLiteralZero(literal);
145
+ }
146
+
147
+ /**
148
+ * Check if a literal is zero
149
+ */
150
+ private isLiteralZero(ctx: Parser.LiteralContext): boolean {
151
+ const text = ctx.getText();
152
+
153
+ // Check integer literals
154
+ if (ctx.INTEGER_LITERAL()) {
155
+ return text === "0";
156
+ }
157
+
158
+ // Check hex literals
159
+ if (ctx.HEX_LITERAL()) {
160
+ return text === "0x0" || text === "0X0";
161
+ }
162
+
163
+ // Check binary literals
164
+ if (ctx.BINARY_LITERAL()) {
165
+ return text === "0b0" || text === "0B0";
166
+ }
167
+
168
+ // Check suffixed literals
169
+ if (ctx.SUFFIXED_DECIMAL()) {
170
+ return text.startsWith("0u") || text.startsWith("0i");
171
+ }
172
+
173
+ if (ctx.SUFFIXED_HEX()) {
174
+ return (
175
+ text.startsWith("0x0u") ||
176
+ text.startsWith("0x0i") ||
177
+ text.startsWith("0X0u") ||
178
+ text.startsWith("0X0i")
179
+ );
180
+ }
181
+
182
+ if (ctx.SUFFIXED_BINARY()) {
183
+ return (
184
+ text.startsWith("0b0u") ||
185
+ text.startsWith("0b0i") ||
186
+ text.startsWith("0B0u") ||
187
+ text.startsWith("0B0i")
188
+ );
189
+ }
190
+
191
+ return false;
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Second pass: Detect division by zero (including const identifiers)
197
+ */
198
+ class DivisionByZeroListener extends CNextListener {
199
+ private analyzer: DivisionByZeroAnalyzer;
200
+ // eslint-disable-next-line @typescript-eslint/lines-between-class-members
201
+ private constZeros: Set<string>;
202
+
203
+ constructor(analyzer: DivisionByZeroAnalyzer, constZeros: Set<string>) {
204
+ super();
205
+ this.analyzer = analyzer;
206
+ this.constZeros = constZeros;
207
+ }
208
+
209
+ /**
210
+ * Check multiplicative expressions for division/modulo by zero
211
+ * multiplicativeExpression: unaryExpression (('*' | '/' | '%') unaryExpression)*
212
+ */
213
+ override enterMultiplicativeExpression = (
214
+ ctx: Parser.MultiplicativeExpressionContext,
215
+ ): void => {
216
+ // Get all unary expressions
217
+ const operands = ctx.unaryExpression();
218
+ if (operands.length < 2) {
219
+ return; // No operator, just a single operand
220
+ }
221
+
222
+ // Check each operator and its right operand
223
+ for (let i = 0; i < operands.length - 1; i++) {
224
+ const operatorToken = ctx.getChild(i * 2 + 1); // Operators are at odd indices
225
+ if (!operatorToken) continue;
226
+
227
+ const operator = operatorToken.getText();
228
+ if (operator !== "/" && operator !== "%") {
229
+ continue; // Only check division and modulo
230
+ }
231
+
232
+ const rightOperand = operands[i + 1];
233
+ const line = rightOperand.start?.line ?? 0;
234
+ const column = rightOperand.start?.column ?? 0;
235
+
236
+ // Check if right operand is zero
237
+ if (this.isZero(rightOperand)) {
238
+ this.analyzer.addError(operator, line, column);
239
+ }
240
+ }
241
+ };
242
+
243
+ /**
244
+ * Check if a unary expression evaluates to zero
245
+ */
246
+ private isZero(ctx: Parser.UnaryExpressionContext): boolean {
247
+ // Get the postfix expression
248
+ const postfixExpr = ctx.postfixExpression();
249
+ if (!postfixExpr) {
250
+ return false;
251
+ }
252
+
253
+ // Get the primary expression
254
+ const primaryExpr = postfixExpr.primaryExpression();
255
+ if (!primaryExpr) {
256
+ return false;
257
+ }
258
+
259
+ // Check if it's a literal
260
+ const literal = primaryExpr.literal();
261
+ if (literal) {
262
+ return this.isLiteralZero(literal);
263
+ }
264
+
265
+ // Check if it's a const identifier that evaluates to zero
266
+ const identifier = primaryExpr.IDENTIFIER();
267
+ if (identifier) {
268
+ const name = identifier.getText();
269
+ return this.constZeros.has(name);
270
+ }
271
+
272
+ return false;
273
+ }
274
+
275
+ /**
276
+ * Check if a literal is zero
277
+ */
278
+ private isLiteralZero(ctx: Parser.LiteralContext): boolean {
279
+ const text = ctx.getText();
280
+
281
+ // Check integer literals
282
+ if (ctx.INTEGER_LITERAL()) {
283
+ return text === "0";
284
+ }
285
+
286
+ // Check hex literals
287
+ if (ctx.HEX_LITERAL()) {
288
+ return text === "0x0" || text === "0X0";
289
+ }
290
+
291
+ // Check binary literals
292
+ if (ctx.BINARY_LITERAL()) {
293
+ return text === "0b0" || text === "0B0";
294
+ }
295
+
296
+ // Check suffixed literals
297
+ if (ctx.SUFFIXED_DECIMAL()) {
298
+ return text.startsWith("0u") || text.startsWith("0i");
299
+ }
300
+
301
+ if (ctx.SUFFIXED_HEX()) {
302
+ return (
303
+ text.startsWith("0x0u") ||
304
+ text.startsWith("0x0i") ||
305
+ text.startsWith("0X0u") ||
306
+ text.startsWith("0X0i")
307
+ );
308
+ }
309
+
310
+ if (ctx.SUFFIXED_BINARY()) {
311
+ return (
312
+ text.startsWith("0b0u") ||
313
+ text.startsWith("0b0i") ||
314
+ text.startsWith("0B0u") ||
315
+ text.startsWith("0B0i")
316
+ );
317
+ }
318
+
319
+ return false;
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Analyzer that detects division by zero
325
+ */
326
+ export class DivisionByZeroAnalyzer {
327
+ private errors: IDivisionByZeroError[] = [];
328
+
329
+ /**
330
+ * Analyze the parse tree for division by zero
331
+ * Two-pass analysis:
332
+ * 1. Collect const declarations that are zero
333
+ * 2. Detect division/modulo by literal zero or const zero
334
+ */
335
+ public analyze(tree: Parser.ProgramContext): IDivisionByZeroError[] {
336
+ this.errors = [];
337
+
338
+ // First pass: collect const zeros
339
+ const collector = new ConstZeroCollector();
340
+ ParseTreeWalker.DEFAULT.walk(collector, tree);
341
+ const constZeros = collector.getConstZeros();
342
+
343
+ // Second pass: detect division by zero
344
+ const listener = new DivisionByZeroListener(this, constZeros);
345
+ ParseTreeWalker.DEFAULT.walk(listener, tree);
346
+
347
+ return this.errors;
348
+ }
349
+
350
+ /**
351
+ * Add a division by zero error
352
+ */
353
+ public addError(operator: string, line: number, column: number): void {
354
+ const isDivision = operator === "/";
355
+ const code = isDivision ? "E0800" : "E0802";
356
+ const opName = isDivision ? "Division" : "Modulo";
357
+
358
+ this.errors.push({
359
+ code,
360
+ operator,
361
+ line,
362
+ column,
363
+ message: `${opName} by zero`,
364
+ helpText: isDivision
365
+ ? "Consider using safe_div(output, numerator, divisor, defaultValue) for runtime safety"
366
+ : "Consider using safe_mod(output, numerator, divisor, defaultValue) for runtime safety",
367
+ });
368
+ }
369
+
370
+ /**
371
+ * Get all detected errors
372
+ */
373
+ public getErrors(): IDivisionByZeroError[] {
374
+ return this.errors;
375
+ }
376
+ }
377
+
378
+ export default DivisionByZeroAnalyzer;