|
| 1 | +import { GeneratedLine, Line } from './parser'; |
| 2 | + |
| 3 | +export type CharMap4 = [ |
| 4 | + generated_charIndex: number, |
| 5 | + original_fileIndex: number, |
| 6 | + original_lineIndex: number, |
| 7 | + original_charIndex: number |
| 8 | +]; |
| 9 | + |
| 10 | +export type LineMap = CharMap4[]; |
| 11 | +export type Mappings = LineMap[]; |
| 12 | +export type Segment = { start: number; text: string }; |
| 13 | +export type Range = { start: Position; end: Position }; |
| 14 | +export type MappedPosition = { generated: GeneratedPosition; original: Position }; |
| 15 | +export type MappedRange = { start: MappedPosition; end: MappedPosition }; |
| 16 | +export const MappedKeys: (keyof MappedPosition)[] = ['generated', 'original']; |
| 17 | + |
| 18 | +export interface Position { |
| 19 | + line: Line; |
| 20 | + character: number; |
| 21 | +} |
| 22 | +export interface GeneratedPosition extends Position { |
| 23 | + line: GeneratedLine; |
| 24 | +} |
| 25 | + |
| 26 | +export function print_string(str: string) { |
| 27 | + return ('' + str) |
| 28 | + .replace(/ /g, '•') |
| 29 | + .replace(/[\r\n]/g, '↲') |
| 30 | + .replace(/\t/g, '╚'); |
| 31 | +} |
| 32 | + |
| 33 | +function edit_string(str: string, { start, text }: Segment, insert: boolean) { |
| 34 | + if (str.length === start) return str + text; |
| 35 | + else if (str.length < start) return str.padEnd(start) + text; |
| 36 | + else return str.slice(0, start) + text + str.slice(start + (insert ? 0 : text.length)); |
| 37 | +} |
| 38 | + |
| 39 | +/** |
| 40 | + * Reduces segments into one big string |
| 41 | + */ |
| 42 | +export function reduce_segments<T>( |
| 43 | + gen: Iterable<T>, |
| 44 | + fn: (value: T, index: number) => Segment | void, |
| 45 | + initial = '' |
| 46 | +) { |
| 47 | + let str = initial; |
| 48 | + let i = 0; |
| 49 | + for (const value of gen) { |
| 50 | + const segment = fn(value, i++); |
| 51 | + if (segment) str = edit_string(str, segment, false); |
| 52 | + } |
| 53 | + return str; |
| 54 | +} |
| 55 | + |
| 56 | +/** |
| 57 | + * Inserts each segment in the given text |
| 58 | + */ |
| 59 | +export function insert_segments(text: string, segments: Iterable<Segment>) { |
| 60 | + let str = ''; |
| 61 | + let prev_start: number = undefined; |
| 62 | + // sort descending |
| 63 | + for (const segment of [...segments].sort((a, b) => b.start - a.start)) { |
| 64 | + str = segment.text + text.slice(segment.start, prev_start) + str; |
| 65 | + prev_start = segment.start; |
| 66 | + } |
| 67 | + return text.slice(0, prev_start) + str; |
| 68 | +} |
| 69 | + |
| 70 | +export function fromLineCharToOffset({ line, character }: { line: Line; character: number }) { |
| 71 | + return line.start + character; |
| 72 | +} |
| 73 | + |
| 74 | +/** |
| 75 | + * Returns a text span starting with head, ending with tail, and repeating body in the middle. |
| 76 | + */ |
| 77 | +export function span(length: number, [head, body, tail]: string = '#==') { |
| 78 | + if (length <= 1) return length < 1 ? '' : body === tail ? head : tail; |
| 79 | + return head + body.repeat(length - 2) + tail; |
| 80 | +} |
| 81 | + |
| 82 | +class Underline { |
| 83 | + constructor(readonly start: number, readonly text: string) {} |
| 84 | + |
| 85 | + toString() { |
| 86 | + return ' '.repeat(this.start) + this.text; |
| 87 | + } |
| 88 | +} |
| 89 | + |
| 90 | +export function underline(start: number, end: number, style?: string) { |
| 91 | + return new Underline(start, span(end - start + 1, style)); |
| 92 | +} |
| 93 | + |
| 94 | +export function* each_subset(lineMap: LineMap) { |
| 95 | + let char = lineMap[0]; |
| 96 | + for (let i = 1; i < lineMap.length; i++) { |
| 97 | + if (char[2] !== lineMap[i][2]) { |
| 98 | + yield { line: char[2], start: char[0], end: (char = lineMap[i])[0] }; |
| 99 | + } |
| 100 | + } |
| 101 | + yield { line: char[2], start: char[0], end: undefined }; |
| 102 | +} |
| 103 | + |
| 104 | +export function* each_exec(str: string, re: RegExp) { |
| 105 | + let arr: RegExpExecArray; |
| 106 | + while ((arr = re.exec(str))) yield { match: arr[0], index: arr.index }; |
| 107 | +} |
| 108 | + |
| 109 | +/** |
| 110 | + * Returns offset of character at index after accounting for extra space taken by tabs |
| 111 | + */ |
| 112 | +export function tab_aware_index(str: string, index: number) { |
| 113 | + return index + get_extra_indent(str.slice(0, index + 1)); |
| 114 | +} |
| 115 | + |
| 116 | +/** |
| 117 | + * Returns all extra space taken by tabs in given string |
| 118 | + */ |
| 119 | +export function get_extra_indent(str: string) { |
| 120 | + return (str.match(/\t/g)?.length ?? 0) * 3; |
| 121 | +} |
| 122 | + |
| 123 | +export function range_for<K extends keyof MappedPosition>(key: K, range: MappedRange) { |
| 124 | + return { start: range.start[key], end: range.end[key] }; |
| 125 | +} |
| 126 | + |
| 127 | +export function hash(str: string): string { |
| 128 | + str = str.replace(/\r/g, ''); |
| 129 | + let hash = 5381; |
| 130 | + let i = str.length; |
| 131 | + while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i); |
| 132 | + return (hash >>> 0).toString(36); |
| 133 | +} |
0 commit comments