Skip to content
Permalink
1cdde3eb41
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
0 contributors

Users who have contributed to this file

146 lines (135 sloc) 4.41 KB
import { compareRangeCovs } from "./compare";
import { RangeCov } from "./types";
interface ReadonlyRangeTree {
readonly start: number;
readonly end: number;
readonly count: number;
readonly children: ReadonlyRangeTree[];
}
export function emitForest(trees: ReadonlyArray<ReadonlyRangeTree>): string {
return emitForestLines(trees).join("\n");
}
export function emitForestLines(trees: ReadonlyArray<ReadonlyRangeTree>): string[] {
const colMap: Map<number, number> = getColMap(trees);
const header: string = emitOffsets(colMap);
return [header, ...trees.map(tree => emitTree(tree, colMap).join("\n"))];
}
function getColMap(trees: Iterable<ReadonlyRangeTree>): Map<number, number> {
const eventSet: Set<number> = new Set();
for (const tree of trees) {
const stack: ReadonlyRangeTree[] = [tree];
while (stack.length > 0) {
const cur: ReadonlyRangeTree = stack.pop()!;
eventSet.add(cur.start);
eventSet.add(cur.end);
for (const child of cur.children) {
stack.push(child);
}
}
}
const events: number[] = [...eventSet];
events.sort((a, b) => a - b);
let maxDigits: number = 1;
for (const event of events) {
maxDigits = Math.max(maxDigits, event.toString(10).length);
}
const colWidth: number = maxDigits + 3;
const colMap: Map<number, number> = new Map();
for (const [i, event] of events.entries()) {
colMap.set(event, i * colWidth);
}
return colMap;
}
function emitTree(tree: ReadonlyRangeTree, colMap: Map<number, number>): string[] {
const layers: ReadonlyRangeTree[][] = [];
let nextLayer: ReadonlyRangeTree[] = [tree];
while (nextLayer.length > 0) {
const layer: ReadonlyRangeTree[] = nextLayer;
layers.push(layer);
nextLayer = [];
for (const node of layer) {
for (const child of node.children) {
nextLayer.push(child);
}
}
}
return layers.map(layer => emitTreeLayer(layer, colMap));
}
export function parseFunctionRanges(text: string, offsetMap: Map<number, number>): RangeCov[] {
const result: RangeCov[] = [];
for (const line of text.split("\n")) {
for (const range of parseTreeLayer(line, offsetMap)) {
result.push(range);
}
}
result.sort(compareRangeCovs);
return result;
}
/**
*
* @param layer Sorted list of disjoint trees.
* @param colMap
*/
function emitTreeLayer(layer: ReadonlyRangeTree[], colMap: Map<number, number>): string {
const line: string[] = [];
let curIdx: number = 0;
for (const {start, end, count} of layer) {
const startIdx: number = colMap.get(start)!;
const endIdx: number = colMap.get(end)!;
if (startIdx > curIdx) {
line.push(" ".repeat(startIdx - curIdx));
}
line.push(emitRange(count, endIdx - startIdx));
curIdx = endIdx;
}
return line.join("");
}
function parseTreeLayer(text: string, offsetMap: Map<number, number>): RangeCov[] {
const result: RangeCov[] = [];
const regex: RegExp = /\[(\d+)-*\)/gs;
while (true) {
const match: RegExpMatchArray | null = regex.exec(text);
if (match === null) {
break;
}
const startIdx: number = match.index!;
const endIdx: number = startIdx + match[0].length;
const count: number = parseInt(match[1], 10);
const startOffset: number | undefined = offsetMap.get(startIdx);
const endOffset: number | undefined = offsetMap.get(endIdx);
if (startOffset === undefined || endOffset === undefined) {
throw new Error(`Invalid offsets for: ${JSON.stringify(text)}`);
}
result.push({startOffset, endOffset, count});
}
return result;
}
function emitRange(count: number, len: number): string {
const rangeStart: string = `[${count.toString(10)}`;
const rangeEnd: string = ")";
const hyphensLen: number = len - (rangeStart.length + rangeEnd.length);
const hyphens: string = "-".repeat(Math.max(0, hyphensLen));
return `${rangeStart}${hyphens}${rangeEnd}`;
}
function emitOffsets(colMap: Map<number, number>): string {
let line: string = "";
for (const [event, col] of colMap) {
if (line.length < col) {
line += " ".repeat(col - line.length);
}
line += event.toString(10);
}
return line;
}
export function parseOffsets(text: string): Map<number, number> {
const result: Map<number, number> = new Map();
const regex: RegExp = /\d+/gs;
while (true) {
const match: RegExpExecArray | null = regex.exec(text);
if (match === null) {
break;
}
result.set(match.index, parseInt(match[0], 10));
}
return result;
}