Spaces:
Sleeping
Sleeping
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; | |
} | |