All files / TRANSPILE/3-Render/codegen/helpers AssignmentOperatorMapper.ts

100% Statements 4/4
100% Branches 0/0
100% Functions 1/1
100% Lines 4/4

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                                                                                        469x 469x 469x       469x          
/**
 * ADR-001: the one place a C-Next assignment operator becomes its C form.
 *
 * #1322 unified the TABLE -- a byte-identical `ASSIGNMENT_OPERATOR_MAP` had
 * stood in `CodeGenerator` beside the shared one. What stayed duplicated was
 * the DECISION: `getText()`, the lookup, and the `|| "="` fallback were
 * rewritten at three call sites (`AssignmentContextBuilder`, and both the
 * `forAssignment` and `forUpdate` paths in `ControlFlowGenerator`). CLAUDE.md
 * counts that separately from the data -- "single source of truth means the
 * _decision_" -- and three copies of a fallback diverge silently.
 *
 * #1585 is what forced it: ADR-001 needs a provenance site for its assignment
 * half, and adding one to each of three sites would have been a fourth thing
 * to keep in step. One decision, one recording.
 *
 * #1588: a lookup miss is an INVARIANT, not a fallback. `|| "="` returned a
 * wrong answer rather than a missing one -- `i +<- 1` became `i = 1`, which in
 * a `for` update is an infinite loop in generated firmware, emitted at exit 0
 * with no diagnostic.
 *
 * It is `invariant()` and not `throw new`, per #1322b: the map and the grammar
 * are two halves of one fact, so a miss means the transpiler is wrong, not the
 * author's program. A `throw new` here would also rejoin the `output/` corpus
 * that #1322 emptied -- bucket 2 of `throw-classification.md` is exactly
 * this shape, and all sixteen of its sites became assertions for that reason.
 * `scripts/__tests__/assignment-operator-parity.test.ts` catches the divergence
 * at its source; this catches it at the point of use.
 */
import AdrProvenance from "../../../../instrumentation/AdrProvenance";
import ASSIGNMENT_OPERATOR_MAP from "../../../../utils/constants/OperatorMappings";
import invariant from "../../../../utils/invariant";
 
class AssignmentOperatorMapper {
  /**
   * The C operator for `cnextOp`, recording that ADR-001's rule fired at
   * `line` so the scope-context matrix can derive which cell it occupied.
   *
   * Takes the operator's TEXT and LINE rather than its parse context on
   * purpose. Accepting the context reads better at the three call sites, and
   * `npm run parse-tree:check` rejected it: #1317 gates the population of
   * modules holding a parse tree and forbids it growing, and this one has no
   * question for the tree that a string and a number cannot answer.
   */
  static toCOperator(cnextOp: string, line: number | undefined): string {
    AdrProvenance.record("001", line);
    const cOp = ASSIGNMENT_OPERATOR_MAP[cnextOp];
    invariant(
      cOp !== undefined,
      `every assignmentOperator alternative in the grammar has an ASSIGNMENT_OPERATOR_MAP entry (missing '${cnextOp}')`,
    );
    return cOp;
  }
}
 
export default AssignmentOperatorMapper;