c-next 0.2.17 → 0.2.18

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 (81) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +7645 -5941
  3. package/dist/index.js.map +4 -4
  4. package/grammar/CNext.g4 +8 -0
  5. package/package.json +1 -3
  6. package/src/transpiler/Transpiler.ts +286 -26
  7. package/src/transpiler/__tests__/compileCommandsDiscovery.integration.test.ts +94 -0
  8. package/src/transpiler/__tests__/externalSymbolRecovery.integration.test.ts +215 -0
  9. package/src/transpiler/logic/__tests__/detectAssemblySyntax.test.ts +116 -0
  10. package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +8 -0
  11. package/src/transpiler/logic/analysis/MixedTypeCategoryAnalyzer.ts +408 -0
  12. package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +16 -97
  13. package/src/transpiler/logic/analysis/ReturnPathAnalyzer.ts +171 -0
  14. package/src/transpiler/logic/analysis/__tests__/MixedTypeCategoryAnalyzer.test.ts +346 -0
  15. package/src/transpiler/logic/analysis/__tests__/ReturnPathAnalyzer.test.ts +232 -0
  16. package/src/transpiler/logic/analysis/runAnalyzers.ts +23 -2
  17. package/src/transpiler/logic/analysis/types/IMixedTypeCategoryError.ts +17 -0
  18. package/src/transpiler/logic/analysis/types/IReturnPathError.ts +12 -0
  19. package/src/transpiler/logic/detectAssemblySyntax.ts +80 -0
  20. package/src/transpiler/logic/parser/grammar/CNext.interp +4 -1
  21. package/src/transpiler/logic/parser/grammar/CNext.tokens +169 -167
  22. package/src/transpiler/logic/parser/grammar/CNextLexer.interp +4 -1
  23. package/src/transpiler/logic/parser/grammar/CNextLexer.tokens +169 -167
  24. package/src/transpiler/logic/parser/grammar/CNextLexer.ts +545 -541
  25. package/src/transpiler/logic/parser/grammar/CNextListener.ts +11 -0
  26. package/src/transpiler/logic/parser/grammar/CNextParser.ts +1257 -1185
  27. package/src/transpiler/logic/parser/grammar/CNextVisitor.ts +7 -0
  28. package/src/transpiler/logic/preprocessor/CompileCommandsReader.ts +332 -0
  29. package/src/transpiler/logic/preprocessor/ExternalDeclarationOracle.ts +202 -0
  30. package/src/transpiler/logic/preprocessor/Preprocessor.ts +24 -6
  31. package/src/transpiler/logic/preprocessor/ToolchainDetector.ts +42 -1
  32. package/src/transpiler/logic/preprocessor/__tests__/CompileCommandsReader.test.ts +216 -0
  33. package/src/transpiler/logic/preprocessor/__tests__/ExternalDeclarationOracle.test.ts +153 -0
  34. package/src/transpiler/logic/preprocessor/__tests__/Preprocessor.test.ts +43 -18
  35. package/src/transpiler/logic/preprocessor/__tests__/ToolchainDetector.crossCompiler.test.ts +75 -0
  36. package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/dependent.h +7 -0
  37. package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/predecessor.h +5 -0
  38. package/src/transpiler/logic/preprocessor/types/ICompileCommandsResult.ts +14 -0
  39. package/src/transpiler/logic/preprocessor/types/IPreprocessOptions.ts +17 -0
  40. package/src/transpiler/logic/symbols/SymbolTable.ts +45 -0
  41. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +49 -0
  42. package/src/transpiler/output/MisraSuppressionUtils.ts +52 -0
  43. package/src/transpiler/output/__tests__/MisraSuppressionUtils.test.ts +67 -0
  44. package/src/transpiler/output/codegen/CodeGenerator.ts +45 -4
  45. package/src/transpiler/output/codegen/TypeResolver.ts +148 -0
  46. package/src/transpiler/output/codegen/TypeValidator.ts +179 -81
  47. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +13 -1
  48. package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +93 -0
  49. package/src/transpiler/output/codegen/__tests__/TypeValidator.alwaysTrueLoop.test.ts +91 -0
  50. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +86 -14
  51. package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +13 -0
  52. package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +1 -1
  53. package/src/transpiler/output/codegen/assignment/handlers/AccessPatternHandlers.ts +20 -12
  54. package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +424 -22
  55. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +31 -18
  56. package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +7 -8
  57. package/src/transpiler/output/codegen/assignment/handlers/RegisterHandlers.ts +6 -8
  58. package/src/transpiler/output/codegen/assignment/handlers/RegisterUtils.ts +12 -10
  59. package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +3 -7
  60. package/src/transpiler/output/codegen/assignment/handlers/SpecialHandlers.ts +7 -9
  61. package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +12 -10
  62. package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +479 -11
  63. package/src/transpiler/output/codegen/generators/IOrchestrator.ts +3 -0
  64. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +28 -15
  65. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +26 -13
  66. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +129 -1
  67. package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +64 -8
  68. package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +8 -5
  69. package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +30 -2
  70. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +16 -13
  71. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +19 -0
  72. package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +57 -11
  73. package/src/transpiler/output/codegen/subscript/SubscriptClassifier.ts +30 -11
  74. package/src/transpiler/output/codegen/subscript/TSubscriptKind.ts +1 -1
  75. package/src/transpiler/output/codegen/subscript/__tests__/SubscriptClassifier.test.ts +38 -6
  76. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +65 -24
  77. package/src/transpiler/state/CodeGenState.ts +20 -16
  78. package/src/transpiler/types/ICachedFileEntry.ts +7 -0
  79. package/src/utils/cache/CacheManager.ts +13 -2
  80. package/src/utils/cache/__tests__/CacheManager.test.ts +18 -1
  81. package/src/utils/constants/TypeConstants.ts +13 -0
@@ -11,11 +11,28 @@ import IAssignmentContext from "../IAssignmentContext";
11
11
  import TAssignmentHandler from "./TAssignmentHandler";
12
12
  import CodeGenState from "../../../../state/CodeGenState";
13
13
  import TypeValidator from "../../TypeValidator";
14
- import type ICodeGenApi from "../../types/ICodeGenApi";
14
+ import type TTypeInfo from "../../types/TTypeInfo";
15
+ import CNEXT_TO_C_TYPE_MAP from "../../../../../utils/constants/TypeMappings";
16
+ import TypeResolver from "../../TypeResolver";
15
17
 
16
- /** Get typed generator reference */
17
- function gen(): ICodeGenApi {
18
- return CodeGenState.generator as ICodeGenApi;
18
+ /** Matches the unsigned C-Next integer types (u8/u16/u32/u64). */
19
+ const UNSIGNED_INT_RE = /^u(8|16|32|64)$/;
20
+ /** Matches the signed C-Next integer types (i8/i16/i32/i64). */
21
+ const SIGNED_INT_RE = /^i(8|16|32|64)$/;
22
+
23
+ /**
24
+ * Comment emitted above an unrolled slice copy so the generated C is
25
+ * self-documenting (Issue #1081). The caller emits it ONLY when an equivalent
26
+ * `memcpy` would actually violate MISRA C:2012 Rule 21.15 — the source type is
27
+ * known and differs from the destination element type, so the two `memcpy`
28
+ * pointer arguments would be incompatible. When the types match (`u32[] <- u32`),
29
+ * the source type is unknown, or a single element is written, no rule is cited.
30
+ */
31
+ function sliceUnrollComment(destCType: string, srcCType: string): string {
32
+ return (
33
+ `/* MISRA C:2012 Rule 21.15: slice copy unrolled to per-element writes ` +
34
+ `(memcpy would pass incompatible pointer types: ${destCType}* vs ${srcCType}*). */`
35
+ );
19
36
  }
20
37
 
21
38
  /**
@@ -47,13 +64,394 @@ function handleMultiDimArrayElement(ctx: IAssignmentContext): string {
47
64
  [...typeInfo.arrayDimensions],
48
65
  [...ctx.subscripts],
49
66
  line,
50
- (expr) => gen().tryEvaluateConstant(expr),
67
+ (expr) => CodeGenState.requireGenerator().tryEvaluateConstant(expr),
51
68
  );
52
69
  }
53
70
 
54
71
  return `${ctx.resolvedTarget} ${ctx.cOp} ${ctx.generatedValue};`;
55
72
  }
56
73
 
74
+ /**
75
+ * Per-element write strategy for a slice-assignment destination (Issue #1081).
76
+ *
77
+ * Slice assignment lowers to element-by-element little-endian writes rather than
78
+ * a `memcpy(&dest[off], &value, len)`. `memcpy` between a byte buffer and a
79
+ * wider integer passes incompatible pointer types, violating MISRA C:2012
80
+ * Rule 21.15; writing each element explicitly avoids the call entirely.
81
+ *
82
+ * The destination *element* type determines:
83
+ * - `bytes`: the element stride. `dest[off + k]` indexes elements, matching
84
+ * the old `&dest[off]` base address, so a u16[] slice writes whole u16s.
85
+ * - `cType`: the C element type, used to detect whether an equivalent `memcpy`
86
+ * would have violated MISRA Rule 21.15 (incompatible pointer types).
87
+ * - `wrap`: the cast applied to each extracted little-endian chunk. MISRA
88
+ * Rule 10.8 forbids casting a composite expression (the `>>` result) across
89
+ * essential-type categories, so `char` and signed destinations cast through
90
+ * a same-width unsigned type first.
91
+ *
92
+ * Throws for element types that cannot be expressed as integer byte writes
93
+ * (float/bool), which would require type punning and are unsupported.
94
+ */
95
+ function resolveSliceElement(
96
+ typeInfo: TTypeInfo | undefined,
97
+ line: number,
98
+ rawName: string,
99
+ ): { bytes: number; cType: string; wrap: (chunk: string) => string } {
100
+ if (typeInfo?.isString) {
101
+ // string buffers are char[]; cast through uint8_t to satisfy MISRA 10.8.
102
+ return {
103
+ bytes: 1,
104
+ cType: "char",
105
+ wrap: (chunk) => `(char)(uint8_t)${chunk}`,
106
+ };
107
+ }
108
+
109
+ const baseType = typeInfo?.baseType ?? "";
110
+ const bytes = Math.floor((typeInfo?.bitWidth ?? 0) / 8);
111
+
112
+ if (bytes > 0 && UNSIGNED_INT_RE.test(baseType)) {
113
+ const cType = CNEXT_TO_C_TYPE_MAP[baseType];
114
+ return { bytes, cType, wrap: (chunk) => `(${cType})${chunk}` };
115
+ }
116
+
117
+ if (bytes > 0 && SIGNED_INT_RE.test(baseType)) {
118
+ const cType = CNEXT_TO_C_TYPE_MAP[baseType];
119
+ const uType = CNEXT_TO_C_TYPE_MAP[`u${baseType.slice(1)}`];
120
+ return { bytes, cType, wrap: (chunk) => `(${cType})(${uType})${chunk}` };
121
+ }
122
+
123
+ throw new Error(
124
+ `${line}:0 Error: Slice assignment is not supported for element type ` +
125
+ `'${baseType}' of '${rawName}'. Only integer and string buffers can be sliced.`,
126
+ );
127
+ }
128
+
129
+ /** Widest source the unroll can serialize when the type can't be resolved. */
130
+ const UNRESOLVED_SOURCE_BYTES = 8;
131
+
132
+ /**
133
+ * Smallest standard unsigned C type that holds `bytes` bytes. Used to size the
134
+ * source temp for an unresolved source to the *slice length* rather than always
135
+ * the widest type — so the materializing cast is same-width (MISRA Rule 10.8
136
+ * forbids casting a composite expression such as `a + b` to a *wider* type).
137
+ */
138
+ function unsignedCTypeForBytes(bytes: number): string {
139
+ if (bytes <= 1) return "uint8_t";
140
+ if (bytes <= 2) return "uint16_t";
141
+ if (bytes <= 4) return "uint32_t";
142
+ return "uint64_t";
143
+ }
144
+
145
+ /**
146
+ * Resolve the *source* value's type for a slice assignment (Issue #1081 review).
147
+ *
148
+ * The source is materialized once into an unsigned temp before the writes
149
+ * (`buildSliceWrites`), so this drives the right-hand side of the unrolled copy:
150
+ * - non-integer sources (float/struct/string) cannot be shifted — reject them
151
+ * with a clear C-Next error instead of emitting non-compiling C;
152
+ * - `bytes` lets the caller reject `length > sizeof(source)` (an over-read that
153
+ * would otherwise emit an undefined out-of-range shift);
154
+ * - `unsignedCType` is the type of the materialized temp. Shifting that temp
155
+ * is MISRA Rule 10.1-clean (always unsigned); a signed source is cast to its
156
+ * same-width unsigned type once, in the temp declaration. It is null when the
157
+ * source type is unresolved — the caller sizes the temp to the slice length.
158
+ *
159
+ * `cType` is the source's actual C type (used to detect a Rule 21.15 mismatch
160
+ * against the destination element type). When the type can't be statically
161
+ * resolved (e.g. a computed expression or function call), `cType`/`unsignedCType`
162
+ * are null and `bytes` is the widest serializable width (8), so the caller caps
163
+ * the length and sizes the temp from the length to keep the cast Rule 10.8-clean.
164
+ */
165
+ function resolveSliceSource(
166
+ ctx: IAssignmentContext,
167
+ line: number,
168
+ rawName: string,
169
+ lengthValue: number,
170
+ ): {
171
+ cType: string | null;
172
+ bytes: number;
173
+ unsignedCType: string | null;
174
+ isComposite: boolean;
175
+ } {
176
+ // A composite source (e.g. `a + b`) is resolved through the Rule 10.4
177
+ // guarantee that its operands share a category, so its essential type is
178
+ // well-defined; `directType` tells us whether the source is composite so the
179
+ // caller can bind it to a same-type temp before the unsigned cast — keeping
180
+ // that cast off the composite itself (MISRA Rule 10.8).
181
+ const directType = ctx.valueCtx
182
+ ? TypeResolver.getExpressionType(ctx.valueCtx)
183
+ : null;
184
+
185
+ // A bare integer literal has no fixed essential category; contextually type it
186
+ // to the slice byte-width (ADR-052), with a compile-time fit check. A positive
187
+ // literal types as `int`; a negative literal (unary minus) types as null yet is
188
+ // still a compile-time constant. Fold either so an out-of-range negative is
189
+ // rejected exactly like an out-of-range positive, rather than silently
190
+ // truncated (e.g. buf[0,1] <- -300 -> (uint8_t)(-300)) (Issue #1085 review).
191
+ // A source that resolves to a fixed-width type (variable, struct field, …)
192
+ // keeps that type and is not folded here.
193
+ const literalValue =
194
+ directType === "int" || directType === null
195
+ ? ctx.valueCtx
196
+ ? CodeGenState.requireGenerator().tryEvaluateConstant(ctx.valueCtx)
197
+ : undefined
198
+ : undefined;
199
+ if (literalValue !== undefined) {
200
+ return resolveLiteralSliceSource(ctx, line, rawName, lengthValue);
201
+ }
202
+
203
+ const sourceType =
204
+ directType ??
205
+ (ctx.valueCtx ? TypeResolver.getIntegerExpressionType(ctx.valueCtx) : null);
206
+
207
+ if (sourceType === null) {
208
+ return {
209
+ cType: null,
210
+ bytes: UNRESOLVED_SOURCE_BYTES,
211
+ unsignedCType: null,
212
+ isComposite: false,
213
+ };
214
+ }
215
+
216
+ const isUnsigned = UNSIGNED_INT_RE.test(sourceType);
217
+ const isSigned = SIGNED_INT_RE.test(sourceType);
218
+ if (!isUnsigned && !isSigned) {
219
+ throw new Error(
220
+ `${line}:0 Error: Slice assignment source must be an integer value; ` +
221
+ `the value assigned to '${rawName}' has type '${sourceType}'.`,
222
+ );
223
+ }
224
+
225
+ return {
226
+ cType: CNEXT_TO_C_TYPE_MAP[sourceType],
227
+ bytes: Number.parseInt(sourceType.slice(1), 10) / 8,
228
+ // Signed sources are reinterpreted to their same-width unsigned type so
229
+ // every shift on the materialized temp satisfies MISRA Rule 10.1.
230
+ unsignedCType: isSigned
231
+ ? CNEXT_TO_C_TYPE_MAP[`u${sourceType.slice(1)}`]
232
+ : CNEXT_TO_C_TYPE_MAP[sourceType],
233
+ isComposite: directType === null,
234
+ };
235
+ }
236
+
237
+ /**
238
+ * Resolve a bare integer-literal slice source (Issue #1085 review, Finding D).
239
+ *
240
+ * A literal has no fixed essential category, so it is contextually typed to the
241
+ * slice byte-width (ADR-052): a `length`-byte slice serializes the literal as an
242
+ * unsigned value of that width. The literal must fit in `length` bytes, else a
243
+ * clear compile error — `buf[0,2] <- 0x12345678` cannot hold a 4-byte value.
244
+ */
245
+ function resolveLiteralSliceSource(
246
+ ctx: IAssignmentContext,
247
+ line: number,
248
+ rawName: string,
249
+ lengthValue: number,
250
+ ): {
251
+ cType: string | null;
252
+ bytes: number;
253
+ unsignedCType: string | null;
254
+ isComposite: boolean;
255
+ } {
256
+ const value = ctx.valueCtx
257
+ ? CodeGenState.requireGenerator().tryEvaluateConstant(ctx.valueCtx)
258
+ : undefined;
259
+ if (value !== undefined) {
260
+ // The literal is contextually typed to the slice byte-width (ADR-052), so it
261
+ // must be representable in `length` bytes — either as an unsigned value
262
+ // (0 .. 2^(8N)-1) or as a two's-complement signed value (-2^(8N-1) .. -1).
263
+ // Guarding only the unsigned upper bound let a negative literal of any
264
+ // magnitude through, silently truncating it (e.g. buf[0,1] <- -300 became
265
+ // (uint8_t)(-300)) while the mirror-image positive overflow was rejected
266
+ // (Issue #1085 review). `lengthValue` is already validated > 0 by the caller.
267
+ const truncated = BigInt(Math.trunc(value));
268
+ const unsignedUpperBound = 1n << BigInt(8 * lengthValue);
269
+ const signedLowerBound = -(1n << BigInt(8 * lengthValue - 1));
270
+ if (truncated >= unsignedUpperBound || truncated < signedLowerBound) {
271
+ throw new Error(
272
+ `${line}:0 Error: Slice assignment literal value (${value}) does not fit ` +
273
+ `in the ${lengthValue}-byte slice for '${rawName}'.`,
274
+ );
275
+ }
276
+ }
277
+
278
+ const cType = unsignedCTypeForBytes(lengthValue);
279
+ return {
280
+ cType,
281
+ bytes: lengthValue,
282
+ unsignedCType: cType,
283
+ isComposite: false,
284
+ };
285
+ }
286
+
287
+ /**
288
+ * Validate a slice's length/bounds against the destination element size and
289
+ * buffer capacity, and return the number of destination elements written.
290
+ *
291
+ * `offset` is an *element* index and `capacity` is an *element* count, while
292
+ * `length` is a *byte* count — so the bounds check compares element spans
293
+ * (`offset + elementCount`), not bytes against elements (Issue #1085 review,
294
+ * Finding 1). Mixing the two wrongly rejected in-bounds wide-element slices.
295
+ */
296
+ function validateSliceSpan(
297
+ dest: { bytes: number },
298
+ src: { bytes: number },
299
+ offsetValue: number,
300
+ lengthValue: number,
301
+ capacity: number,
302
+ line: number,
303
+ rawName: string,
304
+ ): number {
305
+ if (lengthValue % dest.bytes !== 0) {
306
+ throw new Error(
307
+ `${line}:0 Error: Slice assignment length (${lengthValue}) must be a ` +
308
+ `multiple of the element size (${dest.bytes} bytes) for '${rawName}'.`,
309
+ );
310
+ }
311
+
312
+ const elementCount = lengthValue / dest.bytes;
313
+ if (offsetValue + elementCount > capacity) {
314
+ throw new Error(
315
+ `${line}:0 Error: Slice assignment out of bounds: ` +
316
+ `offset(${offsetValue}) + ${elementCount} element(s) = ` +
317
+ `${offsetValue + elementCount} exceeds buffer capacity(${capacity}) ` +
318
+ `for '${rawName}'.`,
319
+ );
320
+ }
321
+
322
+ // A slice cannot copy more bytes than the source value holds — that would be
323
+ // an out-of-range shift (undefined behavior) baked in at compile time.
324
+ if (lengthValue > src.bytes) {
325
+ throw new Error(
326
+ `${line}:0 Error: Slice assignment length (${lengthValue} bytes) exceeds ` +
327
+ `the source value width (${src.bytes} bytes) for '${rawName}'.`,
328
+ );
329
+ }
330
+
331
+ return elementCount;
332
+ }
333
+
334
+ /**
335
+ * Materialize a slice source into a single unsigned temp that the unrolled
336
+ * writes shift to extract each element. The source is read exactly once and
337
+ * every shift operates on a known-width unsigned value (MISRA Rule 10.1).
338
+ *
339
+ * A *composite signed* source (e.g. `a + b` of signed operands) is split into
340
+ * two steps — bind it to its own signed type, then reinterpret that simple
341
+ * identifier to unsigned — so the unsigned cast never applies to the composite
342
+ * expression (MISRA Rule 10.8). Every other source keeps the single same-or-
343
+ * length-sized cast it had before: a same-category cast (resolved unsigned) or
344
+ * a non-composite cast (simple source / function call) does not trip Rule 10.8.
345
+ */
346
+ function materializeSliceSource(
347
+ writes: string[],
348
+ src: {
349
+ cType: string | null;
350
+ unsignedCType: string | null;
351
+ isComposite: boolean;
352
+ },
353
+ value: string,
354
+ lengthValue: number,
355
+ ): string {
356
+ if (
357
+ src.isComposite &&
358
+ src.cType !== null &&
359
+ src.unsignedCType !== null &&
360
+ src.cType !== src.unsignedCType
361
+ ) {
362
+ const signedName = CodeGenState.getNextTempVarName();
363
+ writes.push(`const ${src.cType} ${signedName} = ${value};`);
364
+ const unsignedName = CodeGenState.getNextTempVarName();
365
+ writes.push(
366
+ `const ${src.unsignedCType} ${unsignedName} = (${src.unsignedCType})${signedName};`,
367
+ );
368
+ return unsignedName;
369
+ }
370
+
371
+ const tempType = src.unsignedCType ?? unsignedCTypeForBytes(lengthValue);
372
+ const tempName = CodeGenState.getNextTempVarName();
373
+ writes.push(`const ${tempType} ${tempName} = (${tempType})(${value});`);
374
+ return tempName;
375
+ }
376
+
377
+ /** Validated, constant-folded geometry of a slice-assignment destination. */
378
+ interface ISliceGeometry {
379
+ offsetValue: number;
380
+ lengthValue: number;
381
+ capacity: number;
382
+ }
383
+
384
+ /**
385
+ * Build the unrolled, per-element little-endian writes for a slice assignment
386
+ * (Issue #1081). Offset/length constants are already validated by the caller;
387
+ * this focuses on the destination-element-aware codegen so `handleArraySlice`
388
+ * stays within the cognitive-complexity budget.
389
+ *
390
+ * The source is materialized into a single unsigned temp before the writes so
391
+ * it is evaluated exactly once — an impure source (function call, atomic read)
392
+ * would otherwise be re-evaluated per element (Issue #1085 review, Finding 2) —
393
+ * and so every shift has a known, in-range width (Finding 3). The temp is
394
+ * skipped for a single-element slice, where the source is used only once.
395
+ */
396
+ function buildSliceWrites(
397
+ name: string,
398
+ ctx: IAssignmentContext,
399
+ typeInfo: TTypeInfo | undefined,
400
+ geometry: ISliceGeometry,
401
+ line: number,
402
+ rawName: string,
403
+ ): string {
404
+ const dest = resolveSliceElement(typeInfo, line, rawName);
405
+ const src = resolveSliceSource(ctx, line, rawName, geometry.lengthValue);
406
+ const elementCount = validateSliceSpan(
407
+ dest,
408
+ src,
409
+ geometry.offsetValue,
410
+ geometry.lengthValue,
411
+ geometry.capacity,
412
+ line,
413
+ rawName,
414
+ );
415
+
416
+ const writes: string[] = [];
417
+ // Self-document the codegen ONLY when an equivalent memcpy would genuinely
418
+ // have violated Rule 21.15: the source type is KNOWN and differs from the
419
+ // destination element type (incompatible pointer types) AND more than one
420
+ // element is written (a real unroll). A same-type, single-element, or
421
+ // unresolved-type source does not warrant the citation (Issue #1085 review:
422
+ // the comment must not assert "incompatible pointer types" it cannot prove,
423
+ // nor label a single write as "unrolled").
424
+ if (src.cType !== null && src.cType !== dest.cType && elementCount > 1) {
425
+ writes.push(sliceUnrollComment(dest.cType, src.cType));
426
+ }
427
+
428
+ // Evaluate the source once. Shifting a single unsigned temp keeps every chunk
429
+ // MISRA Rule 10.1-clean and reads the source exactly once — needed when the
430
+ // source feeds more than one element, or whenever it is a composite (so the
431
+ // unsigned cast lands on the temp, never on the composite: MISRA Rule 10.8).
432
+ // Residual: an *unresolved* composite (e.g. a struct-field or function-call
433
+ // expression TypeResolver cannot type) is still sized to the slice length and
434
+ // cast directly — see issue #1089.
435
+ let sourceExpr = ctx.generatedValue;
436
+ if (elementCount > 1 || src.isComposite) {
437
+ sourceExpr = materializeSliceSource(
438
+ writes,
439
+ src,
440
+ ctx.generatedValue,
441
+ geometry.lengthValue,
442
+ );
443
+ }
444
+
445
+ for (let k = 0; k < elementCount; k += 1) {
446
+ const shiftBits = k * dest.bytes * 8;
447
+ const chunk =
448
+ shiftBits === 0 ? `(${sourceExpr})` : `(${sourceExpr} >> ${shiftBits}U)`;
449
+ writes.push(`${name}[${geometry.offsetValue + k}] = ${dest.wrap(chunk)};`);
450
+ }
451
+
452
+ return writes.join("\n");
453
+ }
454
+
57
455
  /**
58
456
  * Handle array slice assignment: buffer[0, 10] <- source
59
457
  *
@@ -88,7 +486,9 @@ function handleArraySlice(ctx: IAssignmentContext): string {
88
486
  }
89
487
 
90
488
  // Validate offset is compile-time constant
91
- const offsetValue = gen().tryEvaluateConstant(ctx.subscripts[0]);
489
+ const offsetValue = CodeGenState.requireGenerator().tryEvaluateConstant(
490
+ ctx.subscripts[0],
491
+ );
92
492
  if (offsetValue === undefined) {
93
493
  throw new Error(
94
494
  `${line}:0 Error: Slice assignment offset must be a compile-time constant. ` +
@@ -97,7 +497,9 @@ function handleArraySlice(ctx: IAssignmentContext): string {
97
497
  }
98
498
 
99
499
  // Validate length is compile-time constant
100
- const lengthValue = gen().tryEvaluateConstant(ctx.subscripts[1]);
500
+ const lengthValue = CodeGenState.requireGenerator().tryEvaluateConstant(
501
+ ctx.subscripts[1],
502
+ );
101
503
  if (lengthValue === undefined) {
102
504
  throw new Error(
103
505
  `${line}:0 Error: Slice assignment length must be a compile-time constant. ` +
@@ -119,17 +521,6 @@ function handleArraySlice(ctx: IAssignmentContext): string {
119
521
  );
120
522
  }
121
523
 
122
- // Bounds validation
123
- if (offsetValue + lengthValue > capacity) {
124
- // Use raw identifier in error message for clarity
125
- const rawName = ctx.identifiers[0];
126
- throw new Error(
127
- `${line}:0 Error: Slice assignment out of bounds: ` +
128
- `offset(${offsetValue}) + length(${lengthValue}) = ${offsetValue + lengthValue} ` +
129
- `exceeds buffer capacity(${capacity}) for '${rawName}'.`,
130
- );
131
- }
132
-
133
524
  if (offsetValue < 0) {
134
525
  throw new Error(
135
526
  `${line}:0 Error: Slice assignment offset cannot be negative: ${offsetValue}`,
@@ -142,10 +533,21 @@ function handleArraySlice(ctx: IAssignmentContext): string {
142
533
  );
143
534
  }
144
535
 
145
- // Mark that we need string.h for memcpy
146
- CodeGenState.needsString = true;
147
-
148
- return `memcpy(&${name}[${offsetValue}], &${ctx.generatedValue}, ${lengthValue});`;
536
+ // Issue #1081: emit per-element little-endian writes instead of memcpy.
537
+ // memcpy between a byte buffer and a wider integer passes incompatible
538
+ // pointer types (MISRA C:2012 Rule 21.15). The slice length is a compile-time
539
+ // constant, so the copy can be fully unrolled at the destination's element
540
+ // granularity with no library call. Element-span bounds, source-width, and
541
+ // length-alignment are validated inside buildSliceWrites where the element
542
+ // size is known.
543
+ return buildSliceWrites(
544
+ name,
545
+ ctx,
546
+ typeInfo,
547
+ { offsetValue, lengthValue, capacity },
548
+ line,
549
+ ctx.identifiers[0],
550
+ );
149
551
  }
150
552
 
151
553
  /**
@@ -12,12 +12,6 @@ import IAssignmentContext from "../IAssignmentContext";
12
12
  import BitUtils from "../../../../../utils/BitUtils";
13
13
  import TAssignmentHandler from "./TAssignmentHandler";
14
14
  import CodeGenState from "../../../../state/CodeGenState";
15
- import type ICodeGenApi from "../../types/ICodeGenApi";
16
-
17
- /** Get typed generator reference */
18
- function gen(): ICodeGenApi {
19
- return CodeGenState.generator as ICodeGenApi;
20
- }
21
15
 
22
16
  /**
23
17
  * Validate compound operators are not used with bit access.
@@ -41,12 +35,14 @@ function handleIntegerBit(ctx: IAssignmentContext): string {
41
35
  // Use resolvedBaseIdentifier for type lookup and code generation
42
36
  // e.g., "ArrayBug_flags" instead of "flags"
43
37
  const name = ctx.resolvedBaseIdentifier;
44
- const bitIndex = gen().generateExpression(ctx.subscripts[0]);
38
+ const bitIndex = CodeGenState.requireGenerator().generateExpression(
39
+ ctx.subscripts[0],
40
+ );
45
41
  const typeInfo = CodeGenState.getVariableTypeInfo(name);
46
42
 
47
43
  // Check for float bit indexing
48
44
  if (typeInfo) {
49
- const floatResult = gen().generateFloatBitWrite(
45
+ const floatResult = CodeGenState.requireGenerator().generateFloatBitWrite(
50
46
  name,
51
47
  typeInfo,
52
48
  bitIndex,
@@ -77,13 +73,17 @@ function handleIntegerBitRange(ctx: IAssignmentContext): string {
77
73
 
78
74
  // Use resolvedBaseIdentifier for type lookup and code generation
79
75
  const name = ctx.resolvedBaseIdentifier;
80
- const start = gen().generateExpression(ctx.subscripts[0]);
81
- const width = gen().generateExpression(ctx.subscripts[1]);
76
+ const start = CodeGenState.requireGenerator().generateExpression(
77
+ ctx.subscripts[0],
78
+ );
79
+ const width = CodeGenState.requireGenerator().generateExpression(
80
+ ctx.subscripts[1],
81
+ );
82
82
  const typeInfo = CodeGenState.getVariableTypeInfo(name);
83
83
 
84
84
  // Check for float bit indexing
85
85
  if (typeInfo) {
86
- const floatResult = gen().generateFloatBitWrite(
86
+ const floatResult = CodeGenState.requireGenerator().generateFloatBitWrite(
87
87
  name,
88
88
  typeInfo,
89
89
  start,
@@ -116,10 +116,14 @@ function handleStructMemberBit(ctx: IAssignmentContext): string {
116
116
  // The last subscript is the bit index
117
117
  // This pattern is complex - the target needs to be built from the member chain
118
118
  // For now, delegate to the existing target generator and build the bit op
119
- const target = gen().generateAssignmentTarget(ctx.targetCtx);
119
+ const target = CodeGenState.requireGenerator().generateAssignmentTarget(
120
+ ctx.targetCtx,
121
+ );
120
122
 
121
123
  // Extract the bit index from the last subscript
122
- const bitIndex = gen().generateExpression(ctx.subscripts.at(-1)!);
124
+ const bitIndex = CodeGenState.requireGenerator().generateExpression(
125
+ ctx.subscripts.at(-1)!,
126
+ );
123
127
 
124
128
  // Limitation: Uses literal "1U" which works for types up to 32 bits.
125
129
  // For 64-bit struct members, would need to track member type through chain.
@@ -151,9 +155,11 @@ function handleArrayElementBit(ctx: IAssignmentContext): string {
151
155
  // Array indices are subscripts[0..numDims-1], bit index is subscripts[numDims]
152
156
  const arrayIndices = ctx.subscripts
153
157
  .slice(0, numDims)
154
- .map((e) => `[${gen().generateExpression(e)}]`)
158
+ .map((e) => `[${CodeGenState.requireGenerator().generateExpression(e)}]`)
155
159
  .join("");
156
- const bitIndex = gen().generateExpression(ctx.subscripts[numDims]);
160
+ const bitIndex = CodeGenState.requireGenerator().generateExpression(
161
+ ctx.subscripts[numDims],
162
+ );
157
163
 
158
164
  const arrayElement = `${arrayName}${arrayIndices}`;
159
165
 
@@ -187,7 +193,10 @@ function handleStructChainBitRange(ctx: IAssignmentContext): string {
187
193
  } else {
188
194
  const exprs = op.expression();
189
195
  if (exprs.length > 0) {
190
- baseTarget += "[" + gen().generateExpression(exprs[0]) + "]";
196
+ baseTarget +=
197
+ "[" +
198
+ CodeGenState.requireGenerator().generateExpression(exprs[0]) +
199
+ "]";
191
200
  }
192
201
  }
193
202
  }
@@ -195,8 +204,12 @@ function handleStructChainBitRange(ctx: IAssignmentContext): string {
195
204
  // Get start and width from the last postfixOp (the bit range)
196
205
  const lastOp = ctx.postfixOps.at(-1)!;
197
206
  const bitRangeExprs = lastOp.expression();
198
- const start = gen().generateExpression(bitRangeExprs[0]);
199
- const width = gen().generateExpression(bitRangeExprs[1]);
207
+ const start = CodeGenState.requireGenerator().generateExpression(
208
+ bitRangeExprs[0],
209
+ );
210
+ const width = CodeGenState.requireGenerator().generateExpression(
211
+ bitRangeExprs[1],
212
+ );
200
213
 
201
214
  // Generate bit range write
202
215
  // Limitation: assumes 32-bit types. For 64-bit struct members,
@@ -15,14 +15,8 @@ import BitUtils from "../../../../../utils/BitUtils";
15
15
  import TAssignmentHandler from "./TAssignmentHandler";
16
16
  import CodeGenState from "../../../../state/CodeGenState";
17
17
  import TypeValidator from "../../TypeValidator";
18
- import type ICodeGenApi from "../../types/ICodeGenApi";
19
18
  import QualifiedNameGenerator from "../../utils/QualifiedNameGenerator";
20
19
 
21
- /** Get typed generator reference */
22
- function gen(): ICodeGenApi {
23
- return CodeGenState.generator as ICodeGenApi;
24
- }
25
-
26
20
  /**
27
21
  * Calculate mask value and hex string for bitmap field.
28
22
  */
@@ -135,7 +129,9 @@ function handleBitmapArrayElementField(ctx: IAssignmentContext): string {
135
129
  const bitmapType = typeInfo!.bitmapTypeName!;
136
130
 
137
131
  const fieldInfo = getBitmapFieldInfo(bitmapType, fieldName, ctx);
138
- const index = gen().generateExpression(ctx.subscripts[0]);
132
+ const index = CodeGenState.requireGenerator().generateExpression(
133
+ ctx.subscripts[0],
134
+ );
139
135
  const arrayElement = `${arrayName}[${index}]`;
140
136
 
141
137
  return generateBitmapWrite(arrayElement, fieldInfo, ctx.generatedValue);
@@ -209,7 +205,10 @@ function handleScopedRegisterMemberBitmapField(
209
205
  fieldName = ctx.identifiers[3];
210
206
 
211
207
  // Validate cross-scope access
212
- gen().validateCrossScopeVisibility(scopeName, regName);
208
+ CodeGenState.requireGenerator().validateCrossScopeVisibility(
209
+ scopeName,
210
+ regName,
211
+ );
213
212
  }
214
213
 
215
214
  const fullRegName = QualifiedNameGenerator.forMember(scopeName, regName);
@@ -15,14 +15,8 @@ import TAssignmentHandler from "./TAssignmentHandler";
15
15
  import RegisterUtils from "./RegisterUtils";
16
16
  import AssignmentHandlerUtils from "./AssignmentHandlerUtils";
17
17
  import CodeGenState from "../../../../state/CodeGenState";
18
- import type ICodeGenApi from "../../types/ICodeGenApi";
19
18
  import QualifiedNameGenerator from "../../utils/QualifiedNameGenerator";
20
19
 
21
- /** Get typed generator reference */
22
- function gen(): ICodeGenApi {
23
- return CodeGenState.generator as ICodeGenApi;
24
- }
25
-
26
20
  /**
27
21
  * Handle register single bit: GPIO7.DR_SET[LED_BIT] <- true
28
22
  */
@@ -41,7 +35,9 @@ function handleRegisterBit(ctx: IAssignmentContext): string {
41
35
  const accessMod = CodeGenState.symbols!.registerMemberAccess.get(fullName);
42
36
  const isWriteOnly = RegisterUtils.isWriteOnlyRegister(accessMod);
43
37
 
44
- const bitIndex = gen().generateExpression(ctx.subscripts[0]);
38
+ const bitIndex = CodeGenState.requireGenerator().generateExpression(
39
+ ctx.subscripts[0],
40
+ );
45
41
 
46
42
  if (isWriteOnly) {
47
43
  AssignmentHandlerUtils.validateWriteOnlyValue(
@@ -135,7 +131,9 @@ function handleScopedRegisterBit(ctx: IAssignmentContext): string {
135
131
  const accessMod = CodeGenState.symbols!.registerMemberAccess.get(regName);
136
132
  const isWriteOnly = RegisterUtils.isWriteOnlyRegister(accessMod);
137
133
 
138
- const bitIndex = gen().generateExpression(ctx.subscripts[0]);
134
+ const bitIndex = CodeGenState.requireGenerator().generateExpression(
135
+ ctx.subscripts[0],
136
+ );
139
137
 
140
138
  if (isWriteOnly) {
141
139
  AssignmentHandlerUtils.validateWriteOnlyValue(