| 1 | import { compareRangeCovs } from "./compare";
|
|---|
| 2 | import { RangeCov } from "./types";
|
|---|
| 3 |
|
|---|
| 4 | interface ReadonlyRangeTree {
|
|---|
| 5 | readonly start: number;
|
|---|
| 6 | readonly end: number;
|
|---|
| 7 | readonly count: number;
|
|---|
| 8 | readonly children: ReadonlyRangeTree[];
|
|---|
| 9 | }
|
|---|
| 10 |
|
|---|
| 11 | export function emitForest(trees: ReadonlyArray<ReadonlyRangeTree>): string {
|
|---|
| 12 | return emitForestLines(trees).join("\n");
|
|---|
| 13 | }
|
|---|
| 14 |
|
|---|
| 15 | export function emitForestLines(trees: ReadonlyArray<ReadonlyRangeTree>): string[] {
|
|---|
| 16 | const colMap: Map<number, number> = getColMap(trees);
|
|---|
| 17 | const header: string = emitOffsets(colMap);
|
|---|
| 18 | return [header, ...trees.map(tree => emitTree(tree, colMap).join("\n"))];
|
|---|
| 19 | }
|
|---|
| 20 |
|
|---|
| 21 | function getColMap(trees: Iterable<ReadonlyRangeTree>): Map<number, number> {
|
|---|
| 22 | const eventSet: Set<number> = new Set();
|
|---|
| 23 | for (const tree of trees) {
|
|---|
| 24 | const stack: ReadonlyRangeTree[] = [tree];
|
|---|
| 25 | while (stack.length > 0) {
|
|---|
| 26 | const cur: ReadonlyRangeTree = stack.pop()!;
|
|---|
| 27 | eventSet.add(cur.start);
|
|---|
| 28 | eventSet.add(cur.end);
|
|---|
| 29 | for (const child of cur.children) {
|
|---|
| 30 | stack.push(child);
|
|---|
| 31 | }
|
|---|
| 32 | }
|
|---|
| 33 | }
|
|---|
| 34 | const events: number[] = [...eventSet];
|
|---|
| 35 | events.sort((a, b) => a - b);
|
|---|
| 36 | let maxDigits: number = 1;
|
|---|
| 37 | for (const event of events) {
|
|---|
| 38 | maxDigits = Math.max(maxDigits, event.toString(10).length);
|
|---|
| 39 | }
|
|---|
| 40 | const colWidth: number = maxDigits + 3;
|
|---|
| 41 | const colMap: Map<number, number> = new Map();
|
|---|
| 42 | for (const [i, event] of events.entries()) {
|
|---|
| 43 | colMap.set(event, i * colWidth);
|
|---|
| 44 | }
|
|---|
| 45 | return colMap;
|
|---|
| 46 | }
|
|---|
| 47 |
|
|---|
| 48 | function emitTree(tree: ReadonlyRangeTree, colMap: Map<number, number>): string[] {
|
|---|
| 49 | const layers: ReadonlyRangeTree[][] = [];
|
|---|
| 50 | let nextLayer: ReadonlyRangeTree[] = [tree];
|
|---|
| 51 | while (nextLayer.length > 0) {
|
|---|
| 52 | const layer: ReadonlyRangeTree[] = nextLayer;
|
|---|
| 53 | layers.push(layer);
|
|---|
| 54 | nextLayer = [];
|
|---|
| 55 | for (const node of layer) {
|
|---|
| 56 | for (const child of node.children) {
|
|---|
| 57 | nextLayer.push(child);
|
|---|
| 58 | }
|
|---|
| 59 | }
|
|---|
| 60 | }
|
|---|
| 61 | return layers.map(layer => emitTreeLayer(layer, colMap));
|
|---|
| 62 | }
|
|---|
| 63 |
|
|---|
| 64 | export function parseFunctionRanges(text: string, offsetMap: Map<number, number>): RangeCov[] {
|
|---|
| 65 | const result: RangeCov[] = [];
|
|---|
| 66 | for (const line of text.split("\n")) {
|
|---|
| 67 | for (const range of parseTreeLayer(line, offsetMap)) {
|
|---|
| 68 | result.push(range);
|
|---|
| 69 | }
|
|---|
| 70 | }
|
|---|
| 71 | result.sort(compareRangeCovs);
|
|---|
| 72 | return result;
|
|---|
| 73 | }
|
|---|
| 74 |
|
|---|
| 75 | /**
|
|---|
| 76 | *
|
|---|
| 77 | * @param layer Sorted list of disjoint trees.
|
|---|
| 78 | * @param colMap
|
|---|
| 79 | */
|
|---|
| 80 | function emitTreeLayer(layer: ReadonlyRangeTree[], colMap: Map<number, number>): string {
|
|---|
| 81 | const line: string[] = [];
|
|---|
| 82 | let curIdx: number = 0;
|
|---|
| 83 | for (const {start, end, count} of layer) {
|
|---|
| 84 | const startIdx: number = colMap.get(start)!;
|
|---|
| 85 | const endIdx: number = colMap.get(end)!;
|
|---|
| 86 | if (startIdx > curIdx) {
|
|---|
| 87 | line.push(" ".repeat(startIdx - curIdx));
|
|---|
| 88 | }
|
|---|
| 89 | line.push(emitRange(count, endIdx - startIdx));
|
|---|
| 90 | curIdx = endIdx;
|
|---|
| 91 | }
|
|---|
| 92 | return line.join("");
|
|---|
| 93 | }
|
|---|
| 94 |
|
|---|
| 95 | function parseTreeLayer(text: string, offsetMap: Map<number, number>): RangeCov[] {
|
|---|
| 96 | const result: RangeCov[] = [];
|
|---|
| 97 | const regex: RegExp = /\[(\d+)-*\)/gs;
|
|---|
| 98 | while (true) {
|
|---|
| 99 | const match: RegExpMatchArray | null = regex.exec(text);
|
|---|
| 100 | if (match === null) {
|
|---|
| 101 | break;
|
|---|
| 102 | }
|
|---|
| 103 | const startIdx: number = match.index!;
|
|---|
| 104 | const endIdx: number = startIdx + match[0].length;
|
|---|
| 105 | const count: number = parseInt(match[1], 10);
|
|---|
| 106 | const startOffset: number | undefined = offsetMap.get(startIdx);
|
|---|
| 107 | const endOffset: number | undefined = offsetMap.get(endIdx);
|
|---|
| 108 | if (startOffset === undefined || endOffset === undefined) {
|
|---|
| 109 | throw new Error(`Invalid offsets for: ${JSON.stringify(text)}`);
|
|---|
| 110 | }
|
|---|
| 111 | result.push({startOffset, endOffset, count});
|
|---|
| 112 | }
|
|---|
| 113 | return result;
|
|---|
| 114 | }
|
|---|
| 115 |
|
|---|
| 116 | function emitRange(count: number, len: number): string {
|
|---|
| 117 | const rangeStart: string = `[${count.toString(10)}`;
|
|---|
| 118 | const rangeEnd: string = ")";
|
|---|
| 119 | const hyphensLen: number = len - (rangeStart.length + rangeEnd.length);
|
|---|
| 120 | const hyphens: string = "-".repeat(Math.max(0, hyphensLen));
|
|---|
| 121 | return `${rangeStart}${hyphens}${rangeEnd}`;
|
|---|
| 122 | }
|
|---|
| 123 |
|
|---|
| 124 | function emitOffsets(colMap: Map<number, number>): string {
|
|---|
| 125 | let line: string = "";
|
|---|
| 126 | for (const [event, col] of colMap) {
|
|---|
| 127 | if (line.length < col) {
|
|---|
| 128 | line += " ".repeat(col - line.length);
|
|---|
| 129 | }
|
|---|
| 130 | line += event.toString(10);
|
|---|
| 131 | }
|
|---|
| 132 | return line;
|
|---|
| 133 | }
|
|---|
| 134 |
|
|---|
| 135 | export function parseOffsets(text: string): Map<number, number> {
|
|---|
| 136 | const result: Map<number, number> = new Map();
|
|---|
| 137 | const regex: RegExp = /\d+/gs;
|
|---|
| 138 | while (true) {
|
|---|
| 139 | const match: RegExpExecArray | null = regex.exec(text);
|
|---|
| 140 | if (match === null) {
|
|---|
| 141 | break;
|
|---|
| 142 | }
|
|---|
| 143 | result.set(match.index, parseInt(match[0], 10));
|
|---|
| 144 | }
|
|---|
| 145 | return result;
|
|---|
| 146 | }
|
|---|