sourav-das/stem-separator
3
1import { StringReader, StringWriter } from './strings';2import { comma, decodeInteger, encodeInteger, hasMoreVlq, semicolon } from './vlq';3 4const EMPTY: any[] = [];5 6type Line = number;7type Column = number;8type Kind = number;9type Name = number;10type Var = number;11type SourcesIndex = number;12type ScopesIndex = number;13 14type Mix<A, B, O> = (A & O) | (B & O);15 16export type OriginalScope = Mix<17 [Line, Column, Line, Column, Kind],18 [Line, Column, Line, Column, Kind, Name],19 { vars: Var[] }20>;21 22export type GeneratedRange = Mix<23 [Line, Column, Line, Column],24 [Line, Column, Line, Column, SourcesIndex, ScopesIndex],25 {26 callsite: CallSite | null;27 bindings: Binding[];28 isScope: boolean;29 }30>;31export type CallSite = [SourcesIndex, Line, Column];32type Binding = BindingExpressionRange[];33export type BindingExpressionRange = [Name] | [Name, Line, Column];34 35export function decodeOriginalScopes(input: string): OriginalScope[] {36 const { length } = input;37 const reader = new StringReader(input);38 const scopes: OriginalScope[] = [];39 const stack: OriginalScope[] = [];40 let line = 0;41 42 for (; reader.pos < length; reader.pos++) {43 line = decodeInteger(reader, line);44 const column = decodeInteger(reader, 0);45 46 if (!hasMoreVlq(reader, length)) {47 const last = stack.pop()!;48 last[2] = line;49 last[3] = column;50 continue;51 }52 53 const kind = decodeInteger(reader, 0);54 const fields = decodeInteger(reader, 0);55 const hasName = fields & 0b0001;56 57 const scope: OriginalScope = (58 hasName ? [line, column, 0, 0, kind, decodeInteger(reader, 0)] : [line, column, 0, 0, kind]59 ) as OriginalScope;60 61 let vars: Var[] = EMPTY;62 if (hasMoreVlq(reader, length)) {63 vars = [];64 do {65 const varsIndex = decodeInteger(reader, 0);66 vars.push(varsIndex);67 } while (hasMoreVlq(reader, length));68 }69 scope.vars = vars;70 71 scopes.push(scope);72 stack.push(scope);73 }74 75 return scopes;76}77 78export function encodeOriginalScopes(scopes: OriginalScope[]): string {79 const writer = new StringWriter();80 81 for (let i = 0; i < scopes.length; ) {82 i = _encodeOriginalScopes(scopes, i, writer, [0]);83 }84 85 return writer.flush();86}87 88function _encodeOriginalScopes(89 scopes: OriginalScope[],90 index: number,91 writer: StringWriter,92 state: [93 number, // GenColumn94 ],95): number {96 const scope = scopes[index];97 const { 0: startLine, 1: startColumn, 2: endLine, 3: endColumn, 4: kind, vars } = scope;98 99 if (index > 0) writer.write(comma);100 101 state[0] = encodeInteger(writer, startLine, state[0]);102 encodeInteger(writer, startColumn, 0);103 encodeInteger(writer, kind, 0);104 105 const fields = scope.length === 6 ? 0b0001 : 0;106 encodeInteger(writer, fields, 0);107 if (scope.length === 6) encodeInteger(writer, scope[5], 0);108 109 for (const v of vars) {110 encodeInteger(writer, v, 0);111 }112 113 for (index++; index < scopes.length; ) {114 const next = scopes[index];115 const { 0: l, 1: c } = next;116 if (l > endLine || (l === endLine && c >= endColumn)) {117 break;118 }119 index = _encodeOriginalScopes(scopes, index, writer, state);120 }121 122 writer.write(comma);123 state[0] = encodeInteger(writer, endLine, state[0]);124 encodeInteger(writer, endColumn, 0);125 126 return index;127}128 129export function decodeGeneratedRanges(input: string): GeneratedRange[] {130 const { length } = input;131 const reader = new StringReader(input);132 const ranges: GeneratedRange[] = [];133 const stack: GeneratedRange[] = [];134 135 let genLine = 0;136 let definitionSourcesIndex = 0;137 let definitionScopeIndex = 0;138 let callsiteSourcesIndex = 0;139 let callsiteLine = 0;140 let callsiteColumn = 0;141 let bindingLine = 0;142 let bindingColumn = 0;143 144 do {145 const semi = reader.indexOf(';');146 let genColumn = 0;147 148 for (; reader.pos < semi; reader.pos++) {149 genColumn = decodeInteger(reader, genColumn);150 151 if (!hasMoreVlq(reader, semi)) {152 const last = stack.pop()!;153 last[2] = genLine;154 last[3] = genColumn;155 continue;156 }157 158 const fields = decodeInteger(reader, 0);159 const hasDefinition = fields & 0b0001;160 const hasCallsite = fields & 0b0010;161 const hasScope = fields & 0b0100;162 163 let callsite: CallSite | null = null;164 let bindings: Binding[] = EMPTY;165 let range: GeneratedRange;166 if (hasDefinition) {167 const defSourcesIndex = decodeInteger(reader, definitionSourcesIndex);168 definitionScopeIndex = decodeInteger(169 reader,170 definitionSourcesIndex === defSourcesIndex ? definitionScopeIndex : 0,171 );172 173 definitionSourcesIndex = defSourcesIndex;174 range = [genLine, genColumn, 0, 0, defSourcesIndex, definitionScopeIndex] as GeneratedRange;175 } else {176 range = [genLine, genColumn, 0, 0] as GeneratedRange;177 }178 179 range.isScope = !!hasScope;180 181 if (hasCallsite) {182 const prevCsi = callsiteSourcesIndex;183 const prevLine = callsiteLine;184 callsiteSourcesIndex = decodeInteger(reader, callsiteSourcesIndex);185 const sameSource = prevCsi === callsiteSourcesIndex;186 callsiteLine = decodeInteger(reader, sameSource ? callsiteLine : 0);187 callsiteColumn = decodeInteger(188 reader,189 sameSource && prevLine === callsiteLine ? callsiteColumn : 0,190 );191 192 callsite = [callsiteSourcesIndex, callsiteLine, callsiteColumn];193 }194 range.callsite = callsite;195 196 if (hasMoreVlq(reader, semi)) {197 bindings = [];198 do {199 bindingLine = genLine;200 bindingColumn = genColumn;201 const expressionsCount = decodeInteger(reader, 0);202 let expressionRanges: BindingExpressionRange[];203 if (expressionsCount < -1) {204 expressionRanges = [[decodeInteger(reader, 0)]];205 for (let i = -1; i > expressionsCount; i--) {206 const prevBl = bindingLine;207 bindingLine = decodeInteger(reader, bindingLine);208 bindingColumn = decodeInteger(reader, bindingLine === prevBl ? bindingColumn : 0);209 const expression = decodeInteger(reader, 0);210 expressionRanges.push([expression, bindingLine, bindingColumn]);211 }212 } else {213 expressionRanges = [[expressionsCount]];214 }215 bindings.push(expressionRanges);216 } while (hasMoreVlq(reader, semi));217 }218 range.bindings = bindings;219 220 ranges.push(range);221 stack.push(range);222 }223 224 genLine++;225 reader.pos = semi + 1;226 } while (reader.pos < length);227 228 return ranges;229}230 231export function encodeGeneratedRanges(ranges: GeneratedRange[]): string {232 if (ranges.length === 0) return '';233 234 const writer = new StringWriter();235 236 for (let i = 0; i < ranges.length; ) {237 i = _encodeGeneratedRanges(ranges, i, writer, [0, 0, 0, 0, 0, 0, 0]);238 }239 240 return writer.flush();241}242 243function _encodeGeneratedRanges(244 ranges: GeneratedRange[],245 index: number,246 writer: StringWriter,247 state: [248 number, // GenLine249 number, // GenColumn250 number, // DefSourcesIndex251 number, // DefScopesIndex252 number, // CallSourcesIndex253 number, // CallLine254 number, // CallColumn255 ],256): number {257 const range = ranges[index];258 const {259 0: startLine,260 1: startColumn,261 2: endLine,262 3: endColumn,263 isScope,264 callsite,265 bindings,266 } = range;267 268 if (state[0] < startLine) {269 catchupLine(writer, state[0], startLine);270 state[0] = startLine;271 state[1] = 0;272 } else if (index > 0) {273 writer.write(comma);274 }275 276 state[1] = encodeInteger(writer, range[1], state[1]);277 278 const fields =279 (range.length === 6 ? 0b0001 : 0) | (callsite ? 0b0010 : 0) | (isScope ? 0b0100 : 0);280 encodeInteger(writer, fields, 0);281 282 if (range.length === 6) {283 const { 4: sourcesIndex, 5: scopesIndex } = range;284 if (sourcesIndex !== state[2]) {285 state[3] = 0;286 }287 state[2] = encodeInteger(writer, sourcesIndex, state[2]);288 state[3] = encodeInteger(writer, scopesIndex, state[3]);289 }290 291 if (callsite) {292 const { 0: sourcesIndex, 1: callLine, 2: callColumn } = range.callsite!;293 if (sourcesIndex !== state[4]) {294 state[5] = 0;295 state[6] = 0;296 } else if (callLine !== state[5]) {297 state[6] = 0;298 }299 state[4] = encodeInteger(writer, sourcesIndex, state[4]);300 state[5] = encodeInteger(writer, callLine, state[5]);301 state[6] = encodeInteger(writer, callColumn, state[6]);302 }303 304 if (bindings) {305 for (const binding of bindings) {306 if (binding.length > 1) encodeInteger(writer, -binding.length, 0);307 const expression = binding[0][0];308 encodeInteger(writer, expression, 0);309 let bindingStartLine = startLine;310 let bindingStartColumn = startColumn;311 for (let i = 1; i < binding.length; i++) {312 const expRange = binding[i];313 bindingStartLine = encodeInteger(writer, expRange[1]!, bindingStartLine);314 bindingStartColumn = encodeInteger(writer, expRange[2]!, bindingStartColumn);315 encodeInteger(writer, expRange[0]!, 0);316 }317 }318 }319 320 for (index++; index < ranges.length; ) {321 const next = ranges[index];322 const { 0: l, 1: c } = next;323 if (l > endLine || (l === endLine && c >= endColumn)) {324 break;325 }326 index = _encodeGeneratedRanges(ranges, index, writer, state);327 }328 329 if (state[0] < endLine) {330 catchupLine(writer, state[0], endLine);331 state[0] = endLine;332 state[1] = 0;333 } else {334 writer.write(comma);335 }336 state[1] = encodeInteger(writer, endColumn, state[1]);337 338 return index;339}340 341function catchupLine(writer: StringWriter, lastLine: number, line: number) {342 do {343 writer.write(semicolon);344 } while (++lastLine < line);345}346 