c-next 0.2.16 → 0.2.17

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 (56) hide show
  1. package/README.md +16 -0
  2. package/dist/index.js +1347 -414
  3. package/dist/index.js.map +3 -3
  4. package/grammar/CNext.g4 +4 -0
  5. package/package.json +5 -1
  6. package/src/transpiler/Transpiler.ts +90 -22
  7. package/src/transpiler/__tests__/DualCodePaths.test.ts +1 -1
  8. package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +57 -10
  9. package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +186 -14
  10. package/src/transpiler/logic/analysis/SignedShiftAnalyzer.ts +124 -12
  11. package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +200 -0
  12. package/src/transpiler/logic/analysis/__tests__/InitializationAnalyzer.test.ts +386 -1
  13. package/src/transpiler/logic/analysis/__tests__/SignedShiftAnalyzer.test.ts +211 -0
  14. package/src/transpiler/logic/parser/grammar/CNext.interp +1 -1
  15. package/src/transpiler/logic/parser/grammar/CNextParser.ts +154 -86
  16. package/src/transpiler/logic/symbols/SymbolTable.ts +17 -2
  17. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +6 -4
  18. package/src/transpiler/logic/symbols/cnext/collectors/VariableCollector.ts +15 -2
  19. package/src/transpiler/logic/symbols/cnext/utils/TypeUtils.ts +64 -50
  20. package/src/transpiler/output/codegen/CodeGenerator.ts +151 -94
  21. package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +165 -17
  22. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +150 -34
  23. package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +2 -2
  24. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +11 -0
  25. package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +26 -7
  26. package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +86 -0
  27. package/src/transpiler/output/codegen/generators/expressions/BinaryExprGenerator.ts +43 -24
  28. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +48 -43
  29. package/src/transpiler/output/codegen/generators/expressions/ExpressionGenerator.ts +9 -2
  30. package/src/transpiler/output/codegen/generators/expressions/__tests__/CallExprGenerator.test.ts +44 -0
  31. package/src/transpiler/output/codegen/generators/expressions/__tests__/ExpressionGenerator.test.ts +82 -1
  32. package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +17 -3
  33. package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +17 -4
  34. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +227 -32
  35. package/src/transpiler/output/codegen/helpers/SymbolLookupHelper.ts +0 -21
  36. package/src/transpiler/output/codegen/helpers/TypeGenerationHelper.ts +60 -39
  37. package/src/transpiler/output/codegen/helpers/TypeRegistrationEngine.ts +170 -36
  38. package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +37 -39
  39. package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +117 -0
  40. package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +94 -2
  41. package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +268 -1
  42. package/src/transpiler/output/codegen/helpers/__tests__/SymbolLookupHelper.test.ts +0 -64
  43. package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +101 -0
  44. package/src/transpiler/output/codegen/helpers/__tests__/VariableDeclHelper.test.ts +29 -5
  45. package/src/transpiler/output/codegen/types/ICallbackTypeInfo.ts +2 -1
  46. package/src/transpiler/output/codegen/types/IParameterInput.ts +7 -0
  47. package/src/transpiler/output/headers/BaseHeaderGenerator.ts +8 -0
  48. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +75 -0
  49. package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +280 -0
  50. package/src/transpiler/output/headers/generators/IHeaderTypeInput.ts +13 -0
  51. package/src/transpiler/output/headers/generators/generateStructHeader.ts +48 -28
  52. package/src/transpiler/state/CodeGenState.ts +71 -6
  53. package/src/transpiler/state/__tests__/CodeGenState.test.ts +253 -11
  54. package/src/utils/LiteralUtils.ts +23 -0
  55. package/src/utils/__tests__/LiteralUtils.test.ts +101 -0
  56. package/src/utils/types/IParameterSymbol.ts +1 -0
@@ -21,6 +21,7 @@ import ISignedShiftError from "./types/ISignedShiftError";
21
21
  import ParserUtils from "../../../utils/ParserUtils";
22
22
  import TypeConstants from "../../../utils/constants/TypeConstants";
23
23
  import ExpressionUtils from "../../../utils/ExpressionUtils";
24
+ import CodeGenState from "../../state/CodeGenState";
24
25
 
25
26
  /**
26
27
  * First pass: Collect variable declarations with their types
@@ -28,25 +29,36 @@ import ExpressionUtils from "../../../utils/ExpressionUtils";
28
29
  class SignedVariableCollector extends CNextListener {
29
30
  private readonly signedVars: Set<string> = new Set();
30
31
 
32
+ // Track all variable types (for resolving struct member chains)
33
+ private readonly varTypes: Map<string, string> = new Map();
34
+
31
35
  public getSignedVars(): Set<string> {
32
36
  return this.signedVars;
33
37
  }
34
38
 
39
+ public getVarTypes(): Map<string, string> {
40
+ return this.varTypes;
41
+ }
42
+
35
43
  /**
36
- * Track a typed identifier if it has a signed type
44
+ * Track a typed identifier - add to signedVars if signed, always track type
37
45
  */
38
- private trackIfSigned(
46
+ private trackType(
39
47
  typeCtx: Parser.TypeContext | null,
40
48
  identifier: { getText(): string } | null,
41
49
  ): void {
42
- if (!typeCtx) return;
50
+ if (!typeCtx || !identifier) return;
43
51
 
44
52
  const typeName = typeCtx.getText();
45
- if (!TypeConstants.SIGNED_TYPES.includes(typeName)) return;
53
+ const varName = identifier.getText();
46
54
 
47
- if (!identifier) return;
55
+ // Always track the variable's type for member chain resolution
56
+ this.varTypes.set(varName, typeName);
48
57
 
49
- this.signedVars.add(identifier.getText());
58
+ // Also track in signedVars if it's a signed type
59
+ if (TypeConstants.SIGNED_TYPES.includes(typeName)) {
60
+ this.signedVars.add(varName);
61
+ }
50
62
  }
51
63
 
52
64
  /**
@@ -55,21 +67,21 @@ class SignedVariableCollector extends CNextListener {
55
67
  override enterVariableDeclaration = (
56
68
  ctx: Parser.VariableDeclarationContext,
57
69
  ): void => {
58
- this.trackIfSigned(ctx.type(), ctx.IDENTIFIER());
70
+ this.trackType(ctx.type(), ctx.IDENTIFIER());
59
71
  };
60
72
 
61
73
  /**
62
74
  * Track function parameters with signed types
63
75
  */
64
76
  override enterParameter = (ctx: Parser.ParameterContext): void => {
65
- this.trackIfSigned(ctx.type(), ctx.IDENTIFIER());
77
+ this.trackType(ctx.type(), ctx.IDENTIFIER());
66
78
  };
67
79
 
68
80
  /**
69
81
  * Track for-loop variable declarations with signed types
70
82
  */
71
83
  override enterForVarDecl = (ctx: Parser.ForVarDeclContext): void => {
72
- this.trackIfSigned(ctx.type(), ctx.IDENTIFIER());
84
+ this.trackType(ctx.type(), ctx.IDENTIFIER());
73
85
  };
74
86
  }
75
87
 
@@ -82,10 +94,18 @@ class SignedShiftListener extends CNextListener {
82
94
  // eslint-disable-next-line @typescript-eslint/lines-between-class-members
83
95
  private readonly signedVars: Set<string>;
84
96
 
85
- constructor(analyzer: SignedShiftAnalyzer, signedVars: Set<string>) {
97
+ // eslint-disable-next-line @typescript-eslint/lines-between-class-members
98
+ private readonly varTypes: Map<string, string>;
99
+
100
+ constructor(
101
+ analyzer: SignedShiftAnalyzer,
102
+ signedVars: Set<string>,
103
+ varTypes: Map<string, string>,
104
+ ) {
86
105
  super();
87
106
  this.analyzer = analyzer;
88
107
  this.signedVars = signedVars;
108
+ this.varTypes = varTypes;
89
109
  }
90
110
 
91
111
  /**
@@ -116,6 +136,97 @@ class SignedShiftListener extends CNextListener {
116
136
  }
117
137
  };
118
138
 
139
+ /**
140
+ * Check compound shift-assign statements for signed targets
141
+ * assignmentStatement: assignmentTarget assignmentOperator expression ';'
142
+ * Issue #1008: <<<- and >><- must also be rejected on signed types
143
+ *
144
+ * Handles both simple identifiers (x <<<- 2) and member chains (s.x <<<- 2)
145
+ */
146
+ override enterAssignmentStatement = (
147
+ ctx: Parser.AssignmentStatementContext,
148
+ ): void => {
149
+ const opCtx = ctx.assignmentOperator();
150
+ if (!opCtx) return;
151
+
152
+ const isLeftShiftAssign = opCtx.LSHIFT_ASSIGN() !== null;
153
+ const isRightShiftAssign = opCtx.RSHIFT_ASSIGN() !== null;
154
+ if (!isLeftShiftAssign && !isRightShiftAssign) return;
155
+
156
+ const target = ctx.assignmentTarget();
157
+ if (!target) return;
158
+
159
+ // Get the base identifier from the assignment target
160
+ const identifier = target.IDENTIFIER();
161
+ if (!identifier) return;
162
+
163
+ const baseName = identifier.getText();
164
+ const postfixOps = target.postfixTargetOp();
165
+
166
+ // Check if the final target type is signed
167
+ if (this.isSignedTarget(baseName, postfixOps)) {
168
+ const operator = isLeftShiftAssign ? "<<<-" : ">><-";
169
+ const { line, column } = ParserUtils.getPosition(target);
170
+ this.analyzer.addError(line, column, operator);
171
+ }
172
+ };
173
+
174
+ /**
175
+ * Resolve the final type of an assignment target, handling member chains.
176
+ * Returns true if the final target is a signed type.
177
+ *
178
+ * Examples:
179
+ * - "x" with no postfix ops → check if x is signed
180
+ * - "s" with postfixOps [".x"] → check if s.x field is signed
181
+ * - "arr" with postfixOps ["[0]", ".field"] → check if field is signed
182
+ */
183
+ private isSignedTarget(
184
+ baseName: string,
185
+ postfixOps: Parser.PostfixTargetOpContext[],
186
+ ): boolean {
187
+ // Simple case: no member access, just a variable
188
+ if (postfixOps.length === 0) {
189
+ return this.signedVars.has(baseName);
190
+ }
191
+
192
+ // Member chain case: resolve through the chain
193
+ let currentType = this.varTypes.get(baseName);
194
+ if (!currentType) {
195
+ // Unknown base type - can't resolve, skip
196
+ return false;
197
+ }
198
+
199
+ // Walk through the postfix operations
200
+ for (const op of postfixOps) {
201
+ const memberIdent = op.IDENTIFIER();
202
+ if (memberIdent) {
203
+ // Member access: .fieldName
204
+ const fieldName = memberIdent.getText();
205
+ const fieldType = CodeGenState.getStructFieldType(
206
+ currentType,
207
+ fieldName,
208
+ );
209
+ if (!fieldType) {
210
+ // Unknown field - can't resolve, skip
211
+ return false;
212
+ }
213
+ currentType = fieldType;
214
+ } else {
215
+ // Array subscript: [expr] - doesn't change the base type for primitives
216
+ // For arrays like u8[4], after [i] we still have u8
217
+ // Strip array dimensions if present
218
+ const bracketIndex = currentType.indexOf("[");
219
+ if (bracketIndex !== -1) {
220
+ currentType = currentType.substring(0, bracketIndex);
221
+ }
222
+ // Otherwise keep the type as-is (e.g., bit indexing on u8)
223
+ }
224
+ }
225
+
226
+ // Check if the final resolved type is signed
227
+ return TypeConstants.SIGNED_TYPES.includes(currentType);
228
+ }
229
+
119
230
  /**
120
231
  * Check if an additive expression contains a signed type operand
121
232
  */
@@ -202,13 +313,14 @@ class SignedShiftAnalyzer {
202
313
  public analyze(tree: Parser.ProgramContext): ISignedShiftError[] {
203
314
  this.errors = [];
204
315
 
205
- // First pass: collect signed variables
316
+ // First pass: collect signed variables and all variable types
206
317
  const collector = new SignedVariableCollector();
207
318
  ParseTreeWalker.DEFAULT.walk(collector, tree);
208
319
  const signedVars = collector.getSignedVars();
320
+ const varTypes = collector.getVarTypes();
209
321
 
210
322
  // Second pass: detect shift with signed operands
211
- const listener = new SignedShiftListener(this, signedVars);
323
+ const listener = new SignedShiftListener(this, signedVars, varTypes);
212
324
  ParseTreeWalker.DEFAULT.walk(listener, tree);
213
325
 
214
326
  return this.errors;
@@ -1117,4 +1117,204 @@ describe("FunctionCallAnalyzer", () => {
1117
1117
  );
1118
1118
  });
1119
1119
  });
1120
+
1121
+ // ========================================================================
1122
+ // Issue #985: global. prefix function call validation
1123
+ // ========================================================================
1124
+
1125
+ describe("global prefix function calls (Issue #985)", () => {
1126
+ it("should detect undeclared global.func() call", () => {
1127
+ const code = `
1128
+ scope Test {
1129
+ void run() {
1130
+ u32 now <- global.millis();
1131
+ }
1132
+ }
1133
+ `;
1134
+ const tree = parse(code);
1135
+ const analyzer = new FunctionCallAnalyzer();
1136
+ const errors = analyzer.analyze(tree);
1137
+
1138
+ expect(errors).toHaveLength(1);
1139
+ expect(errors[0].code).toBe("E0422");
1140
+ expect(errors[0].message).toContain("'millis'");
1141
+ expect(errors[0].message).toContain(
1142
+ "not declared in any included header",
1143
+ );
1144
+ expect(errors[0].message).toContain("#include <Arduino.h>");
1145
+ });
1146
+
1147
+ it("should detect undeclared global.func() with unknown function", () => {
1148
+ const code = `
1149
+ scope Test {
1150
+ void run() {
1151
+ u32 result <- global.unknownFunc();
1152
+ }
1153
+ }
1154
+ `;
1155
+ const tree = parse(code);
1156
+ const analyzer = new FunctionCallAnalyzer();
1157
+ const errors = analyzer.analyze(tree);
1158
+
1159
+ expect(errors).toHaveLength(1);
1160
+ expect(errors[0].code).toBe("E0422");
1161
+ expect(errors[0].message).toContain("'unknownFunc'");
1162
+ expect(errors[0].message).toContain(
1163
+ "not declared in any included header",
1164
+ );
1165
+ expect(errors[0].message).not.toContain("#include");
1166
+ });
1167
+
1168
+ it("should allow global.func() when function is defined", () => {
1169
+ const code = `
1170
+ void helper() {
1171
+ u32 x <- 5;
1172
+ }
1173
+ scope Test {
1174
+ void run() {
1175
+ global.helper();
1176
+ }
1177
+ }
1178
+ `;
1179
+ const tree = parse(code);
1180
+ const analyzer = new FunctionCallAnalyzer();
1181
+ const errors = analyzer.analyze(tree);
1182
+
1183
+ expect(errors).toHaveLength(0);
1184
+ });
1185
+
1186
+ it("should allow global.func() when header is included", () => {
1187
+ const code = `
1188
+ #include <Arduino.h>
1189
+ scope Test {
1190
+ void run() {
1191
+ u32 now <- global.millis();
1192
+ }
1193
+ }
1194
+ `;
1195
+ const tree = parse(code);
1196
+ const analyzer = new FunctionCallAnalyzer();
1197
+ const errors = analyzer.analyze(tree);
1198
+
1199
+ expect(errors).toHaveLength(0);
1200
+ });
1201
+
1202
+ it("should allow global.Scope.method() when scope is defined", () => {
1203
+ const code = `
1204
+ scope Motor {
1205
+ public void start() {
1206
+ u32 x <- 5;
1207
+ }
1208
+ }
1209
+ scope Controller {
1210
+ void run() {
1211
+ global.Motor.start();
1212
+ }
1213
+ }
1214
+ `;
1215
+ const tree = parse(code);
1216
+ const analyzer = new FunctionCallAnalyzer();
1217
+ const errors = analyzer.analyze(tree);
1218
+
1219
+ expect(errors).toHaveLength(0);
1220
+ });
1221
+
1222
+ it("should detect undeclared global.Scope.method() call", () => {
1223
+ const code = `
1224
+ scope Motor {
1225
+ public void start() {
1226
+ u32 x <- 5;
1227
+ }
1228
+ }
1229
+ scope Controller {
1230
+ void run() {
1231
+ global.Motor.undefinedMethod();
1232
+ }
1233
+ }
1234
+ `;
1235
+ const tree = parse(code);
1236
+ const analyzer = new FunctionCallAnalyzer();
1237
+ const errors = analyzer.analyze(tree);
1238
+
1239
+ expect(errors).toHaveLength(1);
1240
+ expect(errors[0].code).toBe("E0422");
1241
+ expect(errors[0].functionName).toBe("Motor_undefinedMethod");
1242
+ expect(errors[0].message).toContain("called before definition");
1243
+ expect(errors[0].message).not.toContain(
1244
+ "not declared in any included header",
1245
+ );
1246
+ });
1247
+
1248
+ it("should NOT resolve global.helper() to scope method Test_helper", () => {
1249
+ const code = `
1250
+ scope Test {
1251
+ void helper() {
1252
+ }
1253
+ void run() {
1254
+ global.helper();
1255
+ }
1256
+ }
1257
+ `;
1258
+ const tree = parse(code);
1259
+ const symbolTable = new SymbolTable();
1260
+
1261
+ const analyzer = new FunctionCallAnalyzer();
1262
+ const errors = analyzer.analyze(tree, symbolTable);
1263
+
1264
+ expect(errors).toHaveLength(1);
1265
+ expect(errors[0].code).toBe("E0422");
1266
+ expect(errors[0].functionName).toBe("helper");
1267
+ });
1268
+
1269
+ it("should say 'called before definition' for global.func() on local function", () => {
1270
+ const code = `
1271
+ scope Test {
1272
+ void run() {
1273
+ global.helper();
1274
+ }
1275
+ }
1276
+ void helper() {
1277
+ }
1278
+ `;
1279
+ const tree = parse(code);
1280
+ const symbolTable = new SymbolTable();
1281
+
1282
+ const analyzer = new FunctionCallAnalyzer();
1283
+ const errors = analyzer.analyze(tree, symbolTable);
1284
+
1285
+ expect(errors).toHaveLength(1);
1286
+ expect(errors[0].code).toBe("E0422");
1287
+ expect(errors[0].message).toContain("called before definition");
1288
+ expect(errors[0].message).not.toContain(
1289
+ "not declared in any included header",
1290
+ );
1291
+ });
1292
+
1293
+ it("should allow global.func() for external C function", () => {
1294
+ const code = `
1295
+ scope Test {
1296
+ void run() {
1297
+ global.externalFunc();
1298
+ }
1299
+ }
1300
+ `;
1301
+ const tree = parse(code);
1302
+ const symbolTable = new SymbolTable();
1303
+ symbolTable.addCSymbol({
1304
+ name: "externalFunc",
1305
+ kind: "function",
1306
+ sourceLanguage: ESourceLanguage.C,
1307
+ sourceFile: "external.h",
1308
+ sourceLine: 1,
1309
+ isExported: true,
1310
+ type: "void",
1311
+ parameters: [],
1312
+ });
1313
+
1314
+ const analyzer = new FunctionCallAnalyzer();
1315
+ const errors = analyzer.analyze(tree, symbolTable);
1316
+
1317
+ expect(errors).toHaveLength(0);
1318
+ });
1319
+ });
1120
1320
  });