c-next 0.1.3 → 0.1.4

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 CHANGED
@@ -661,42 +661,43 @@ Decisions are documented in `/docs/decisions/`:
661
661
 
662
662
  ### Implemented
663
663
 
664
- | ADR | Title | Description |
665
- | ------------------------------------------------------------ | ----------------------- | ------------------------------------------------------------ |
666
- | [ADR-001](docs/decisions/adr-001-assignment-operator.md) | Assignment Operator | `<-` for assignment, `=` for comparison |
667
- | [ADR-003](docs/decisions/adr-003-static-allocation.md) | Static Allocation | No dynamic memory after init |
668
- | [ADR-004](docs/decisions/adr-004-register-bindings.md) | Register Bindings | Type-safe hardware access |
669
- | [ADR-006](docs/decisions/adr-006-simplified-references.md) | Simplified References | Pass by reference, no pointer syntax |
670
- | [ADR-007](docs/decisions/adr-007-type-aware-bit-indexing.md) | Type-Aware Bit Indexing | Integers as bit arrays, `.length` property |
671
- | [ADR-010](docs/decisions/adr-010-c-interoperability.md) | C Interoperability | Unified ANTLR parser architecture |
672
- | [ADR-011](docs/decisions/adr-011-vscode-extension.md) | VS Code Extension | Live C preview with syntax highlighting |
673
- | [ADR-012](docs/decisions/adr-012-static-analysis.md) | Static Analysis | cppcheck integration for generated C |
674
- | [ADR-013](docs/decisions/adr-013-const-qualifier.md) | Const Qualifier | Compile-time const enforcement |
675
- | [ADR-014](docs/decisions/adr-014-structs.md) | Structs | Data containers without methods |
676
- | [ADR-015](docs/decisions/adr-015-null-state.md) | Null State | Zero initialization for all variables |
677
- | [ADR-016](docs/decisions/adr-016-scope.md) | Scope | `this.`/`global.` explicit qualification |
678
- | [ADR-017](docs/decisions/adr-017-enums.md) | Enums | Type-safe enums with C-style casting |
679
- | [ADR-030](docs/decisions/adr-030-forward-declarations.md) | Define-Before-Use | Functions must be defined before called |
680
- | [ADR-037](docs/decisions/adr-037-preprocessor.md) | Preprocessor | Flag-only defines, const for values |
681
- | [ADR-043](docs/decisions/adr-043-comments.md) | Comments | Comment preservation with MISRA compliance |
682
- | [ADR-044](docs/decisions/adr-044-primitive-types.md) | Primitive Types | Fixed-width types with `clamp`/`wrap` overflow |
683
- | [ADR-024](docs/decisions/adr-024-type-casting.md) | Type Casting | Widening implicit, narrowing uses bit indexing |
684
- | [ADR-022](docs/decisions/adr-022-conditional-expressions.md) | Conditional Expressions | Ternary with required parens, boolean condition, no nesting |
685
- | [ADR-025](docs/decisions/adr-025-switch-statements.md) | Switch Statements | Safe switch with braces, `\|\|` syntax, counted `default(n)` |
686
- | [ADR-029](docs/decisions/adr-029-function-pointers.md) | Callbacks | Function-as-Type pattern with nominal typing |
687
- | [ADR-045](docs/decisions/adr-045-string-type.md) | Bounded Strings | `string<N>` with compile-time safety |
688
- | [ADR-023](docs/decisions/adr-023-sizeof.md) | Sizeof | Type/value size queries with safety checks |
689
- | [ADR-027](docs/decisions/adr-027-do-while.md) | Do-While | `do { } while ()` with boolean condition (E0701) |
690
- | [ADR-032](docs/decisions/adr-032-nested-structs.md) | Nested Structs | Named nested structs only (no anonymous) |
691
- | [ADR-035](docs/decisions/adr-035-array-initializers.md) | Array Initializers | `[1, 2, 3]` syntax with `[0*]` fill-all |
692
- | [ADR-036](docs/decisions/adr-036-multidimensional-arrays.md) | Multi-dim Arrays | `arr[i][j]` with compile-time bounds enforcement |
693
- | [ADR-040](docs/decisions/adr-040-isr-declaration.md) | ISR Type | Built-in `ISR` type for `void(void)` function pointers |
694
- | [ADR-034](docs/decisions/adr-034-bit-fields.md) | Bitmap Types | `bitmap8`/`bitmap16`/`bitmap32` for portable bit-packed data |
695
- | [ADR-048](docs/decisions/adr-048-cli-executable.md) | CLI Executable | `cnext` command with smart defaults |
696
- | [ADR-049](docs/decisions/adr-049-atomic-types.md) | Atomic Types | `atomic` keyword with LDREX/STREX or PRIMASK fallback |
697
- | [ADR-050](docs/decisions/adr-050-critical-sections.md) | Critical Sections | `critical { }` blocks with PRIMASK save/restore |
698
- | [ADR-108](docs/decisions/adr-108-volatile-keyword.md) | Volatile Variables | `volatile` keyword prevents compiler optimization |
699
- | [ADR-047](docs/decisions/adr-047-nullable-types.md) | NULL for C Interop | `NULL` keyword for C stream function comparisons |
664
+ | ADR | Title | Description |
665
+ | -------------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------ |
666
+ | [ADR-001](docs/decisions/adr-001-assignment-operator.md) | Assignment Operator | `<-` for assignment, `=` for comparison |
667
+ | [ADR-003](docs/decisions/adr-003-static-allocation.md) | Static Allocation | No dynamic memory after init |
668
+ | [ADR-004](docs/decisions/adr-004-register-bindings.md) | Register Bindings | Type-safe hardware access |
669
+ | [ADR-006](docs/decisions/adr-006-simplified-references.md) | Simplified References | Pass by reference, no pointer syntax |
670
+ | [ADR-007](docs/decisions/adr-007-type-aware-bit-indexing.md) | Type-Aware Bit Indexing | Integers as bit arrays, `.length` property |
671
+ | [ADR-010](docs/decisions/adr-010-c-interoperability.md) | C Interoperability | Unified ANTLR parser architecture |
672
+ | [ADR-011](docs/decisions/adr-011-vscode-extension.md) | VS Code Extension | Live C preview with syntax highlighting |
673
+ | [ADR-012](docs/decisions/adr-012-static-analysis.md) | Static Analysis | cppcheck integration for generated C |
674
+ | [ADR-013](docs/decisions/adr-013-const-qualifier.md) | Const Qualifier | Compile-time const enforcement |
675
+ | [ADR-014](docs/decisions/adr-014-structs.md) | Structs | Data containers without methods |
676
+ | [ADR-015](docs/decisions/adr-015-null-state.md) | Null State | Zero initialization for all variables |
677
+ | [ADR-016](docs/decisions/adr-016-scope.md) | Scope | `this.`/`global.` explicit qualification |
678
+ | [ADR-017](docs/decisions/adr-017-enums.md) | Enums | Type-safe enums with C-style casting |
679
+ | [ADR-030](docs/decisions/adr-030-forward-declarations.md) | Define-Before-Use | Functions must be defined before called |
680
+ | [ADR-037](docs/decisions/adr-037-preprocessor.md) | Preprocessor | Flag-only defines, const for values |
681
+ | [ADR-043](docs/decisions/adr-043-comments.md) | Comments | Comment preservation with MISRA compliance |
682
+ | [ADR-044](docs/decisions/adr-044-primitive-types.md) | Primitive Types | Fixed-width types with `clamp`/`wrap` overflow |
683
+ | [ADR-024](docs/decisions/adr-024-type-casting.md) | Type Casting | Widening implicit, narrowing uses bit indexing |
684
+ | [ADR-022](docs/decisions/adr-022-conditional-expressions.md) | Conditional Expressions | Ternary with required parens, boolean condition, no nesting |
685
+ | [ADR-025](docs/decisions/adr-025-switch-statements.md) | Switch Statements | Safe switch with braces, `\|\|` syntax, counted `default(n)` |
686
+ | [ADR-029](docs/decisions/adr-029-function-pointers.md) | Callbacks | Function-as-Type pattern with nominal typing |
687
+ | [ADR-045](docs/decisions/adr-045-string-type.md) | Bounded Strings | `string<N>` with compile-time safety |
688
+ | [ADR-023](docs/decisions/adr-023-sizeof.md) | Sizeof | Type/value size queries with safety checks |
689
+ | [ADR-027](docs/decisions/adr-027-do-while.md) | Do-While | `do { } while ()` with boolean condition (E0701) |
690
+ | [ADR-032](docs/decisions/adr-032-nested-structs.md) | Nested Structs | Named nested structs only (no anonymous) |
691
+ | [ADR-035](docs/decisions/adr-035-array-initializers.md) | Array Initializers | `[1, 2, 3]` syntax with `[0*]` fill-all |
692
+ | [ADR-036](docs/decisions/adr-036-multidimensional-arrays.md) | Multi-dim Arrays | `arr[i][j]` with compile-time bounds enforcement |
693
+ | [ADR-040](docs/decisions/adr-040-isr-declaration.md) | ISR Type | Built-in `ISR` type for `void(void)` function pointers |
694
+ | [ADR-034](docs/decisions/adr-034-bit-fields.md) | Bitmap Types | `bitmap8`/`bitmap16`/`bitmap32` for portable bit-packed data |
695
+ | [ADR-048](docs/decisions/adr-048-cli-executable.md) | CLI Executable | `cnext` command with smart defaults |
696
+ | [ADR-049](docs/decisions/adr-049-atomic-types.md) | Atomic Types | `atomic` keyword with LDREX/STREX or PRIMASK fallback |
697
+ | [ADR-050](docs/decisions/adr-050-critical-sections.md) | Critical Sections | `critical { }` blocks with PRIMASK save/restore |
698
+ | [ADR-108](docs/decisions/adr-108-volatile-keyword.md) | Volatile Variables | `volatile` keyword prevents compiler optimization |
699
+ | [ADR-047](docs/decisions/adr-047-nullable-types.md) | NULL for C Interop | `NULL` keyword for C stream function comparisons |
700
+ | [ADR-052](docs/decisions/adr-052-safe-numeric-literal-generation.md) | Safe Numeric Literals | `type_MIN`/`type_MAX` constants + safe hex conversion |
700
701
 
701
702
  ### Research (v1 Roadmap)
702
703
 
package/grammar/CNext.g4 CHANGED
@@ -345,8 +345,8 @@ switchCase
345
345
  caseLabel
346
346
  : qualifiedType // Enum value: EState.IDLE
347
347
  | IDENTIFIER // Const or enum member
348
- | INTEGER_LITERAL
349
- | HEX_LITERAL
348
+ | '-'? INTEGER_LITERAL // Allow negative integers
349
+ | '-'? HEX_LITERAL // Allow negative hex (e.g., -0x80)
350
350
  | BINARY_LITERAL
351
351
  | CHAR_LITERAL
352
352
  ;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c-next",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -42,6 +42,21 @@ const TYPE_MAP: Record<string, string> = {
42
42
  ISR: "ISR", // ADR-040: Interrupt Service Routine function pointer
43
43
  };
44
44
 
45
+ /**
46
+ * Maps C-Next types to wider C types for clamp helper operands
47
+ * Issue #94: Prevents silent truncation when operand exceeds target type range
48
+ */
49
+ const WIDER_TYPE_MAP: Record<string, string> = {
50
+ u8: "uint32_t",
51
+ u16: "uint32_t",
52
+ u32: "uint64_t",
53
+ u64: "uint64_t", // Already widest
54
+ i8: "int32_t",
55
+ i16: "int32_t",
56
+ i32: "int64_t",
57
+ i64: "int64_t", // Already widest
58
+ };
59
+
45
60
  /**
46
61
  * Maps C-Next assignment operators to C assignment operators
47
62
  */
@@ -329,6 +344,23 @@ export default class CodeGenerator {
329
344
  return undefined;
330
345
  }
331
346
 
347
+ /**
348
+ * Check if a type name is a known struct (C-Next or C header).
349
+ * Issue #103: Must check both local knownStructs AND SymbolTable
350
+ * for proper type chain tracking through nested C header structs.
351
+ */
352
+ private isKnownStruct(typeName: string): boolean {
353
+ // Check C-Next structs first (local definitions)
354
+ if (this.knownStructs.has(typeName)) {
355
+ return true;
356
+ }
357
+ // Check SymbolTable for C header structs
358
+ if (this.symbolTable?.getStructFields(typeName)) {
359
+ return true;
360
+ }
361
+ return false;
362
+ }
363
+
332
364
  /**
333
365
  * Check if a function is a C-Next function (uses pass-by-reference semantics).
334
366
  * Checks both internal tracking and external symbol table.
@@ -1896,7 +1928,7 @@ export default class CodeGenerator {
1896
1928
  );
1897
1929
  }
1898
1930
 
1899
- if (this.knownStructs.has(identifier)) {
1931
+ if (this.isKnownStruct(identifier)) {
1900
1932
  throw new Error(
1901
1933
  `Error: Use 'global.${identifier}' to access global struct '${identifier}' inside scope '${this.context.currentScope}'`,
1902
1934
  );
@@ -4147,7 +4179,7 @@ export default class CodeGenerator {
4147
4179
 
4148
4180
  // ADR-029: Validate callback field assignments with nominal typing
4149
4181
  const rootTypeInfo = this.context.typeRegistry.get(rootName);
4150
- if (rootTypeInfo && this.knownStructs.has(rootTypeInfo.baseType)) {
4182
+ if (rootTypeInfo && this.isKnownStruct(rootTypeInfo.baseType)) {
4151
4183
  const structType = rootTypeInfo.baseType;
4152
4184
  const callbackFieldKey = `${structType}.${memberName}`;
4153
4185
  const expectedCallbackType =
@@ -4439,7 +4471,7 @@ export default class CodeGenerator {
4439
4471
  let _lastMemberStructType: string | undefined; // Struct type containing the last member (kept for future use)
4440
4472
  const firstTypeInfo = this.context.typeRegistry.get(firstId);
4441
4473
  if (firstTypeInfo) {
4442
- currentStructType = this.knownStructs.has(firstTypeInfo.baseType)
4474
+ currentStructType = this.isKnownStruct(firstTypeInfo.baseType)
4443
4475
  ? firstTypeInfo.baseType
4444
4476
  : undefined;
4445
4477
  }
@@ -4472,7 +4504,7 @@ export default class CodeGenerator {
4472
4504
  this.structFieldArrays.get(currentStructType);
4473
4505
  lastMemberIsArray = arrayFields?.has(memberName) ?? false;
4474
4506
  // Check if this member is itself a struct
4475
- if (lastMemberType && this.knownStructs.has(lastMemberType)) {
4507
+ if (lastMemberType && this.isKnownStruct(lastMemberType)) {
4476
4508
  currentStructType = lastMemberType;
4477
4509
  } else {
4478
4510
  currentStructType = undefined;
@@ -4518,7 +4550,7 @@ export default class CodeGenerator {
4518
4550
  if (firstTypeInfo?.isArray && exprIndex === 1) {
4519
4551
  // First subscript on array - element type might be a struct
4520
4552
  const elementType = firstTypeInfo.baseType;
4521
- if (this.knownStructs.has(elementType)) {
4553
+ if (this.isKnownStruct(elementType)) {
4522
4554
  currentStructType = elementType;
4523
4555
  }
4524
4556
  }
@@ -4543,10 +4575,7 @@ export default class CodeGenerator {
4543
4575
  const fieldName = identifiers[1].getText();
4544
4576
 
4545
4577
  const structTypeInfo = this.context.typeRegistry.get(structName);
4546
- if (
4547
- structTypeInfo &&
4548
- this.knownStructs.has(structTypeInfo.baseType)
4549
- ) {
4578
+ if (structTypeInfo && this.isKnownStruct(structTypeInfo.baseType)) {
4550
4579
  const structType = structTypeInfo.baseType;
4551
4580
  const fieldDimensions =
4552
4581
  this.structFieldDimensions.get(structType);
@@ -5084,7 +5113,7 @@ export default class CodeGenerator {
5084
5113
  const fieldName = identifiers[1].getText();
5085
5114
 
5086
5115
  const structTypeInfo = this.context.typeRegistry.get(structName);
5087
- if (structTypeInfo && this.knownStructs.has(structTypeInfo.baseType)) {
5116
+ if (structTypeInfo && this.isKnownStruct(structTypeInfo.baseType)) {
5088
5117
  const structType = structTypeInfo.baseType;
5089
5118
 
5090
5119
  // Check if this field is a string array in the struct
@@ -5759,13 +5788,19 @@ export default class CodeGenerator {
5759
5788
  return ctx.IDENTIFIER()!.getText();
5760
5789
  }
5761
5790
 
5762
- // Numeric literals
5791
+ // Numeric literals (may have optional minus prefix)
5763
5792
  if (ctx.INTEGER_LITERAL()) {
5764
- return ctx.INTEGER_LITERAL()!.getText();
5793
+ const num = ctx.INTEGER_LITERAL()!.getText();
5794
+ // Check if minus token exists (first child would be '-')
5795
+ const hasNeg = ctx.children && ctx.children[0]?.getText() === "-";
5796
+ return hasNeg ? `-${num}` : num;
5765
5797
  }
5766
5798
 
5767
5799
  if (ctx.HEX_LITERAL()) {
5768
- return ctx.HEX_LITERAL()!.getText();
5800
+ const hex = ctx.HEX_LITERAL()!.getText();
5801
+ // Check if minus token exists (first child would be '-')
5802
+ const hasNeg = ctx.children && ctx.children[0]?.getText() === "-";
5803
+ return hasNeg ? `-${hex}` : hex;
5769
5804
  }
5770
5805
 
5771
5806
  if (ctx.BINARY_LITERAL()) {
@@ -5928,17 +5963,24 @@ export default class CodeGenerator {
5928
5963
  return ctx.IDENTIFIER()!.getText();
5929
5964
  }
5930
5965
  if (ctx.INTEGER_LITERAL()) {
5931
- return ctx.INTEGER_LITERAL()!.getText();
5966
+ const num = ctx.INTEGER_LITERAL()!.getText();
5967
+ // Check if minus token exists (first child would be '-')
5968
+ const hasNeg = ctx.children && ctx.children[0]?.getText() === "-";
5969
+ const value = BigInt(num);
5970
+ return String(hasNeg ? -value : value);
5932
5971
  }
5933
5972
  if (ctx.HEX_LITERAL()) {
5934
5973
  // Normalize hex to decimal for comparison
5935
5974
  const hex = ctx.HEX_LITERAL()!.getText();
5936
- return String(parseInt(hex, 16));
5975
+ // Check if minus token exists (first child would be '-')
5976
+ const hasNeg = ctx.children && ctx.children[0]?.getText() === "-";
5977
+ const value = BigInt(hex); // BigInt handles 0x prefix natively
5978
+ return String(hasNeg ? -value : value);
5937
5979
  }
5938
5980
  if (ctx.BINARY_LITERAL()) {
5939
5981
  // Normalize binary to decimal for comparison
5940
5982
  const bin = ctx.BINARY_LITERAL()!.getText();
5941
- return String(parseInt(bin.replace(/0[bB]/, ""), 2));
5983
+ return String(BigInt(bin)); // BigInt handles 0b prefix natively
5942
5984
  }
5943
5985
  if (ctx.CHAR_LITERAL()) {
5944
5986
  return ctx.CHAR_LITERAL()!.getText();
@@ -6854,7 +6896,7 @@ export default class CodeGenerator {
6854
6896
  const resolvedTypeInfo = this.context.typeRegistry.get(result);
6855
6897
  if (
6856
6898
  resolvedTypeInfo &&
6857
- this.knownStructs.has(resolvedTypeInfo.baseType)
6899
+ this.isKnownStruct(resolvedTypeInfo.baseType)
6858
6900
  ) {
6859
6901
  currentStructType = resolvedTypeInfo.baseType;
6860
6902
  }
@@ -6908,7 +6950,7 @@ export default class CodeGenerator {
6908
6950
  this.context.typeRegistry.get(currentIdentifier);
6909
6951
  if (
6910
6952
  identifierTypeInfo &&
6911
- this.knownStructs.has(identifierTypeInfo.baseType)
6953
+ this.isKnownStruct(identifierTypeInfo.baseType)
6912
6954
  ) {
6913
6955
  currentStructType = identifierTypeInfo.baseType;
6914
6956
  }
@@ -6937,7 +6979,7 @@ export default class CodeGenerator {
6937
6979
  this.context.typeRegistry.get(currentIdentifier);
6938
6980
  if (
6939
6981
  identifierTypeInfo &&
6940
- this.knownStructs.has(identifierTypeInfo.baseType)
6982
+ this.isKnownStruct(identifierTypeInfo.baseType)
6941
6983
  ) {
6942
6984
  currentStructType = identifierTypeInfo.baseType;
6943
6985
  }
@@ -6977,7 +7019,7 @@ export default class CodeGenerator {
6977
7019
  const resolvedTypeInfo = this.context.typeRegistry.get(result);
6978
7020
  if (
6979
7021
  resolvedTypeInfo &&
6980
- this.knownStructs.has(resolvedTypeInfo.baseType)
7022
+ this.isKnownStruct(resolvedTypeInfo.baseType)
6981
7023
  ) {
6982
7024
  currentStructType = resolvedTypeInfo.baseType;
6983
7025
  }
@@ -7096,7 +7138,7 @@ export default class CodeGenerator {
7096
7138
  // After consuming all array dimensions, set struct type if element is struct
7097
7139
  if (remainingArrayDims === 0 && primaryTypeInfo) {
7098
7140
  const elementType = primaryTypeInfo.baseType;
7099
- if (this.knownStructs.has(elementType)) {
7141
+ if (this.isKnownStruct(elementType)) {
7100
7142
  currentStructType = elementType;
7101
7143
  }
7102
7144
  }
@@ -7112,7 +7154,7 @@ export default class CodeGenerator {
7112
7154
  // After subscripting an array, set currentStructType if the element is a struct
7113
7155
  if (identifierTypeInfo && !currentStructType) {
7114
7156
  const elementType = identifierTypeInfo.baseType;
7115
- if (this.knownStructs.has(elementType)) {
7157
+ if (this.isKnownStruct(elementType)) {
7116
7158
  currentStructType = elementType;
7117
7159
  }
7118
7160
  }
@@ -7344,11 +7386,19 @@ export default class CodeGenerator {
7344
7386
  return id;
7345
7387
  }
7346
7388
  if (ctx.literal()) {
7347
- const literalText = ctx.literal()!.getText();
7389
+ let literalText = ctx.literal()!.getText();
7348
7390
  // Track boolean literal usage to include stdbool.h
7349
7391
  if (literalText === "true" || literalText === "false") {
7350
7392
  this.needsStdbool = true;
7351
7393
  }
7394
+ // ADR-024: Transform C-Next float suffixes to standard C syntax
7395
+ // 3.14f32 -> 3.14f (C float)
7396
+ // 3.14f64 -> 3.14 (C double, no suffix needed)
7397
+ if (/[fF]32$/.test(literalText)) {
7398
+ literalText = literalText.replace(/[fF]32$/, "f");
7399
+ } else if (/[fF]64$/.test(literalText)) {
7400
+ literalText = literalText.replace(/[fF]64$/, "");
7401
+ }
7352
7402
  return literalText;
7353
7403
  }
7354
7404
  if (ctx.expression()) {
@@ -7450,7 +7500,7 @@ export default class CodeGenerator {
7450
7500
  }
7451
7501
 
7452
7502
  // Check if it's a known struct (actual type)
7453
- if (this.knownStructs.has(varName)) {
7503
+ if (this.isKnownStruct(varName)) {
7454
7504
  return `sizeof(${varName})`;
7455
7505
  }
7456
7506
 
@@ -7725,7 +7775,7 @@ export default class CodeGenerator {
7725
7775
  let lastMemberIsArray = false; // Track if last accessed member is an array
7726
7776
  const firstTypeInfo = this.context.typeRegistry.get(firstPart);
7727
7777
  if (firstTypeInfo) {
7728
- currentStructType = this.knownStructs.has(firstTypeInfo.baseType)
7778
+ currentStructType = this.isKnownStruct(firstTypeInfo.baseType)
7729
7779
  ? firstTypeInfo.baseType
7730
7780
  : undefined;
7731
7781
  }
@@ -7754,7 +7804,7 @@ export default class CodeGenerator {
7754
7804
  this.structFieldArrays.get(currentStructType);
7755
7805
  lastMemberIsArray = arrayFields?.has(memberName) ?? false;
7756
7806
  // Check if this member is itself a struct
7757
- if (lastMemberType && this.knownStructs.has(lastMemberType)) {
7807
+ if (lastMemberType && this.isKnownStruct(lastMemberType)) {
7758
7808
  currentStructType = lastMemberType;
7759
7809
  } else {
7760
7810
  currentStructType = undefined;
@@ -7795,7 +7845,7 @@ export default class CodeGenerator {
7795
7845
  if (firstTypeInfo?.isArray && exprIndex === 1) {
7796
7846
  // First subscript on array - element type might be a struct
7797
7847
  const elementType = firstTypeInfo.baseType;
7798
- if (this.knownStructs.has(elementType)) {
7848
+ if (this.isKnownStruct(elementType)) {
7799
7849
  currentStructType = elementType;
7800
7850
  }
7801
7851
  }
@@ -8363,6 +8413,7 @@ export default class CodeGenerator {
8363
8413
  cnxType: string,
8364
8414
  ): string | null {
8365
8415
  const cType = TYPE_MAP[cnxType];
8416
+ const widerType = WIDER_TYPE_MAP[cnxType] || cType;
8366
8417
  const maxValue = CodeGenerator.TYPE_MAX[cnxType];
8367
8418
  const minValue = CodeGenerator.TYPE_MIN[cnxType];
8368
8419
 
@@ -8372,16 +8423,29 @@ export default class CodeGenerator {
8372
8423
 
8373
8424
  const isUnsigned = cnxType.startsWith("u");
8374
8425
 
8426
+ // For signed types narrower than i64, use wider arithmetic to avoid UB (Issue #94)
8427
+ const useWiderArithmetic =
8428
+ !isUnsigned && widerType !== cType && cnxType !== "i64";
8429
+
8375
8430
  switch (operation) {
8376
8431
  case "add":
8377
8432
  if (isUnsigned) {
8378
- // Unsigned addition: check if result would wrap
8379
- return `static inline ${cType} cnx_clamp_add_${cnxType}(${cType} a, ${cType} b) {
8380
- if (a > ${maxValue} - b) return ${maxValue};
8381
- return a + b;
8433
+ // Unsigned addition: use wider type for b to prevent truncation (Issue #94)
8434
+ return `static inline ${cType} cnx_clamp_add_${cnxType}(${cType} a, ${widerType} b) {
8435
+ if (b > ${maxValue} - a) return ${maxValue};
8436
+ return a + (${cType})b;
8437
+ }`;
8438
+ } else if (useWiderArithmetic) {
8439
+ // Signed addition: compute in wider type, then clamp (Issue #94)
8440
+ // This avoids UB from casting out-of-range values
8441
+ return `static inline ${cType} cnx_clamp_add_${cnxType}(${cType} a, ${widerType} b) {
8442
+ ${widerType} result = (${widerType})a + b;
8443
+ if (result > ${maxValue}) return ${maxValue};
8444
+ if (result < ${minValue}) return ${minValue};
8445
+ return (${cType})result;
8382
8446
  }`;
8383
8447
  } else {
8384
- // Signed addition: check both overflow and underflow
8448
+ // i64: already widest type, use original check logic
8385
8449
  return `static inline ${cType} cnx_clamp_add_${cnxType}(${cType} a, ${cType} b) {
8386
8450
  if (b > 0 && a > ${maxValue} - b) return ${maxValue};
8387
8451
  if (b < 0 && a < ${minValue} - b) return ${minValue};
@@ -8391,13 +8455,22 @@ export default class CodeGenerator {
8391
8455
 
8392
8456
  case "sub":
8393
8457
  if (isUnsigned) {
8394
- // Unsigned subtraction: check if result would underflow
8395
- return `static inline ${cType} cnx_clamp_sub_${cnxType}(${cType} a, ${cType} b) {
8396
- if (a < b) return 0;
8397
- return a - b;
8458
+ // Unsigned subtraction: use wider type for b to prevent truncation (Issue #94)
8459
+ // Cast a to wider type for comparison to handle b > type max
8460
+ return `static inline ${cType} cnx_clamp_sub_${cnxType}(${cType} a, ${widerType} b) {
8461
+ if (b >= (${widerType})a) return 0;
8462
+ return a - (${cType})b;
8463
+ }`;
8464
+ } else if (useWiderArithmetic) {
8465
+ // Signed subtraction: compute in wider type, then clamp (Issue #94)
8466
+ return `static inline ${cType} cnx_clamp_sub_${cnxType}(${cType} a, ${widerType} b) {
8467
+ ${widerType} result = (${widerType})a - b;
8468
+ if (result > ${maxValue}) return ${maxValue};
8469
+ if (result < ${minValue}) return ${minValue};
8470
+ return (${cType})result;
8398
8471
  }`;
8399
8472
  } else {
8400
- // Signed subtraction: check both overflow and underflow
8473
+ // i64: already widest type, use original check logic
8401
8474
  return `static inline ${cType} cnx_clamp_sub_${cnxType}(${cType} a, ${cType} b) {
8402
8475
  if (b < 0 && a > ${maxValue} + b) return ${maxValue};
8403
8476
  if (b > 0 && a < ${minValue} + b) return ${minValue};
@@ -8407,13 +8480,21 @@ export default class CodeGenerator {
8407
8480
 
8408
8481
  case "mul":
8409
8482
  if (isUnsigned) {
8410
- // Unsigned multiplication
8411
- return `static inline ${cType} cnx_clamp_mul_${cnxType}(${cType} a, ${cType} b) {
8483
+ // Unsigned multiplication: use wider type for b to prevent truncation (Issue #94)
8484
+ return `static inline ${cType} cnx_clamp_mul_${cnxType}(${cType} a, ${widerType} b) {
8412
8485
  if (b != 0 && a > ${maxValue} / b) return ${maxValue};
8413
- return a * b;
8486
+ return a * (${cType})b;
8487
+ }`;
8488
+ } else if (useWiderArithmetic) {
8489
+ // Signed multiplication: compute in wider type, then clamp (Issue #94)
8490
+ return `static inline ${cType} cnx_clamp_mul_${cnxType}(${cType} a, ${widerType} b) {
8491
+ ${widerType} result = (${widerType})a * b;
8492
+ if (result > ${maxValue}) return ${maxValue};
8493
+ if (result < ${minValue}) return ${minValue};
8494
+ return (${cType})result;
8414
8495
  }`;
8415
8496
  } else {
8416
- // Signed multiplication: handle negative cases
8497
+ // i64: already widest type, use original check logic
8417
8498
  return `static inline ${cType} cnx_clamp_mul_${cnxType}(${cType} a, ${cType} b) {
8418
8499
  if (a == 0 || b == 0) return 0;
8419
8500
  if (a > 0 && b > 0 && a > ${maxValue} / b) return ${maxValue};
@@ -8437,6 +8518,7 @@ export default class CodeGenerator {
8437
8518
  cnxType: string,
8438
8519
  ): string | null {
8439
8520
  const cType = TYPE_MAP[cnxType];
8521
+ const widerType = WIDER_TYPE_MAP[cnxType] || cType;
8440
8522
  const maxValue = CodeGenerator.TYPE_MAX[cnxType];
8441
8523
  const minValue = CodeGenerator.TYPE_MIN[cnxType];
8442
8524
 
@@ -8452,17 +8534,33 @@ export default class CodeGenerator {
8452
8534
  ? "subtraction"
8453
8535
  : "multiplication";
8454
8536
 
8537
+ // For signed types narrower than i64, use wider arithmetic to avoid UB (Issue #94)
8538
+ const useWiderArithmetic =
8539
+ !isUnsigned && widerType !== cType && cnxType !== "i64";
8540
+
8455
8541
  switch (operation) {
8456
8542
  case "add":
8457
8543
  if (isUnsigned) {
8458
- return `static inline ${cType} cnx_clamp_add_${cnxType}(${cType} a, ${cType} b) {
8459
- if (a > ${maxValue} - b) {
8544
+ // Use wider type for b to prevent truncation (Issue #94)
8545
+ return `static inline ${cType} cnx_clamp_add_${cnxType}(${cType} a, ${widerType} b) {
8546
+ if (b > ${maxValue} - a) {
8460
8547
  fprintf(stderr, "PANIC: Integer overflow in ${cnxType} ${opName}\\n");
8461
8548
  abort();
8462
8549
  }
8463
- return a + b;
8550
+ return a + (${cType})b;
8551
+ }`;
8552
+ } else if (useWiderArithmetic) {
8553
+ // Signed addition: compute in wider type, check bounds (Issue #94)
8554
+ return `static inline ${cType} cnx_clamp_add_${cnxType}(${cType} a, ${widerType} b) {
8555
+ ${widerType} result = (${widerType})a + b;
8556
+ if (result > ${maxValue} || result < ${minValue}) {
8557
+ fprintf(stderr, "PANIC: Integer overflow in ${cnxType} ${opName}\\n");
8558
+ abort();
8559
+ }
8560
+ return (${cType})result;
8464
8561
  }`;
8465
8562
  } else {
8563
+ // i64: already widest type, use original check logic
8466
8564
  return `static inline ${cType} cnx_clamp_add_${cnxType}(${cType} a, ${cType} b) {
8467
8565
  if ((b > 0 && a > ${maxValue} - b) || (b < 0 && a < ${minValue} - b)) {
8468
8566
  fprintf(stderr, "PANIC: Integer overflow in ${cnxType} ${opName}\\n");
@@ -8474,14 +8572,26 @@ export default class CodeGenerator {
8474
8572
 
8475
8573
  case "sub":
8476
8574
  if (isUnsigned) {
8477
- return `static inline ${cType} cnx_clamp_sub_${cnxType}(${cType} a, ${cType} b) {
8478
- if (a < b) {
8575
+ // Use wider type for b to prevent truncation (Issue #94)
8576
+ return `static inline ${cType} cnx_clamp_sub_${cnxType}(${cType} a, ${widerType} b) {
8577
+ if (b >= (${widerType})a) {
8479
8578
  fprintf(stderr, "PANIC: Integer underflow in ${cnxType} ${opName}\\n");
8480
8579
  abort();
8481
8580
  }
8482
- return a - b;
8581
+ return a - (${cType})b;
8582
+ }`;
8583
+ } else if (useWiderArithmetic) {
8584
+ // Signed subtraction: compute in wider type, check bounds (Issue #94)
8585
+ return `static inline ${cType} cnx_clamp_sub_${cnxType}(${cType} a, ${widerType} b) {
8586
+ ${widerType} result = (${widerType})a - b;
8587
+ if (result > ${maxValue} || result < ${minValue}) {
8588
+ fprintf(stderr, "PANIC: Integer overflow in ${cnxType} ${opName}\\n");
8589
+ abort();
8590
+ }
8591
+ return (${cType})result;
8483
8592
  }`;
8484
8593
  } else {
8594
+ // i64: already widest type, use original check logic
8485
8595
  return `static inline ${cType} cnx_clamp_sub_${cnxType}(${cType} a, ${cType} b) {
8486
8596
  if ((b < 0 && a > ${maxValue} + b) || (b > 0 && a < ${minValue} + b)) {
8487
8597
  fprintf(stderr, "PANIC: Integer overflow in ${cnxType} ${opName}\\n");
@@ -8493,14 +8603,26 @@ export default class CodeGenerator {
8493
8603
 
8494
8604
  case "mul":
8495
8605
  if (isUnsigned) {
8496
- return `static inline ${cType} cnx_clamp_mul_${cnxType}(${cType} a, ${cType} b) {
8606
+ // Use wider type for b to prevent truncation (Issue #94)
8607
+ return `static inline ${cType} cnx_clamp_mul_${cnxType}(${cType} a, ${widerType} b) {
8497
8608
  if (b != 0 && a > ${maxValue} / b) {
8498
8609
  fprintf(stderr, "PANIC: Integer overflow in ${cnxType} ${opName}\\n");
8499
8610
  abort();
8500
8611
  }
8501
- return a * b;
8612
+ return a * (${cType})b;
8613
+ }`;
8614
+ } else if (useWiderArithmetic) {
8615
+ // Signed multiplication: compute in wider type, check bounds (Issue #94)
8616
+ return `static inline ${cType} cnx_clamp_mul_${cnxType}(${cType} a, ${widerType} b) {
8617
+ ${widerType} result = (${widerType})a * b;
8618
+ if (result > ${maxValue} || result < ${minValue}) {
8619
+ fprintf(stderr, "PANIC: Integer overflow in ${cnxType} ${opName}\\n");
8620
+ abort();
8621
+ }
8622
+ return (${cType})result;
8502
8623
  }`;
8503
8624
  } else {
8625
+ // i64: already widest type, use original check logic
8504
8626
  return `static inline ${cType} cnx_clamp_mul_${cnxType}(${cType} a, ${cType} b) {
8505
8627
  if (a != 0 && b != 0) {
8506
8628
  if ((a > 0 && b > 0 && a > ${maxValue} / b) ||
@@ -47,12 +47,22 @@ class TypeResolver {
47
47
  }
48
48
 
49
49
  /**
50
- * Check if a type is a user-defined struct
50
+ * Check if a type is a user-defined struct (C-Next or C header).
51
+ * Issue #103: Now checks both knownStructs AND SymbolTable.
51
52
  */
52
53
  isStructType(typeName: string): boolean {
53
- // Access CodeGenerator's knownStructs set via reference
54
+ // Check C-Next structs first
54
55
  // eslint-disable-next-line @typescript-eslint/dot-notation
55
- return this.codeGen["knownStructs"].has(typeName);
56
+ if (this.codeGen["knownStructs"].has(typeName)) {
57
+ return true;
58
+ }
59
+ // Check SymbolTable for C header structs
60
+ // eslint-disable-next-line @typescript-eslint/dot-notation
61
+ const symbolTable = this.codeGen["symbolTable"];
62
+ if (symbolTable?.getStructFields(typeName)) {
63
+ return true;
64
+ }
65
+ return false;
56
66
  }
57
67
 
58
68
  /**
@@ -340,11 +350,28 @@ class TypeResolver {
340
350
  /**
341
351
  * Get type info for a struct member field
342
352
  * Used to track types through member access chains like buf.data[0]
353
+ * Issue #103: Now checks SymbolTable first for C header structs
343
354
  */
344
355
  getMemberTypeInfo(
345
356
  structType: string,
346
357
  memberName: string,
347
358
  ): { isArray: boolean; baseType: string } | undefined {
359
+ // First check SymbolTable (C header structs) - Issue #103 fix
360
+ // eslint-disable-next-line @typescript-eslint/dot-notation
361
+ const symbolTable = this.codeGen["symbolTable"];
362
+ if (symbolTable) {
363
+ const fieldInfo = symbolTable.getStructFieldInfo(structType, memberName);
364
+ if (fieldInfo) {
365
+ return {
366
+ isArray:
367
+ fieldInfo.arrayDimensions !== undefined &&
368
+ fieldInfo.arrayDimensions.length > 0,
369
+ baseType: fieldInfo.type,
370
+ };
371
+ }
372
+ }
373
+
374
+ // Fall back to local C-Next struct fields
348
375
  // eslint-disable-next-line @typescript-eslint/dot-notation
349
376
  const fieldType = this.codeGen["structFields"]
350
377
  .get(structType)