c-next 0.1.68 → 0.1.69
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/package.json +1 -1
- package/src/transpiler/output/codegen/CodeGenerator.ts +125 -135
- package/src/transpiler/output/codegen/TypeValidator.ts +2 -2
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +2 -2
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +29 -1
- package/src/transpiler/output/codegen/__tests__/TypeValidator.resolution.test.ts +1 -3
- package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +3 -1
- package/src/transpiler/output/codegen/assignment/AssignmentContextBuilder.ts +49 -0
- package/src/transpiler/output/codegen/assignment/IAssignmentContext.ts +15 -0
- package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +7 -0
- package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +24 -17
- package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +16 -5
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/AccessPatternHandlers.test.ts +9 -1
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +18 -1
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +9 -1
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitmapHandlers.test.ts +9 -1
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/RegisterHandlers.test.ts +10 -1
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/SpecialHandlers.test.ts +9 -1
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/StringHandlers.test.ts +9 -1
- package/src/transpiler/output/codegen/helpers/MemberSeparatorResolver.ts +6 -1
- package/src/transpiler/state/CodeGenState.ts +35 -1
- package/src/transpiler/state/__tests__/CodeGenState.test.ts +5 -5
package/package.json
CHANGED
|
@@ -220,41 +220,6 @@ const DEFAULT_TARGET: TargetCapabilities = {
|
|
|
220
220
|
hasBasepri: false,
|
|
221
221
|
};
|
|
222
222
|
|
|
223
|
-
/**
|
|
224
|
-
* ADR-044: Assignment context for overflow behavior tracking
|
|
225
|
-
*/
|
|
226
|
-
interface AssignmentContext {
|
|
227
|
-
targetName: string | null;
|
|
228
|
-
targetType: string | null;
|
|
229
|
-
overflowBehavior: TOverflowBehavior;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Context for tracking current scope during code generation
|
|
234
|
-
*/
|
|
235
|
-
interface GeneratorContext {
|
|
236
|
-
currentScope: string | null; // ADR-016: renamed from currentNamespace
|
|
237
|
-
currentFunctionName: string | null; // Issue #269: track current function for pass-by-value lookup
|
|
238
|
-
currentFunctionReturnType: string | null; // Issue #477: track return type for enum inference
|
|
239
|
-
indentLevel: number;
|
|
240
|
-
scopeMembers: Map<string, Set<string>>; // scope -> member names (ADR-016)
|
|
241
|
-
currentParameters: Map<string, TParameterInfo>; // ADR-006: track params for pointer semantics
|
|
242
|
-
// Issue #558: modifiedParameters removed - now uses analysis-phase results from CodeGenState.modifiedParameters
|
|
243
|
-
localArrays: Set<string>; // ADR-006: track local array variables (no & needed)
|
|
244
|
-
localVariables: Set<string>; // ADR-016: track local variables (allowed as bare identifiers)
|
|
245
|
-
floatBitShadows: Set<string>; // Track declared shadow variables for float bit indexing
|
|
246
|
-
floatShadowCurrent: Set<string>; // Track which shadows have current value (skip redundant memcpy reads)
|
|
247
|
-
inFunctionBody: boolean; // ADR-016: track if we're inside a function body
|
|
248
|
-
typeRegistry: Map<string, TTypeInfo>; // Track variable types for bit access and .length
|
|
249
|
-
expectedType: string | null; // For inferred struct initializers
|
|
250
|
-
mainArgsName: string | null; // Track the args parameter name for main() translation
|
|
251
|
-
assignmentContext: AssignmentContext; // ADR-044: Track current assignment for overflow
|
|
252
|
-
lastArrayInitCount: number; // ADR-035: Track element count for size inference
|
|
253
|
-
lastArrayFillValue: string | undefined; // ADR-035: Track fill-all value
|
|
254
|
-
lengthCache: Map<string, string> | null; // Cache: variable name -> temp variable name for strlen optimization
|
|
255
|
-
targetCapabilities: TargetCapabilities; // ADR-049: Target platform for atomic code generation
|
|
256
|
-
}
|
|
257
|
-
|
|
258
223
|
/**
|
|
259
224
|
* Code Generator - Transpiles C-Next to C
|
|
260
225
|
*
|
|
@@ -269,42 +234,6 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
269
234
|
["f64", "0.0"],
|
|
270
235
|
]);
|
|
271
236
|
|
|
272
|
-
private context: GeneratorContext =
|
|
273
|
-
CodeGenerator.createDefaultContext(DEFAULT_TARGET);
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Create a fresh GeneratorContext with default values.
|
|
277
|
-
*/
|
|
278
|
-
private static createDefaultContext(
|
|
279
|
-
targetCapabilities: TargetCapabilities,
|
|
280
|
-
): GeneratorContext {
|
|
281
|
-
return {
|
|
282
|
-
currentScope: null,
|
|
283
|
-
currentFunctionName: null,
|
|
284
|
-
currentFunctionReturnType: null,
|
|
285
|
-
indentLevel: 0,
|
|
286
|
-
scopeMembers: new Map(),
|
|
287
|
-
currentParameters: new Map(),
|
|
288
|
-
localArrays: new Set(),
|
|
289
|
-
localVariables: new Set(),
|
|
290
|
-
floatBitShadows: new Set(),
|
|
291
|
-
floatShadowCurrent: new Set(),
|
|
292
|
-
inFunctionBody: false,
|
|
293
|
-
typeRegistry: new Map(),
|
|
294
|
-
expectedType: null,
|
|
295
|
-
mainArgsName: null,
|
|
296
|
-
assignmentContext: {
|
|
297
|
-
targetName: null,
|
|
298
|
-
targetType: null,
|
|
299
|
-
overflowBehavior: "clamp",
|
|
300
|
-
},
|
|
301
|
-
lastArrayInitCount: 0,
|
|
302
|
-
lastArrayFillValue: undefined,
|
|
303
|
-
lengthCache: null,
|
|
304
|
-
targetCapabilities,
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
|
|
308
237
|
/** Token stream for comment extraction (ADR-043) */
|
|
309
238
|
private tokenStream: CommonTokenStream | null = null;
|
|
310
239
|
|
|
@@ -485,7 +414,7 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
485
414
|
expectedType: CodeGenState.expectedType,
|
|
486
415
|
selfIncludeAdded: CodeGenState.selfIncludeAdded, // Issue #369
|
|
487
416
|
// Issue #644: Postfix expression state
|
|
488
|
-
scopeMembers: CodeGenState.
|
|
417
|
+
scopeMembers: CodeGenState.getAllScopeMembers(),
|
|
489
418
|
mainArgsName: CodeGenState.mainArgsName,
|
|
490
419
|
floatBitShadows: CodeGenState.floatBitShadows,
|
|
491
420
|
floatShadowCurrent: CodeGenState.floatShadowCurrent,
|
|
@@ -628,7 +557,7 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
628
557
|
resolveIdentifier(identifier: string): string {
|
|
629
558
|
// Check current scope first (inner scope shadows outer)
|
|
630
559
|
if (CodeGenState.currentScope) {
|
|
631
|
-
const members = CodeGenState.
|
|
560
|
+
const members = CodeGenState.getScopeMembers(CodeGenState.currentScope);
|
|
632
561
|
if (members?.has(identifier)) {
|
|
633
562
|
return `${CodeGenState.currentScope}_${identifier}`;
|
|
634
563
|
}
|
|
@@ -1148,10 +1077,34 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
1148
1077
|
);
|
|
1149
1078
|
}
|
|
1150
1079
|
|
|
1080
|
+
// Issue #779: Resolve bare scope member identifiers before postfix chain processing
|
|
1081
|
+
// This ensures scope members get their prefix even with array/member access.
|
|
1082
|
+
// Skip parameters - they don't need scope resolution and shouldn't be dereferenced
|
|
1083
|
+
// when used with array indexing (buf[idx] is valid C for pointer params).
|
|
1084
|
+
// Also skip known registers - they should be handled by the postfix chain builder
|
|
1085
|
+
// to enable proper register validation (requiring global. when shadowed).
|
|
1086
|
+
let resolvedIdentifier = identifier ?? "";
|
|
1087
|
+
if (!hasGlobal && !hasThis && identifier) {
|
|
1088
|
+
const isParameter = CodeGenState.currentParameters.has(identifier);
|
|
1089
|
+
const isLocalVariable = CodeGenState.localVariables.has(identifier);
|
|
1090
|
+
const isKnownRegister =
|
|
1091
|
+
CodeGenState.symbols?.knownRegisters.has(identifier);
|
|
1092
|
+
if (!isParameter && !isLocalVariable && !isKnownRegister) {
|
|
1093
|
+
const resolved = TypeValidator.resolveBareIdentifier(
|
|
1094
|
+
identifier,
|
|
1095
|
+
false, // not local
|
|
1096
|
+
(name: string) => this.isKnownStruct(name),
|
|
1097
|
+
);
|
|
1098
|
+
if (resolved !== null) {
|
|
1099
|
+
resolvedIdentifier = resolved;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1151
1104
|
// SonarCloud S3776: Use BaseIdentifierBuilder for base identifier
|
|
1152
1105
|
const safeIdentifier = identifier ?? "";
|
|
1153
1106
|
const { result: baseResult, firstId } = BaseIdentifierBuilder.build(
|
|
1154
|
-
safeIdentifier,
|
|
1107
|
+
hasGlobal || hasThis ? safeIdentifier : resolvedIdentifier,
|
|
1155
1108
|
hasGlobal,
|
|
1156
1109
|
hasThis,
|
|
1157
1110
|
CodeGenState.currentScope,
|
|
@@ -1546,36 +1499,24 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
1546
1499
|
return `\ntypedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});\n`;
|
|
1547
1500
|
}
|
|
1548
1501
|
|
|
1549
|
-
/**
|
|
1550
|
-
* Issue #268: Store unmodified parameters for a function.
|
|
1551
|
-
* Maps function name -> Set of parameter names that were NOT modified.
|
|
1552
|
-
* Used by Pipeline to update symbol info before header generation.
|
|
1553
|
-
*/
|
|
1554
|
-
private readonly functionUnmodifiedParams: Map<string, Set<string>> =
|
|
1555
|
-
new Map();
|
|
1556
|
-
|
|
1557
1502
|
/**
|
|
1558
1503
|
* Issue #268: Get unmodified parameters info for all functions.
|
|
1559
1504
|
* Returns map of function name -> Set of unmodified parameter names.
|
|
1505
|
+
* Computed on-demand from functionSignatures and modifiedParameters.
|
|
1560
1506
|
*/
|
|
1561
1507
|
getFunctionUnmodifiedParams(): ReadonlyMap<string, Set<string>> {
|
|
1562
|
-
return
|
|
1508
|
+
return CodeGenState.getUnmodifiedParameters();
|
|
1563
1509
|
}
|
|
1564
1510
|
|
|
1565
1511
|
/**
|
|
1566
1512
|
* Issue #268: Update symbol parameters with auto-const info.
|
|
1567
|
-
*
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
if (!modifiedSet?.has(paramName)) {
|
|
1575
|
-
unmodifiedParams.add(paramName);
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
this.functionUnmodifiedParams.set(functionName, unmodifiedParams);
|
|
1513
|
+
* Now a no-op - unmodified params are computed on-demand from CodeGenState.
|
|
1514
|
+
* Kept for IOrchestrator interface compatibility.
|
|
1515
|
+
*/
|
|
1516
|
+
updateFunctionParamsAutoConst(_functionName: string): void {
|
|
1517
|
+
// No-op: Unmodified parameters are now computed on-demand from
|
|
1518
|
+
// CodeGenState.functionSignatures and CodeGenState.modifiedParameters
|
|
1519
|
+
// via CodeGenState.getUnmodifiedParameters().
|
|
1579
1520
|
}
|
|
1580
1521
|
|
|
1581
1522
|
/**
|
|
@@ -1773,23 +1714,16 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
1773
1714
|
* Returns true if the callee modifies that parameter (should not have const).
|
|
1774
1715
|
*/
|
|
1775
1716
|
isCalleeParameterModified(funcName: string, paramIndex: number): boolean {
|
|
1776
|
-
const unmodifiedParams = this.functionUnmodifiedParams.get(funcName);
|
|
1777
|
-
if (!unmodifiedParams) {
|
|
1778
|
-
// Callee not yet processed - conservatively return false (assume unmodified)
|
|
1779
|
-
// This means we won't mark our param as modified, which may cause a C compiler error
|
|
1780
|
-
// if the callee actually modifies the param. The C compiler will catch this.
|
|
1781
|
-
return false;
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
1717
|
// Get the parameter name at the given index from the function signature
|
|
1785
1718
|
const sig = CodeGenState.functionSignatures.get(funcName);
|
|
1786
1719
|
if (!sig || paramIndex >= sig.parameters.length) {
|
|
1720
|
+
// Callee not yet processed - conservatively return false (assume unmodified)
|
|
1787
1721
|
return false;
|
|
1788
1722
|
}
|
|
1789
1723
|
|
|
1790
1724
|
const paramName = sig.parameters[paramIndex].name;
|
|
1791
|
-
//
|
|
1792
|
-
return
|
|
1725
|
+
// Check directly if the parameter is in the modified set
|
|
1726
|
+
return CodeGenState.isParameterModified(funcName, paramName);
|
|
1793
1727
|
}
|
|
1794
1728
|
|
|
1795
1729
|
/**
|
|
@@ -2047,7 +1981,14 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
2047
1981
|
}
|
|
2048
1982
|
|
|
2049
1983
|
/**
|
|
2050
|
-
* Validate register access from inside a scope requires global. prefix
|
|
1984
|
+
* Validate register access from inside a scope requires global. prefix.
|
|
1985
|
+
*
|
|
1986
|
+
* Issue #779: Use ambiguity-aware validation - only require global. when
|
|
1987
|
+
* the register name is ACTUALLY shadowed by a local or scope member.
|
|
1988
|
+
*
|
|
1989
|
+
* Exceptions (no global. required):
|
|
1990
|
+
* 1. Scoped registers defined within the current scope
|
|
1991
|
+
* 2. Unambiguous access - no local/scope member with the same name
|
|
2051
1992
|
*/
|
|
2052
1993
|
private _validateRegisterAccess(
|
|
2053
1994
|
registerName: string,
|
|
@@ -2056,6 +1997,27 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
2056
1997
|
): void {
|
|
2057
1998
|
// Only validate when inside a scope and accessing without global. prefix
|
|
2058
1999
|
if (CodeGenState.currentScope && !hasGlobal) {
|
|
2000
|
+
// Check if this is a scoped register (defined within the current scope)
|
|
2001
|
+
// The registerName may already be the fully qualified name (e.g., "GPIO_PORTA")
|
|
2002
|
+
// if accessed as PORTA from inside scope GPIO
|
|
2003
|
+
const scopePrefix = `${CodeGenState.currentScope}_`;
|
|
2004
|
+
if (registerName.startsWith(scopePrefix)) {
|
|
2005
|
+
// This is a scoped register - allow bare access
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
// Issue #779: Ambiguity-aware validation
|
|
2010
|
+
// Only require global. if the register name is shadowed by:
|
|
2011
|
+
// 1. A local variable in the current function
|
|
2012
|
+
// 2. A member of the current scope
|
|
2013
|
+
const isShadowedByLocal = CodeGenState.localVariables.has(registerName);
|
|
2014
|
+
const isShadowedByScope = CodeGenState.isCurrentScopeMember(registerName);
|
|
2015
|
+
|
|
2016
|
+
if (!isShadowedByLocal && !isShadowedByScope) {
|
|
2017
|
+
// Unambiguous - allow bare access
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2059
2021
|
throw new Error(
|
|
2060
2022
|
`Error: Use 'global.${registerName}.${memberName}' to access register '${registerName}' ` +
|
|
2061
2023
|
`from inside scope '${CodeGenState.currentScope}'`,
|
|
@@ -2190,30 +2152,11 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
2190
2152
|
* Reset all generator state for a fresh generation pass.
|
|
2191
2153
|
*/
|
|
2192
2154
|
private resetGeneratorState(targetCapabilities: TargetCapabilities): void {
|
|
2193
|
-
// Reset global state
|
|
2155
|
+
// Reset global state (CodeGenState.reset() handles all field initialization)
|
|
2194
2156
|
CodeGenState.reset(targetCapabilities);
|
|
2195
2157
|
|
|
2196
2158
|
// Set generator reference for handlers to use
|
|
2197
2159
|
CodeGenState.generator = this;
|
|
2198
|
-
|
|
2199
|
-
// Reset local context (will gradually migrate to CodeGenState)
|
|
2200
|
-
this.context = CodeGenerator.createDefaultContext(targetCapabilities);
|
|
2201
|
-
|
|
2202
|
-
CodeGenState.knownFunctions = new Set();
|
|
2203
|
-
CodeGenState.functionSignatures = new Map();
|
|
2204
|
-
CodeGenState.callbackTypes = new Map();
|
|
2205
|
-
CodeGenState.callbackFieldTypes = new Map();
|
|
2206
|
-
CodeGenState.usedClampOps = new Set();
|
|
2207
|
-
CodeGenState.usedSafeDivOps = new Set();
|
|
2208
|
-
CodeGenState.needsStdint = false;
|
|
2209
|
-
CodeGenState.needsStdbool = false;
|
|
2210
|
-
CodeGenState.needsString = false;
|
|
2211
|
-
CodeGenState.needsFloatStaticAssert = false;
|
|
2212
|
-
CodeGenState.needsISR = false;
|
|
2213
|
-
CodeGenState.needsCMSIS = false;
|
|
2214
|
-
CodeGenState.needsLimits = false;
|
|
2215
|
-
CodeGenState.needsIrqWrappers = false;
|
|
2216
|
-
CodeGenState.selfIncludeAdded = false;
|
|
2217
2160
|
}
|
|
2218
2161
|
|
|
2219
2162
|
/**
|
|
@@ -2224,7 +2167,7 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
2224
2167
|
|
|
2225
2168
|
// Copy symbol data to CodeGenState.scopeMembers
|
|
2226
2169
|
for (const [scopeName, members] of symbols.scopeMembers) {
|
|
2227
|
-
CodeGenState.
|
|
2170
|
+
CodeGenState.setScopeMembers(scopeName, new Set(members));
|
|
2228
2171
|
}
|
|
2229
2172
|
|
|
2230
2173
|
// Issue #461: Initialize constValues from symbol table
|
|
@@ -2412,13 +2355,7 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
2412
2355
|
}
|
|
2413
2356
|
|
|
2414
2357
|
if (CodeGenState.needsIrqWrappers) {
|
|
2415
|
-
output.push(
|
|
2416
|
-
"// ADR-050: IRQ wrappers to avoid macro collisions with platform headers",
|
|
2417
|
-
"static inline void __cnx_disable_irq(void) { __disable_irq(); }",
|
|
2418
|
-
"static inline uint32_t __cnx_get_PRIMASK(void) { return __get_PRIMASK(); }",
|
|
2419
|
-
"static inline void __cnx_set_PRIMASK(uint32_t mask) { __set_PRIMASK(mask); }",
|
|
2420
|
-
"",
|
|
2421
|
-
);
|
|
2358
|
+
output.push(...this.generateIrqWrappers());
|
|
2422
2359
|
}
|
|
2423
2360
|
|
|
2424
2361
|
if (CodeGenState.needsISR) {
|
|
@@ -4525,7 +4462,7 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
4525
4462
|
|
|
4526
4463
|
// Scope member - may need prefixing
|
|
4527
4464
|
if (CodeGenState.currentScope) {
|
|
4528
|
-
const members = CodeGenState.
|
|
4465
|
+
const members = CodeGenState.getScopeMembers(CodeGenState.currentScope);
|
|
4529
4466
|
if (members?.has(id)) {
|
|
4530
4467
|
const scopedName = `${CodeGenState.currentScope}_${id}`;
|
|
4531
4468
|
return CppModeHelper.maybeAddressOf(scopedName);
|
|
@@ -6120,6 +6057,10 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
6120
6057
|
const assignCtx = buildAssignmentContext(ctx, {
|
|
6121
6058
|
typeRegistry: CodeGenState.typeRegistry,
|
|
6122
6059
|
generateExpression: () => value,
|
|
6060
|
+
generateAssignmentTarget: (targetCtx) =>
|
|
6061
|
+
this.generateAssignmentTarget(targetCtx),
|
|
6062
|
+
isKnownRegister: (name) => CodeGenState.symbols!.knownRegisters.has(name),
|
|
6063
|
+
currentScope: CodeGenState.currentScope,
|
|
6123
6064
|
});
|
|
6124
6065
|
// ADR-109: Handlers access CodeGenState directly, no deps needed
|
|
6125
6066
|
const assignmentKind = AssignmentClassifier.classify(assignCtx);
|
|
@@ -6608,6 +6549,55 @@ export default class CodeGenerator implements IOrchestrator {
|
|
|
6608
6549
|
);
|
|
6609
6550
|
}
|
|
6610
6551
|
|
|
6552
|
+
/**
|
|
6553
|
+
* Generate platform-portable IRQ wrappers for critical sections (ADR-050, Issue #778)
|
|
6554
|
+
*
|
|
6555
|
+
* Generates code that works on:
|
|
6556
|
+
* - ARM platforms (bare-metal or Arduino): Uses inline assembly for PRIMASK access
|
|
6557
|
+
* - AVR Arduino: Uses SREG save/restore pattern
|
|
6558
|
+
* - Other platforms: Falls back to CMSIS intrinsics
|
|
6559
|
+
*
|
|
6560
|
+
* This avoids dependencies on CMSIS headers which may not be available on all platforms
|
|
6561
|
+
* (e.g., Teensy 4.x via Arduino.h doesn't expose __get_PRIMASK/__set_PRIMASK).
|
|
6562
|
+
*/
|
|
6563
|
+
private generateIrqWrappers(): string[] {
|
|
6564
|
+
return [
|
|
6565
|
+
"// ADR-050: Platform-portable IRQ wrappers for critical sections",
|
|
6566
|
+
"#if defined(__arm__) || defined(__ARM_ARCH)",
|
|
6567
|
+
"// ARM platforms (including ARM Arduino like Teensy 4.x, Due, Zero)",
|
|
6568
|
+
"// Provide inline assembly PRIMASK access to avoid CMSIS header dependencies",
|
|
6569
|
+
"__attribute__((always_inline)) static inline uint32_t __cnx_get_PRIMASK(void) {",
|
|
6570
|
+
" uint32_t result;",
|
|
6571
|
+
' __asm volatile ("MRS %0, primask" : "=r" (result));',
|
|
6572
|
+
" return result;",
|
|
6573
|
+
"}",
|
|
6574
|
+
"__attribute__((always_inline)) static inline void __cnx_set_PRIMASK(uint32_t mask) {",
|
|
6575
|
+
' __asm volatile ("MSR primask, %0" :: "r" (mask) : "memory");',
|
|
6576
|
+
"}",
|
|
6577
|
+
"#if defined(ARDUINO)",
|
|
6578
|
+
"static inline void __cnx_disable_irq(void) { noInterrupts(); }",
|
|
6579
|
+
"#else",
|
|
6580
|
+
"__attribute__((always_inline)) static inline void __cnx_disable_irq(void) {",
|
|
6581
|
+
' __asm volatile ("cpsid i" ::: "memory");',
|
|
6582
|
+
"}",
|
|
6583
|
+
"#endif",
|
|
6584
|
+
"#elif defined(__AVR__)",
|
|
6585
|
+
"// AVR Arduino: use SREG for interrupt state",
|
|
6586
|
+
"// Note: Uses PRIMASK naming for API consistency across platforms (AVR has no PRIMASK)",
|
|
6587
|
+
"// Returns uint8_t which is implicitly widened to uint32_t at call sites - this is intentional",
|
|
6588
|
+
"static inline uint8_t __cnx_get_PRIMASK(void) { return SREG; }",
|
|
6589
|
+
"static inline void __cnx_set_PRIMASK(uint8_t mask) { SREG = mask; }",
|
|
6590
|
+
"static inline void __cnx_disable_irq(void) { cli(); }",
|
|
6591
|
+
"#else",
|
|
6592
|
+
"// Fallback: assume CMSIS is available",
|
|
6593
|
+
"static inline void __cnx_disable_irq(void) { __disable_irq(); }",
|
|
6594
|
+
"static inline uint32_t __cnx_get_PRIMASK(void) { return __get_PRIMASK(); }",
|
|
6595
|
+
"static inline void __cnx_set_PRIMASK(uint32_t mask) { __set_PRIMASK(mask); }",
|
|
6596
|
+
"#endif",
|
|
6597
|
+
"",
|
|
6598
|
+
];
|
|
6599
|
+
}
|
|
6600
|
+
|
|
6611
6601
|
/**
|
|
6612
6602
|
* Mark a clamp operation as used (will trigger helper generation)
|
|
6613
6603
|
*/
|
|
@@ -332,7 +332,7 @@ class TypeValidator {
|
|
|
332
332
|
return;
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
-
const scopeMembers = CodeGenState.
|
|
335
|
+
const scopeMembers = CodeGenState.getScopeMembers(currentScope);
|
|
336
336
|
if (scopeMembers?.has(identifier)) {
|
|
337
337
|
throw new Error(
|
|
338
338
|
`Error: Use 'this.${identifier}' to access scope member '${identifier}' inside scope '${currentScope}'`,
|
|
@@ -410,7 +410,7 @@ class TypeValidator {
|
|
|
410
410
|
identifier: string,
|
|
411
411
|
currentScope: string,
|
|
412
412
|
): string | null {
|
|
413
|
-
const scopeMembers = CodeGenState.
|
|
413
|
+
const scopeMembers = CodeGenState.getScopeMembers(currentScope);
|
|
414
414
|
if (scopeMembers?.has(identifier)) {
|
|
415
415
|
return `${currentScope}_${identifier}`;
|
|
416
416
|
}
|
|
@@ -242,7 +242,7 @@ describe("CodeGenerator Coverage Tests", () => {
|
|
|
242
242
|
|
|
243
243
|
// Manually set up scope context to test the resolution path
|
|
244
244
|
CodeGenState.currentScope = "Motor";
|
|
245
|
-
CodeGenState.
|
|
245
|
+
CodeGenState.setScopeMembers("Motor", new Set(["speed", "setSpeed"]));
|
|
246
246
|
|
|
247
247
|
// Now resolve should return prefixed name (line 633)
|
|
248
248
|
const resolved = generator.resolveIdentifier("speed");
|
|
@@ -253,7 +253,7 @@ describe("CodeGenerator Coverage Tests", () => {
|
|
|
253
253
|
const { generator } = setupGenerator("u32 globalVar; void main() {}");
|
|
254
254
|
|
|
255
255
|
CodeGenState.currentScope = "Motor";
|
|
256
|
-
CodeGenState.
|
|
256
|
+
CodeGenState.setScopeMembers("Motor", new Set(["speed"]));
|
|
257
257
|
|
|
258
258
|
// globalVar is not in Motor scope members
|
|
259
259
|
const resolved = generator.resolveIdentifier("globalVar");
|
|
@@ -7318,12 +7318,15 @@ describe("CodeGenerator", () => {
|
|
|
7318
7318
|
expect(code).toContain("cfg.enabled = true");
|
|
7319
7319
|
});
|
|
7320
7320
|
|
|
7321
|
-
it("should throw when writing register from inside scope without global prefix", () => {
|
|
7321
|
+
it("should throw when writing register from inside scope when SHADOWED without global prefix", () => {
|
|
7322
|
+
// Issue #779: Ambiguity-aware validation - only require global. when shadowed
|
|
7322
7323
|
const source = `
|
|
7323
7324
|
register GPIO @ 0x40000000 {
|
|
7324
7325
|
DR: u32 rw @ 0x00,
|
|
7325
7326
|
}
|
|
7326
7327
|
scope Motor {
|
|
7328
|
+
// Shadow the register name with a scope member
|
|
7329
|
+
u32 GPIO <- 0;
|
|
7327
7330
|
public void init() {
|
|
7328
7331
|
GPIO.DR <- 0xFF;
|
|
7329
7332
|
}
|
|
@@ -7342,6 +7345,31 @@ describe("CodeGenerator", () => {
|
|
|
7342
7345
|
).toThrow("Use 'global.GPIO.DR' to access register");
|
|
7343
7346
|
});
|
|
7344
7347
|
|
|
7348
|
+
it("should allow bare register access from inside scope when NOT shadowed", () => {
|
|
7349
|
+
// Issue #779: Ambiguity-aware validation - allow unambiguous access
|
|
7350
|
+
const source = `
|
|
7351
|
+
register GPIO @ 0x40000000 {
|
|
7352
|
+
DR: u32 rw @ 0x00,
|
|
7353
|
+
}
|
|
7354
|
+
scope Motor {
|
|
7355
|
+
public void init() {
|
|
7356
|
+
GPIO.DR <- 0xFF;
|
|
7357
|
+
}
|
|
7358
|
+
}
|
|
7359
|
+
`;
|
|
7360
|
+
const { tree, tokenStream } = CNextSourceParser.parse(source);
|
|
7361
|
+
const generator = new CodeGenerator();
|
|
7362
|
+
const tSymbols = CNextResolver.resolve(tree, "test.cnx");
|
|
7363
|
+
const symbols = TSymbolInfoAdapter.convert(tSymbols);
|
|
7364
|
+
const code = generator.generate(tree, tokenStream, {
|
|
7365
|
+
symbolInfo: symbols,
|
|
7366
|
+
sourcePath: "test.cnx",
|
|
7367
|
+
});
|
|
7368
|
+
|
|
7369
|
+
// Should generate GPIO_DR without error since GPIO is unambiguous
|
|
7370
|
+
expect(code).toContain("GPIO_DR = 0xFF");
|
|
7371
|
+
});
|
|
7372
|
+
|
|
7345
7373
|
it("should generate scope member write from outside any scope", () => {
|
|
7346
7374
|
const source = `
|
|
7347
7375
|
scope Timer {
|
|
@@ -38,9 +38,7 @@ describe("TypeValidator.resolveBareIdentifier", () => {
|
|
|
38
38
|
|
|
39
39
|
beforeEach(() => {
|
|
40
40
|
CodeGenState.reset();
|
|
41
|
-
CodeGenState.
|
|
42
|
-
["Motor", new Set(["speed", "maxSpeed"])],
|
|
43
|
-
]);
|
|
41
|
+
CodeGenState.setScopeMembers("Motor", new Set(["speed", "maxSpeed"]));
|
|
44
42
|
CodeGenState.typeRegistry.set("globalCounter", {
|
|
45
43
|
baseType: "u32",
|
|
46
44
|
bitWidth: 32,
|
|
@@ -89,7 +89,9 @@ function setupState(options: SetupStateOptions = {}): void {
|
|
|
89
89
|
CodeGenState.currentScope = options.currentScope;
|
|
90
90
|
}
|
|
91
91
|
if (options.scopeMembers) {
|
|
92
|
-
|
|
92
|
+
for (const [scope, members] of options.scopeMembers) {
|
|
93
|
+
CodeGenState.setScopeMembers(scope, members);
|
|
94
|
+
}
|
|
93
95
|
}
|
|
94
96
|
if (options.currentParameters) {
|
|
95
97
|
CodeGenState.currentParameters = options.currentParameters;
|
|
@@ -18,6 +18,15 @@ interface IContextBuilderDeps {
|
|
|
18
18
|
|
|
19
19
|
/** Generate C expression for a value */
|
|
20
20
|
generateExpression(ctx: Parser.ExpressionContext): string;
|
|
21
|
+
|
|
22
|
+
/** Generate fully-resolved assignment target with scope prefixes */
|
|
23
|
+
generateAssignmentTarget(ctx: Parser.AssignmentTargetContext): string;
|
|
24
|
+
|
|
25
|
+
/** Check if an identifier is a known register (for skipping resolution) */
|
|
26
|
+
isKnownRegister(name: string): boolean;
|
|
27
|
+
|
|
28
|
+
/** Current scope name (for scoped register detection) */
|
|
29
|
+
currentScope: string | null;
|
|
21
30
|
}
|
|
22
31
|
|
|
23
32
|
/**
|
|
@@ -32,6 +41,37 @@ interface ITargetExtraction {
|
|
|
32
41
|
lastSubscriptExprCount: number;
|
|
33
42
|
}
|
|
34
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Extract the base identifier from a resolved target string.
|
|
46
|
+
* Removes subscripts ([...]) from the resolved target.
|
|
47
|
+
* Examples:
|
|
48
|
+
* "ArrayBug_data[0]" -> "ArrayBug_data"
|
|
49
|
+
* "matrix[i][j]" -> "matrix"
|
|
50
|
+
* "item.field" -> "item"
|
|
51
|
+
* "Motor_speed" -> "Motor_speed"
|
|
52
|
+
*/
|
|
53
|
+
function extractResolvedBaseIdentifier(resolvedTarget: string): string {
|
|
54
|
+
// Assumes resolvedTarget always starts with a valid identifier (not empty or starting with [ . ->)
|
|
55
|
+
// as generated by generateAssignmentTarget()
|
|
56
|
+
const bracketIndex = resolvedTarget.indexOf("[");
|
|
57
|
+
const dotIndex = resolvedTarget.indexOf(".");
|
|
58
|
+
const arrowIndex = resolvedTarget.indexOf("->");
|
|
59
|
+
|
|
60
|
+
// Find the earliest terminator
|
|
61
|
+
let endIndex = resolvedTarget.length;
|
|
62
|
+
if (bracketIndex !== -1 && bracketIndex < endIndex) {
|
|
63
|
+
endIndex = bracketIndex;
|
|
64
|
+
}
|
|
65
|
+
if (dotIndex !== -1 && dotIndex < endIndex) {
|
|
66
|
+
endIndex = dotIndex;
|
|
67
|
+
}
|
|
68
|
+
if (arrowIndex !== -1 && arrowIndex < endIndex) {
|
|
69
|
+
endIndex = arrowIndex;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return resolvedTarget.substring(0, endIndex);
|
|
73
|
+
}
|
|
74
|
+
|
|
35
75
|
/**
|
|
36
76
|
* Extract base identifier from assignment target.
|
|
37
77
|
* With unified grammar, all patterns use IDENTIFIER postfixTargetOp*.
|
|
@@ -100,6 +140,13 @@ function buildAssignmentContext(
|
|
|
100
140
|
// Generate value expression
|
|
101
141
|
const generatedValue = deps.generateExpression(valueCtx);
|
|
102
142
|
|
|
143
|
+
// Generate fully-resolved target (with scope prefixes)
|
|
144
|
+
const resolvedTarget = deps.generateAssignmentTarget(targetCtx);
|
|
145
|
+
|
|
146
|
+
// Extract resolved base identifier for type lookups
|
|
147
|
+
// Removes subscripts ([...]) and member access (. or ->) from the end
|
|
148
|
+
const resolvedBaseIdentifier = extractResolvedBaseIdentifier(resolvedTarget);
|
|
149
|
+
|
|
103
150
|
// Extract target info
|
|
104
151
|
const hasGlobal = targetCtx.GLOBAL() !== null;
|
|
105
152
|
const hasThis = targetCtx.THIS() !== null;
|
|
@@ -152,6 +199,8 @@ function buildAssignmentContext(
|
|
|
152
199
|
cOp,
|
|
153
200
|
isCompound,
|
|
154
201
|
generatedValue,
|
|
202
|
+
resolvedTarget,
|
|
203
|
+
resolvedBaseIdentifier,
|
|
155
204
|
firstIdTypeInfo,
|
|
156
205
|
memberAccessDepth,
|
|
157
206
|
subscriptDepth,
|
|
@@ -67,6 +67,21 @@ interface IAssignmentContext {
|
|
|
67
67
|
/** Generated C expression for the value (right-hand side) */
|
|
68
68
|
readonly generatedValue: string;
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Fully-resolved assignment target with scope prefixes applied.
|
|
72
|
+
* Use this instead of raw identifiers to ensure proper scope resolution.
|
|
73
|
+
* Example: "data" inside scope ArrayBug -> "ArrayBug_data"
|
|
74
|
+
*/
|
|
75
|
+
readonly resolvedTarget: string;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Resolved base identifier for type lookups.
|
|
79
|
+
* Extracted from resolvedTarget by removing subscripts and member access.
|
|
80
|
+
* Example: "ArrayBug_data[0]" -> "ArrayBug_data"
|
|
81
|
+
* Use this for CodeGenState.typeRegistry lookups instead of identifiers[0].
|
|
82
|
+
*/
|
|
83
|
+
readonly resolvedBaseIdentifier: string;
|
|
84
|
+
|
|
70
85
|
// === Type info (looked up from registry) ===
|
|
71
86
|
|
|
72
87
|
/** First identifier's type info, if found */
|
|
@@ -15,6 +15,11 @@ import TTypeInfo from "../../types/TTypeInfo";
|
|
|
15
15
|
function createMockContext(
|
|
16
16
|
overrides: Partial<IAssignmentContext> = {},
|
|
17
17
|
): IAssignmentContext {
|
|
18
|
+
// Compute resolvedBaseIdentifier from resolvedTarget if not explicitly provided
|
|
19
|
+
const resolvedTarget = overrides.resolvedTarget ?? "x";
|
|
20
|
+
const resolvedBaseIdentifier =
|
|
21
|
+
overrides.resolvedBaseIdentifier ?? resolvedTarget.split(/[[.]/)[0];
|
|
22
|
+
|
|
18
23
|
return {
|
|
19
24
|
statementCtx: {} as IAssignmentContext["statementCtx"],
|
|
20
25
|
targetCtx: {} as IAssignmentContext["targetCtx"],
|
|
@@ -31,6 +36,8 @@ function createMockContext(
|
|
|
31
36
|
cOp: "=",
|
|
32
37
|
isCompound: false,
|
|
33
38
|
generatedValue: "5",
|
|
39
|
+
resolvedTarget,
|
|
40
|
+
resolvedBaseIdentifier,
|
|
34
41
|
firstIdTypeInfo: null,
|
|
35
42
|
memberAccessDepth: 0,
|
|
36
43
|
subscriptDepth: 0,
|
|
@@ -20,26 +20,30 @@ function gen(): ICodeGenApi {
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Handle simple array element: arr[i] <- value
|
|
23
|
+
*
|
|
24
|
+
* Uses resolvedTarget which includes scope prefix and subscript,
|
|
25
|
+
* e.g., "data[0]" inside scope ArrayBug -> "ArrayBug_data[0]"
|
|
23
26
|
*/
|
|
24
27
|
function handleArrayElement(ctx: IAssignmentContext): string {
|
|
25
|
-
|
|
26
|
-
const index = gen().generateExpression(ctx.subscripts[0]);
|
|
27
|
-
|
|
28
|
-
return `${name}[${index}] ${ctx.cOp} ${ctx.generatedValue};`;
|
|
28
|
+
return `${ctx.resolvedTarget} ${ctx.cOp} ${ctx.generatedValue};`;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Handle multi-dimensional array element: matrix[i][j] <- value
|
|
33
|
+
*
|
|
34
|
+
* Uses resolvedTarget which includes scope prefix and all subscripts.
|
|
35
|
+
* Uses resolvedBaseIdentifier for type lookups to support scoped arrays.
|
|
33
36
|
*/
|
|
34
37
|
function handleMultiDimArrayElement(ctx: IAssignmentContext): string {
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
// Use resolvedBaseIdentifier for type lookup (includes scope prefix)
|
|
39
|
+
// e.g., "ArrayBug_data" instead of "data"
|
|
40
|
+
const typeInfo = CodeGenState.typeRegistry.get(ctx.resolvedBaseIdentifier);
|
|
37
41
|
|
|
38
42
|
// ADR-036: Compile-time bounds checking for constant indices
|
|
39
43
|
if (typeInfo?.arrayDimensions) {
|
|
40
44
|
const line = ctx.subscripts[0]?.start?.line ?? 0;
|
|
41
45
|
TypeValidator.checkArrayBounds(
|
|
42
|
-
|
|
46
|
+
ctx.resolvedBaseIdentifier,
|
|
43
47
|
[...typeInfo.arrayDimensions],
|
|
44
48
|
[...ctx.subscripts],
|
|
45
49
|
line,
|
|
@@ -47,11 +51,7 @@ function handleMultiDimArrayElement(ctx: IAssignmentContext): string {
|
|
|
47
51
|
);
|
|
48
52
|
}
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
.map((e) => gen().generateExpression(e))
|
|
52
|
-
.join("][");
|
|
53
|
-
|
|
54
|
-
return `${name}[${indices}] ${ctx.cOp} ${ctx.generatedValue};`;
|
|
54
|
+
return `${ctx.resolvedTarget} ${ctx.cOp} ${ctx.generatedValue};`;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
@@ -69,7 +69,8 @@ function handleArraySlice(ctx: IAssignmentContext): string {
|
|
|
69
69
|
);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
// Use resolvedBaseIdentifier for type lookup (includes scope prefix)
|
|
73
|
+
const name = ctx.resolvedBaseIdentifier;
|
|
73
74
|
const typeInfo = CodeGenState.typeRegistry.get(name);
|
|
74
75
|
|
|
75
76
|
// Get line number for error messages
|
|
@@ -77,10 +78,12 @@ function handleArraySlice(ctx: IAssignmentContext): string {
|
|
|
77
78
|
|
|
78
79
|
// Validate 1D array only
|
|
79
80
|
if (typeInfo?.arrayDimensions && typeInfo.arrayDimensions.length > 1) {
|
|
81
|
+
// Use raw identifier in error message for clarity
|
|
82
|
+
const rawName = ctx.identifiers[0];
|
|
80
83
|
throw new Error(
|
|
81
84
|
`${line}:0 Error: Slice assignment is only valid on one-dimensional arrays. ` +
|
|
82
|
-
`'${
|
|
83
|
-
`Access the innermost dimension first (e.g., ${
|
|
85
|
+
`'${rawName}' has ${typeInfo.arrayDimensions.length} dimensions. ` +
|
|
86
|
+
`Access the innermost dimension first (e.g., ${rawName}[index][offset, length]).`,
|
|
84
87
|
);
|
|
85
88
|
}
|
|
86
89
|
|
|
@@ -109,17 +112,21 @@ function handleArraySlice(ctx: IAssignmentContext): string {
|
|
|
109
112
|
} else if (typeInfo?.arrayDimensions?.[0]) {
|
|
110
113
|
capacity = typeInfo.arrayDimensions[0];
|
|
111
114
|
} else {
|
|
115
|
+
// Use raw identifier in error message for clarity
|
|
116
|
+
const rawName = ctx.identifiers[0];
|
|
112
117
|
throw new Error(
|
|
113
|
-
`${line}:0 Error: Cannot determine buffer size for '${
|
|
118
|
+
`${line}:0 Error: Cannot determine buffer size for '${rawName}' at compile time.`,
|
|
114
119
|
);
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
// Bounds validation
|
|
118
123
|
if (offsetValue + lengthValue > capacity) {
|
|
124
|
+
// Use raw identifier in error message for clarity
|
|
125
|
+
const rawName = ctx.identifiers[0];
|
|
119
126
|
throw new Error(
|
|
120
127
|
`${line}:0 Error: Slice assignment out of bounds: ` +
|
|
121
128
|
`offset(${offsetValue}) + length(${lengthValue}) = ${offsetValue + lengthValue} ` +
|
|
122
|
-
`exceeds buffer capacity(${capacity}) for '${
|
|
129
|
+
`exceeds buffer capacity(${capacity}) for '${rawName}'.`,
|
|
123
130
|
);
|
|
124
131
|
}
|
|
125
132
|
|
|
@@ -33,11 +33,14 @@ function validateNotCompound(ctx: IAssignmentContext): void {
|
|
|
33
33
|
/**
|
|
34
34
|
* Handle single bit on integer variable: flags[3] <- true
|
|
35
35
|
* Also handles float bit indexing: f32Var[3] <- true
|
|
36
|
+
* Uses resolvedBaseIdentifier for proper scope prefix support.
|
|
36
37
|
*/
|
|
37
38
|
function handleIntegerBit(ctx: IAssignmentContext): string {
|
|
38
39
|
validateNotCompound(ctx);
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
// Use resolvedBaseIdentifier for type lookup and code generation
|
|
42
|
+
// e.g., "ArrayBug_flags" instead of "flags"
|
|
43
|
+
const name = ctx.resolvedBaseIdentifier;
|
|
41
44
|
const bitIndex = gen().generateExpression(ctx.subscripts[0]);
|
|
42
45
|
const typeInfo = CodeGenState.typeRegistry.get(name);
|
|
43
46
|
|
|
@@ -67,11 +70,13 @@ function handleIntegerBit(ctx: IAssignmentContext): string {
|
|
|
67
70
|
/**
|
|
68
71
|
* Handle bit range on integer variable: flags[0, 3] <- 5
|
|
69
72
|
* Also handles float bit range: f32Var[0, 8] <- 0xFF
|
|
73
|
+
* Uses resolvedBaseIdentifier for proper scope prefix support.
|
|
70
74
|
*/
|
|
71
75
|
function handleIntegerBitRange(ctx: IAssignmentContext): string {
|
|
72
76
|
validateNotCompound(ctx);
|
|
73
77
|
|
|
74
|
-
|
|
78
|
+
// Use resolvedBaseIdentifier for type lookup and code generation
|
|
79
|
+
const name = ctx.resolvedBaseIdentifier;
|
|
75
80
|
const start = gen().generateExpression(ctx.subscripts[0]);
|
|
76
81
|
const width = gen().generateExpression(ctx.subscripts[1]);
|
|
77
82
|
const typeInfo = CodeGenState.typeRegistry.get(name);
|
|
@@ -126,15 +131,19 @@ function handleStructMemberBit(ctx: IAssignmentContext): string {
|
|
|
126
131
|
|
|
127
132
|
/**
|
|
128
133
|
* Handle bit on multi-dimensional array element: matrix[i][j][FIELD_BIT] <- false
|
|
134
|
+
* Uses resolvedBaseIdentifier for proper scope prefix support.
|
|
129
135
|
*/
|
|
130
136
|
function handleArrayElementBit(ctx: IAssignmentContext): string {
|
|
131
137
|
validateNotCompound(ctx);
|
|
132
138
|
|
|
133
|
-
|
|
139
|
+
// Use resolvedBaseIdentifier for type lookup and code generation
|
|
140
|
+
const arrayName = ctx.resolvedBaseIdentifier;
|
|
134
141
|
const typeInfo = CodeGenState.typeRegistry.get(arrayName);
|
|
135
142
|
|
|
136
143
|
if (!typeInfo?.arrayDimensions) {
|
|
137
|
-
|
|
144
|
+
// Use raw identifier in error message for clarity
|
|
145
|
+
const rawName = ctx.identifiers[0];
|
|
146
|
+
throw new Error(`Error: ${rawName} is not an array`);
|
|
138
147
|
}
|
|
139
148
|
|
|
140
149
|
const numDims = typeInfo.arrayDimensions.length;
|
|
@@ -160,12 +169,14 @@ function handleArrayElementBit(ctx: IAssignmentContext): string {
|
|
|
160
169
|
*
|
|
161
170
|
* The target is a chain like array[idx].member or struct.field with a
|
|
162
171
|
* bit range subscript [start, width] at the end.
|
|
172
|
+
* Uses resolvedBaseIdentifier for proper scope prefix support.
|
|
163
173
|
*/
|
|
164
174
|
function handleStructChainBitRange(ctx: IAssignmentContext): string {
|
|
165
175
|
validateNotCompound(ctx);
|
|
166
176
|
|
|
167
177
|
// Build the base target from postfixOps, excluding the last one (the bit range)
|
|
168
|
-
|
|
178
|
+
// Use resolvedBaseIdentifier for the base to include scope prefix
|
|
179
|
+
const baseId = ctx.resolvedBaseIdentifier;
|
|
169
180
|
const opsBeforeLast = ctx.postfixOps.slice(0, -1);
|
|
170
181
|
|
|
171
182
|
let baseTarget = baseId;
|
package/src/transpiler/output/codegen/assignment/handlers/__tests__/AccessPatternHandlers.test.ts
CHANGED
|
@@ -16,8 +16,14 @@ import HandlerTestUtils from "./handlerTestUtils";
|
|
|
16
16
|
function createMockContext(
|
|
17
17
|
overrides: Partial<IAssignmentContext> = {},
|
|
18
18
|
): IAssignmentContext {
|
|
19
|
+
// Default resolved values based on first identifier
|
|
20
|
+
const identifiers = overrides.identifiers ?? ["Counter", "value"];
|
|
21
|
+
const resolvedTarget = overrides.resolvedTarget ?? `${identifiers.join("_")}`;
|
|
22
|
+
const resolvedBaseIdentifier =
|
|
23
|
+
overrides.resolvedBaseIdentifier ?? identifiers[0];
|
|
24
|
+
|
|
19
25
|
return {
|
|
20
|
-
identifiers
|
|
26
|
+
identifiers,
|
|
21
27
|
subscripts: [],
|
|
22
28
|
isCompound: false,
|
|
23
29
|
cnextOp: "<-",
|
|
@@ -34,6 +40,8 @@ function createMockContext(
|
|
|
34
40
|
isSimpleIdentifier: false,
|
|
35
41
|
isSimpleThisAccess: false,
|
|
36
42
|
isSimpleGlobalAccess: false,
|
|
43
|
+
resolvedTarget,
|
|
44
|
+
resolvedBaseIdentifier,
|
|
37
45
|
...overrides,
|
|
38
46
|
} as IAssignmentContext;
|
|
39
47
|
}
|
|
@@ -28,8 +28,14 @@ import HandlerTestUtils from "./handlerTestUtils";
|
|
|
28
28
|
function createMockContext(
|
|
29
29
|
overrides: Partial<IAssignmentContext> = {},
|
|
30
30
|
): IAssignmentContext {
|
|
31
|
+
// Default resolved values based on first identifier
|
|
32
|
+
const identifiers = overrides.identifiers ?? ["arr"];
|
|
33
|
+
const resolvedTarget = overrides.resolvedTarget ?? `${identifiers[0]}[i]`;
|
|
34
|
+
const resolvedBaseIdentifier =
|
|
35
|
+
overrides.resolvedBaseIdentifier ?? identifiers[0];
|
|
36
|
+
|
|
31
37
|
return {
|
|
32
|
-
identifiers
|
|
38
|
+
identifiers,
|
|
33
39
|
subscripts: [{ mockValue: "i", start: { line: 1 } } as never],
|
|
34
40
|
isCompound: false,
|
|
35
41
|
cnextOp: "<-",
|
|
@@ -46,6 +52,8 @@ function createMockContext(
|
|
|
46
52
|
isSimpleIdentifier: false,
|
|
47
53
|
isSimpleThisAccess: false,
|
|
48
54
|
isSimpleGlobalAccess: false,
|
|
55
|
+
resolvedTarget,
|
|
56
|
+
resolvedBaseIdentifier,
|
|
49
57
|
...overrides,
|
|
50
58
|
} as IAssignmentContext;
|
|
51
59
|
}
|
|
@@ -96,6 +104,7 @@ describe("ArrayHandlers", () => {
|
|
|
96
104
|
isCompound: true,
|
|
97
105
|
cnextOp: "+<-",
|
|
98
106
|
cOp: "+=",
|
|
107
|
+
resolvedTarget: "arr[0]",
|
|
99
108
|
});
|
|
100
109
|
|
|
101
110
|
const result = getHandler()!(ctx);
|
|
@@ -109,6 +118,8 @@ describe("ArrayHandlers", () => {
|
|
|
109
118
|
});
|
|
110
119
|
const ctx = createMockContext({
|
|
111
120
|
identifiers: ["buffer"],
|
|
121
|
+
resolvedTarget: "buffer[idx]",
|
|
122
|
+
resolvedBaseIdentifier: "buffer",
|
|
112
123
|
});
|
|
113
124
|
|
|
114
125
|
const result = getHandler()!(ctx);
|
|
@@ -137,6 +148,8 @@ describe("ArrayHandlers", () => {
|
|
|
137
148
|
{ mockValue: "j", start: { line: 1 } } as never,
|
|
138
149
|
],
|
|
139
150
|
subscriptDepth: 2,
|
|
151
|
+
resolvedTarget: "matrix[i][j]",
|
|
152
|
+
resolvedBaseIdentifier: "matrix",
|
|
140
153
|
});
|
|
141
154
|
|
|
142
155
|
const result = getHandler()!(ctx);
|
|
@@ -160,6 +173,8 @@ describe("ArrayHandlers", () => {
|
|
|
160
173
|
{ mockValue: "z", start: { line: 1 } } as never,
|
|
161
174
|
],
|
|
162
175
|
subscriptDepth: 3,
|
|
176
|
+
resolvedTarget: "cube[x][y][z]",
|
|
177
|
+
resolvedBaseIdentifier: "cube",
|
|
163
178
|
});
|
|
164
179
|
|
|
165
180
|
const result = getHandler()!(ctx);
|
|
@@ -212,6 +227,8 @@ describe("ArrayHandlers", () => {
|
|
|
212
227
|
],
|
|
213
228
|
isCompound: true,
|
|
214
229
|
cOp: "-=",
|
|
230
|
+
resolvedTarget: "grid[0][1]",
|
|
231
|
+
resolvedBaseIdentifier: "grid",
|
|
215
232
|
});
|
|
216
233
|
|
|
217
234
|
const result = getHandler()!(ctx);
|
package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts
CHANGED
|
@@ -16,8 +16,14 @@ import HandlerTestUtils from "./handlerTestUtils";
|
|
|
16
16
|
function createMockContext(
|
|
17
17
|
overrides: Partial<IAssignmentContext> = {},
|
|
18
18
|
): IAssignmentContext {
|
|
19
|
+
// Default resolved values based on first identifier
|
|
20
|
+
const identifiers = overrides.identifiers ?? ["flags"];
|
|
21
|
+
const resolvedTarget = overrides.resolvedTarget ?? `${identifiers[0]}[3]`;
|
|
22
|
+
const resolvedBaseIdentifier =
|
|
23
|
+
overrides.resolvedBaseIdentifier ?? identifiers[0];
|
|
24
|
+
|
|
19
25
|
return {
|
|
20
|
-
identifiers
|
|
26
|
+
identifiers,
|
|
21
27
|
subscripts: [{ mockValue: "3" } as never],
|
|
22
28
|
isCompound: false,
|
|
23
29
|
cnextOp: "<-",
|
|
@@ -34,6 +40,8 @@ function createMockContext(
|
|
|
34
40
|
isSimpleIdentifier: false,
|
|
35
41
|
isSimpleThisAccess: false,
|
|
36
42
|
isSimpleGlobalAccess: false,
|
|
43
|
+
resolvedTarget,
|
|
44
|
+
resolvedBaseIdentifier,
|
|
37
45
|
...overrides,
|
|
38
46
|
} as IAssignmentContext;
|
|
39
47
|
}
|
|
@@ -25,8 +25,14 @@ import HandlerTestUtils from "./handlerTestUtils";
|
|
|
25
25
|
function createMockContext(
|
|
26
26
|
overrides: Partial<IAssignmentContext> = {},
|
|
27
27
|
): IAssignmentContext {
|
|
28
|
+
// Default resolved values based on first identifier
|
|
29
|
+
const identifiers = overrides.identifiers ?? ["flags", "Running"];
|
|
30
|
+
const resolvedTarget = overrides.resolvedTarget ?? identifiers[0];
|
|
31
|
+
const resolvedBaseIdentifier =
|
|
32
|
+
overrides.resolvedBaseIdentifier ?? identifiers[0];
|
|
33
|
+
|
|
28
34
|
return {
|
|
29
|
-
identifiers
|
|
35
|
+
identifiers,
|
|
30
36
|
subscripts: [],
|
|
31
37
|
isCompound: false,
|
|
32
38
|
cnextOp: "<-",
|
|
@@ -44,6 +50,8 @@ function createMockContext(
|
|
|
44
50
|
isSimpleIdentifier: false,
|
|
45
51
|
isSimpleThisAccess: false,
|
|
46
52
|
isSimpleGlobalAccess: false,
|
|
53
|
+
resolvedTarget,
|
|
54
|
+
resolvedBaseIdentifier,
|
|
47
55
|
...overrides,
|
|
48
56
|
} as IAssignmentContext;
|
|
49
57
|
}
|
package/src/transpiler/output/codegen/assignment/handlers/__tests__/RegisterHandlers.test.ts
CHANGED
|
@@ -16,8 +16,15 @@ import HandlerTestUtils from "./handlerTestUtils";
|
|
|
16
16
|
function createMockContext(
|
|
17
17
|
overrides: Partial<IAssignmentContext> = {},
|
|
18
18
|
): IAssignmentContext {
|
|
19
|
+
// Default resolved values based on first identifier
|
|
20
|
+
const identifiers = overrides.identifiers ?? ["GPIO7", "DR_SET"];
|
|
21
|
+
const resolvedTarget =
|
|
22
|
+
overrides.resolvedTarget ?? `${identifiers.join("_")}[LED_BIT]`;
|
|
23
|
+
const resolvedBaseIdentifier =
|
|
24
|
+
overrides.resolvedBaseIdentifier ?? identifiers[0];
|
|
25
|
+
|
|
19
26
|
return {
|
|
20
|
-
identifiers
|
|
27
|
+
identifiers,
|
|
21
28
|
subscripts: [{ mockValue: "LED_BIT" } as never],
|
|
22
29
|
isCompound: false,
|
|
23
30
|
cnextOp: "<-",
|
|
@@ -34,6 +41,8 @@ function createMockContext(
|
|
|
34
41
|
isSimpleIdentifier: false,
|
|
35
42
|
isSimpleThisAccess: false,
|
|
36
43
|
isSimpleGlobalAccess: false,
|
|
44
|
+
resolvedTarget,
|
|
45
|
+
resolvedBaseIdentifier,
|
|
37
46
|
...overrides,
|
|
38
47
|
} as IAssignmentContext;
|
|
39
48
|
}
|
|
@@ -16,8 +16,14 @@ import HandlerTestUtils from "./handlerTestUtils";
|
|
|
16
16
|
function createMockContext(
|
|
17
17
|
overrides: Partial<IAssignmentContext> = {},
|
|
18
18
|
): IAssignmentContext {
|
|
19
|
+
// Default resolved values based on first identifier
|
|
20
|
+
const identifiers = overrides.identifiers ?? ["counter"];
|
|
21
|
+
const resolvedTarget = overrides.resolvedTarget ?? identifiers[0];
|
|
22
|
+
const resolvedBaseIdentifier =
|
|
23
|
+
overrides.resolvedBaseIdentifier ?? identifiers[0];
|
|
24
|
+
|
|
19
25
|
return {
|
|
20
|
-
identifiers
|
|
26
|
+
identifiers,
|
|
21
27
|
subscripts: [],
|
|
22
28
|
isCompound: true,
|
|
23
29
|
cnextOp: "+<-",
|
|
@@ -34,6 +40,8 @@ function createMockContext(
|
|
|
34
40
|
isSimpleIdentifier: true,
|
|
35
41
|
isSimpleThisAccess: false,
|
|
36
42
|
isSimpleGlobalAccess: false,
|
|
43
|
+
resolvedTarget,
|
|
44
|
+
resolvedBaseIdentifier,
|
|
37
45
|
...overrides,
|
|
38
46
|
} as IAssignmentContext;
|
|
39
47
|
}
|
|
@@ -16,14 +16,22 @@ import HandlerTestUtils from "./handlerTestUtils";
|
|
|
16
16
|
function createMockContext(
|
|
17
17
|
overrides: Partial<IAssignmentContext> = {},
|
|
18
18
|
): IAssignmentContext {
|
|
19
|
+
// Default resolved values based on first identifier
|
|
20
|
+
const identifiers = overrides.identifiers ?? ["testVar"];
|
|
21
|
+
const resolvedTarget = overrides.resolvedTarget ?? identifiers[0];
|
|
22
|
+
const resolvedBaseIdentifier =
|
|
23
|
+
overrides.resolvedBaseIdentifier ?? identifiers[0];
|
|
24
|
+
|
|
19
25
|
return {
|
|
20
|
-
identifiers
|
|
26
|
+
identifiers,
|
|
21
27
|
subscripts: [],
|
|
22
28
|
isCompound: false,
|
|
23
29
|
cnextOp: "<-",
|
|
24
30
|
cOp: "=",
|
|
25
31
|
generatedValue: '"hello"',
|
|
26
32
|
targetCtx: {} as never,
|
|
33
|
+
resolvedTarget,
|
|
34
|
+
resolvedBaseIdentifier,
|
|
27
35
|
...overrides,
|
|
28
36
|
} as IAssignmentContext;
|
|
29
37
|
}
|
|
@@ -89,7 +89,12 @@ class MemberSeparatorResolver {
|
|
|
89
89
|
// Scope member access: Sensor.buffer -> Sensor_buffer
|
|
90
90
|
// Works with or without global. prefix (both are valid syntax)
|
|
91
91
|
if (deps.isKnownScope(identifierChain[0])) {
|
|
92
|
-
|
|
92
|
+
// Issue #779: Skip cross-scope validation for scoped register access
|
|
93
|
+
// Board.GPIO where Board_GPIO is a known register is valid
|
|
94
|
+
const scopedRegisterName = `${identifierChain[0]}_${memberName}`;
|
|
95
|
+
if (!deps.isKnownRegister(scopedRegisterName)) {
|
|
96
|
+
deps.validateCrossScopeVisibility(identifierChain[0], memberName);
|
|
97
|
+
}
|
|
93
98
|
return "_";
|
|
94
99
|
}
|
|
95
100
|
|
|
@@ -187,7 +187,7 @@ export default class CodeGenState {
|
|
|
187
187
|
static localArrays: Set<string> = new Set();
|
|
188
188
|
|
|
189
189
|
/** Scope member names: scope -> Set of member names */
|
|
190
|
-
static scopeMembers: Map<string, Set<string>> = new Map();
|
|
190
|
+
private static scopeMembers: Map<string, Set<string>> = new Map();
|
|
191
191
|
|
|
192
192
|
/** Float bit indexing: declared shadow variables */
|
|
193
193
|
static floatBitShadows: Set<string> = new Set();
|
|
@@ -463,6 +463,26 @@ export default class CodeGenState {
|
|
|
463
463
|
return this.modifiedParameters.get(funcName)?.has(paramName) ?? false;
|
|
464
464
|
}
|
|
465
465
|
|
|
466
|
+
/**
|
|
467
|
+
* Compute unmodified parameters for all functions on-demand.
|
|
468
|
+
* Returns a map of function name -> Set of parameter names NOT modified.
|
|
469
|
+
* Computed from functionSignatures and modifiedParameters (no cached state).
|
|
470
|
+
*/
|
|
471
|
+
static getUnmodifiedParameters(): Map<string, Set<string>> {
|
|
472
|
+
const result = new Map<string, Set<string>>();
|
|
473
|
+
for (const [funcName, signature] of this.functionSignatures) {
|
|
474
|
+
const modifiedSet = this.modifiedParameters.get(funcName);
|
|
475
|
+
const unmodified = new Set<string>();
|
|
476
|
+
for (const param of signature.parameters) {
|
|
477
|
+
if (!modifiedSet?.has(param.name)) {
|
|
478
|
+
unmodified.add(param.name);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
result.set(funcName, unmodified);
|
|
482
|
+
}
|
|
483
|
+
return result;
|
|
484
|
+
}
|
|
485
|
+
|
|
466
486
|
/**
|
|
467
487
|
* Check if a parameter should pass by value.
|
|
468
488
|
*/
|
|
@@ -542,6 +562,20 @@ export default class CodeGenState {
|
|
|
542
562
|
return this.scopeMembers.get(scopeName);
|
|
543
563
|
}
|
|
544
564
|
|
|
565
|
+
/**
|
|
566
|
+
* Set members of a scope.
|
|
567
|
+
*/
|
|
568
|
+
static setScopeMembers(scopeName: string, members: Set<string>): void {
|
|
569
|
+
this.scopeMembers.set(scopeName, members);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Get all scope members (for IGeneratorState).
|
|
574
|
+
*/
|
|
575
|
+
static getAllScopeMembers(): ReadonlyMap<string, ReadonlySet<string>> {
|
|
576
|
+
return this.scopeMembers;
|
|
577
|
+
}
|
|
578
|
+
|
|
545
579
|
/**
|
|
546
580
|
* Check if an identifier is a member of the current scope.
|
|
547
581
|
*/
|
|
@@ -110,7 +110,7 @@ describe("CodeGenState", () => {
|
|
|
110
110
|
|
|
111
111
|
it("getScopeMembers returns members for known scope", () => {
|
|
112
112
|
const members = new Set(["member1", "member2"]);
|
|
113
|
-
CodeGenState.
|
|
113
|
+
CodeGenState.setScopeMembers("TestScope", members);
|
|
114
114
|
|
|
115
115
|
expect(CodeGenState.getScopeMembers("TestScope")).toBe(members);
|
|
116
116
|
});
|
|
@@ -122,14 +122,14 @@ describe("CodeGenState", () => {
|
|
|
122
122
|
|
|
123
123
|
it("isCurrentScopeMember returns false for non-member", () => {
|
|
124
124
|
CodeGenState.currentScope = "TestScope";
|
|
125
|
-
CodeGenState.
|
|
125
|
+
CodeGenState.setScopeMembers("TestScope", new Set(["member1"]));
|
|
126
126
|
|
|
127
127
|
expect(CodeGenState.isCurrentScopeMember("nonMember")).toBe(false);
|
|
128
128
|
});
|
|
129
129
|
|
|
130
130
|
it("isCurrentScopeMember returns true for member", () => {
|
|
131
131
|
CodeGenState.currentScope = "TestScope";
|
|
132
|
-
CodeGenState.
|
|
132
|
+
CodeGenState.setScopeMembers("TestScope", new Set(["member1"]));
|
|
133
133
|
|
|
134
134
|
expect(CodeGenState.isCurrentScopeMember("member1")).toBe(true);
|
|
135
135
|
});
|
|
@@ -143,14 +143,14 @@ describe("CodeGenState", () => {
|
|
|
143
143
|
|
|
144
144
|
it("returns identifier unchanged when not a scope member", () => {
|
|
145
145
|
CodeGenState.currentScope = "TestScope";
|
|
146
|
-
CodeGenState.
|
|
146
|
+
CodeGenState.setScopeMembers("TestScope", new Set(["member1"]));
|
|
147
147
|
|
|
148
148
|
expect(CodeGenState.resolveIdentifier("varName")).toBe("varName");
|
|
149
149
|
});
|
|
150
150
|
|
|
151
151
|
it("returns scoped name for scope member", () => {
|
|
152
152
|
CodeGenState.currentScope = "TestScope";
|
|
153
|
-
CodeGenState.
|
|
153
|
+
CodeGenState.setScopeMembers("TestScope", new Set(["member1"]));
|
|
154
154
|
|
|
155
155
|
expect(CodeGenState.resolveIdentifier("member1")).toBe(
|
|
156
156
|
"TestScope_member1",
|