All files / transpiler/output/codegen/assignment/handlers AssignmentHandlerUtils.ts

100% Statements 22/22
100% Branches 17/17
100% Functions 5/5
100% Lines 22/22

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135                                        13x 3x                             28x 5x                                           18x 5x         13x 3x                                           10x                                 18x   18x   5x 5x 5x     13x 13x 13x               31x   31x 31x 31x   31x        
/**
 * Assignment Handler Utilities
 *
 * Common utilities shared across assignment handlers to reduce duplication.
 * Issue #707: Extracted from RegisterHandlers.ts and AccessPatternHandlers.ts.
 */
 
import IRegisterNameResult from "./IRegisterNameResult";
import QualifiedCName from "../../../../../utils/QualifiedCName";
import ScopeUtils from "../../../../../utils/ScopeUtils";
import IScopeSymbol from "../../../../types/symbols/IScopeSymbol";
 
/**
 * Validate that 'this' is being used within a scope context.
 * Throws if currentScope is not set.
 *
 * @param currentScope - The current scope name or null
 * @throws Error if 'this' is used outside a scope
 */
function validateScopeContext(currentScope: IScopeSymbol | null): void {
  if (!currentScope) {
    throw new Error("Error: 'this' can only be used inside a scope");
  }
}
 
/**
 * Validate that compound assignment operators are not used with bit field access.
 *
 * @param isCompound - Whether this is a compound assignment
 * @param cnextOp - The C-Next operator being used
 * @throws Error if compound operator is used with bit fields
 */
function validateNoCompoundForBitAccess(
  isCompound: boolean,
  cnextOp: string,
): void {
  if (isCompound) {
    throw new Error(
      `Compound assignment operators not supported for bit field access: ${cnextOp}`,
    );
  }
}
 
/**
 * Validate write-only register assignment value.
 * Throws if trying to clear bits on a write-only register.
 *
 * @param value - The value being assigned
 * @param targetName - The full register name for error messages
 * @param bitIndex - The bit index expression for error messages
 * @param isSingleBit - True for single bit access, false for bit range
 * @throws Error if attempting to clear bits on write-only register
 */
function validateWriteOnlyValue(
  value: string,
  targetName: string,
  bitIndex: string,
  isSingleBit: boolean,
): void {
  if (isSingleBit && (value === "false" || value === "0")) {
    throw new Error(
      `Cannot assign false to write-only register bit ${targetName}[${bitIndex}]. ` +
        `Use the corresponding CLEAR register to clear bits.`,
    );
  }
  if (!isSingleBit && value === "0") {
    throw new Error(
      `Cannot assign 0 to write-only register bits ${targetName}[${bitIndex}]. ` +
        `Use the corresponding CLEAR register to clear bits.`,
    );
  }
}
 
/**
 * Build a scoped register name from scope and identifier parts.
 *
 * @param scopeName - The scope name prefix
 * @param parts - The identifier parts (register name, member name)
 * @returns The full scoped register name (e.g., "Scope_Register_Member")
 */
function buildScopedRegisterName(
  declaringScope: IScopeSymbol | null,
  parts: readonly string[],
): string {
  // #1285: the accumulator loop was a hand-rolled join -- each turn fed the
  // PREVIOUS result back in as if it were a scope, which is why `forMember` had to
  // accept an arbitrary string. The scope qualifies the head; the remaining parts
  // are register/member components joined textually.
  return QualifiedCName.fromParts([
    ScopeUtils.qualifyInScope(parts[0], declaringScope),
    ...parts.slice(1),
  ]);
}
 
/**
 * Build register name with automatic scope detection.
 *
 * @param identifiers - The identifier chain
 * @param isKnownScope - Function to check if an identifier is a known scope
 * @returns Object with fullName, regName, and isScoped flag
 */
function buildRegisterNameWithScopeDetection(
  identifiers: readonly string[],
  isKnownScope: (name: string) => boolean,
): IRegisterNameResult {
  const leadingId = identifiers[0];
 
  if (isKnownScope(leadingId) && identifiers.length >= 3) {
    // Scoped: Scope.Register.Member
    const regName = QualifiedCName.fromParts([leadingId, identifiers[1]]);
    const fullName = QualifiedCName.fromParts([regName, identifiers[2]]);
    return { fullName, regName, isScoped: true };
  } else {
    // Non-scoped: Register.Member
    const regName = leadingId;
    const fullName = QualifiedCName.fromParts([leadingId, identifiers[1]]);
    return { fullName, regName, isScoped: false };
  }
}
 
/**
 * Assignment Handler Utilities
 */
class AssignmentHandlerUtils {
  static readonly validateScopeContext = validateScopeContext;
  static readonly validateNoCompoundForBitAccess =
    validateNoCompoundForBitAccess;
  static readonly validateWriteOnlyValue = validateWriteOnlyValue;
  static readonly buildScopedRegisterName = buildScopedRegisterName;
  static readonly buildRegisterNameWithScopeDetection =
    buildRegisterNameWithScopeDetection;
}
 
export default AssignmentHandlerUtils;