All files / utils ChainRoot.ts

100% Statements 16/16
88.88% Branches 16/18
100% Functions 3/3
100% Lines 12/12

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                                            12673x 12584x 12270x         3007x 2852x 2599x                                                                     7356x 7356x 7093x     263x   263x     7356x          
/**
 * Which of ADR-016's three spellings a member chain is written in.
 *
 * #1322 review. Four copies of this two-line decision existed --
 * `RegisterMemberReference` held two, `BitAccessAnalyzer` held one as a method
 * and one as an inline nested ternary -- and they are one decision, not four.
 *
 * The two overloads are not a convenience: a POSTFIX EXPRESSION carries its
 * root on the primary and the chain's first name in its first op, while an
 * ASSIGNMENT TARGET carries both the root and the first name on the target
 * itself. Reading one shape with the other's offset is what left a register
 * READ silent while the write beside it fired (see `BitmapAccessAnalyzer`), so
 * the two node shapes stay two named questions rather than one guess.
 */
 
import { TerminalNode } from "antlr4ng";
import * as Parser from "../PARSE/2-Parse/grammar/CNextParser";
import TChainRoot from "../types/TChainRoot";
 
class ChainRoot {
  /** The root a postfix expression starts from. */
  static ofPrimary(primary: Parser.PrimaryExpressionContext): TChainRoot {
    if (primary.THIS()) return "this";
    if (primary.GLOBAL()) return "global";
    return null;
  }
 
  /** The same for an assignment target, whose keywords sit on the target. */
  static ofTarget(target: Parser.AssignmentTargetContext): TChainRoot {
    if (target.THIS()) return "this";
    if (target.GLOBAL()) return "global";
    return null;
  }
 
  /**
   * The root AND where the chain's first name sits -- one answer, because they
   * are one question.
   *
   * `ofPrimary` gives half of it. The other half is the OFFSET: a root keyword
   * consumes the primary, so a rooted chain carries its first name in the first
   * op, and everything positional after it -- call parentheses, member slicing
   * -- shifts by `opsConsumed`. Reading one shape with the other's offset is
   * the defect this class's header already names, and answering only "which
   * root" left every caller to re-derive the rest: five sites did, in four
   * different spellings, two of them keeping the `DOT()` guard below and two
   * dropping it.
   *
   * The shape is declared here rather than in `types/`: it carries a parse-tree
   * node, and nothing under `1-Analyze/types/` holds one -- `parse-tree:check`
   * treats a pass reaching for the tree as a lifetime violation, and a shared
   * contract is exactly where that coupling would spread from. This class is
   * already a grammar site, so the node stops here.
   *
   * `identifier` is null when the head is not a name at all -- a literal, a
   * parenthesised expression, or a rooted chain whose first op subscripts
   * rather than names (`this[0]`), which no spelling that omits the guard can
   * tell apart from a member access.
   */
  static headOf(
    primary: Parser.PrimaryExpressionContext,
    ops: readonly Parser.PostfixOpContext[],
  ): {
    root: TChainRoot;
    identifier: TerminalNode | null;
    opsConsumed: number;
  } {
    const root = ChainRoot.ofPrimary(primary);
    if (root === null) {
      return { root, identifier: primary.IDENTIFIER() ?? null, opsConsumed: 0 };
    }
 
    const first = ops[0];
    const identifier: TerminalNode | null =
      first !== undefined && first.DOT() !== null
        ? (first.IDENTIFIER() ?? null)
        : null;
    return { root, identifier, opsConsumed: 1 };
  }
}
 
export default ChainRoot;