All files / utils ArrayDimensionText.ts

100% Statements 14/14
100% Branches 6/6
100% Functions 1/1
100% Lines 14/14

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                                                                                                                                    116x 116x   116x 133x 133x 74x   59x 59x   3x     56x 56x 56x 133x     116x          
/**
 * ArrayDimensionText - extracts array dimensions from a type or declarator
 * string.
 *
 * Issue #1127: `SymbolUtils.parseArrayDimensions` and
 * `TypeResolver.parseArrayType` each did this, and disagreed:
 *
 *   "[0x10]"   SymbolUtils "0x10"   TypeResolver 0     parseInt("0x10", 10)
 *   "[8+1]"    SymbolUtils "8+1"    TypeResolver 8     stops at '+'
 *   "[8ul]"    SymbolUtils "8ul"    TypeResolver 8     stops at 'u'
 *
 * TypeResolver's readings are not merely different, they are wrong: a 16-byte
 * array read as dimension 0, and `8+1` read as 8. Both now use this, which
 * folds through `LiteralUtils.parseIntegerLiteral` so every integer notation
 * resolves, and otherwise keeps the source text rather than truncating it.
 *
 * This is the string-level counterpart to `ArrayDimensionParser`, which works
 * from the parse tree. Two entry points exist because C and C++ declarators
 * reach the symbol layer as text, with no C-Next parse tree behind them.
 *
 * ## An unsized `[]` now yields an entry
 *
 * Both replaced implementations matched `[^\]]+` -- one or more -- so `[]`
 * produced *no* entry and an unsized array was indistinguishable from a
 * non-array. This yields `""`, so `extern unsigned char buf[]` and a flexible
 * array member now reach the C and C++ symbol layers as arrays of one unsized
 * dimension rather than as scalars.
 *
 * ## `""` is not `UNRESOLVED_DIMENSION`
 *
 * They describe different facts and must not be merged:
 *
 * - `""` -- the source declares NO size (`u8[]`). A fact about the
 *   declaration, and the dimension renders back as `[]`, which is valid C for
 *   an extern or a flexible array member.
 * - `UNRESOLVED_DIMENSION` -- the source declares a size that could not be
 *   folded here. A fact about resolution, in a numeric list, where it means
 *   "cannot bounds-check this subscript".
 *
 * Collapsing them would make an unsized array look like a resolution failure,
 * or a resolution failure look like a deliberate `[]` in the emitted C.
 */
 
import LiteralUtils from "./LiteralUtils.js";
 
class ArrayDimensionText {
  /**
   * Extract every bracketed dimension from a type or declarator string.
   *
   * @param text e.g. "u8[10]", "buf[2][N]", "arr[BUF_SIZE]"
   * @returns One entry per dimension: a number when the text is an integer
   *          literal in any notation, otherwise the trimmed source text (a
   *          macro name, an enum count, an expression). An unsized `[]` yields
   *          the empty string, so the dimension keeps its position.
   *
   * @example parse("u8[10]")      => [10]
   * @example parse("u8[0x10]")    => [16]
   * @example parse("u8[2][N]")    => [2, "N"]
   * @example parse("u8[8+1]")     => ["8+1"]
   */
  static parse(text: string): (number | string)[] {
    // Scanned rather than matched with /\[([^\]]*)\]/g. That pattern is
    // super-linear (SonarCloud S8786): on a '[' with no closing bracket the
    // greedy class runs to the end of the string and then backtracks a
    // character at a time, once per '[' in the input. indexOf is linear, needs
    // no lastIndex bookkeeping, and says plainly what it does.
    const dimensions: (number | string)[] = [];
    let cursor = 0;
 
    while (cursor < text.length) {
      const open = text.indexOf("[", cursor);
      if (open === -1) {
        break;
      }
      const close = text.indexOf("]", open + 1);
      if (close === -1) {
        // Unterminated bracket: nothing after it is a well-formed dimension.
        break;
      }
 
      const content = text.slice(open + 1, close).trim();
      const literal = LiteralUtils.parseIntegerLiteral(content);
      dimensions.push(literal ?? content);
      cursor = close + 1;
    }
 
    return dimensions;
  }
}
 
export default ArrayDimensionText;