| import { StringReader, StringWriter } from './strings'; |
| import { comma, decodeInteger, encodeInteger, hasMoreVlq, semicolon } from './vlq'; |
|
|
| const EMPTY: any[] = []; |
|
|
| type Line = number; |
| type Column = number; |
| type Kind = number; |
| type Name = number; |
| type Var = number; |
| type SourcesIndex = number; |
| type ScopesIndex = number; |
|
|
| type Mix<A, B, O> = (A & O) | (B & O); |
|
|
| export type OriginalScope = Mix< |
| [Line, Column, Line, Column, Kind], |
| [Line, Column, Line, Column, Kind, Name], |
| { vars: Var[] } |
| >; |
|
|
| export type GeneratedRange = Mix< |
| [Line, Column, Line, Column], |
| [Line, Column, Line, Column, SourcesIndex, ScopesIndex], |
| { |
| callsite: CallSite | null; |
| bindings: Binding[]; |
| isScope: boolean; |
| } |
| >; |
| export type CallSite = [SourcesIndex, Line, Column]; |
| type Binding = BindingExpressionRange[]; |
| export type BindingExpressionRange = [Name] | [Name, Line, Column]; |
|
|
| export function decodeOriginalScopes(input: string): OriginalScope[] { |
| const { length } = input; |
| const reader = new StringReader(input); |
| const scopes: OriginalScope[] = []; |
| const stack: OriginalScope[] = []; |
| let line = 0; |
|
|
| for (; reader.pos < length; reader.pos++) { |
| line = decodeInteger(reader, line); |
| const column = decodeInteger(reader, 0); |
|
|
| if (!hasMoreVlq(reader, length)) { |
| const last = stack.pop()!; |
| last[2] = line; |
| last[3] = column; |
| continue; |
| } |
|
|
| const kind = decodeInteger(reader, 0); |
| const fields = decodeInteger(reader, 0); |
| const hasName = fields & 0b0001; |
|
|
| const scope: OriginalScope = ( |
| hasName ? [line, column, 0, 0, kind, decodeInteger(reader, 0)] : [line, column, 0, 0, kind] |
| ) as OriginalScope; |
|
|
| let vars: Var[] = EMPTY; |
| if (hasMoreVlq(reader, length)) { |
| vars = []; |
| do { |
| const varsIndex = decodeInteger(reader, 0); |
| vars.push(varsIndex); |
| } while (hasMoreVlq(reader, length)); |
| } |
| scope.vars = vars; |
|
|
| scopes.push(scope); |
| stack.push(scope); |
| } |
|
|
| return scopes; |
| } |
|
|
| export function encodeOriginalScopes(scopes: OriginalScope[]): string { |
| const writer = new StringWriter(); |
|
|
| for (let i = 0; i < scopes.length; ) { |
| i = _encodeOriginalScopes(scopes, i, writer, [0]); |
| } |
|
|
| return writer.flush(); |
| } |
|
|
| function _encodeOriginalScopes( |
| scopes: OriginalScope[], |
| index: number, |
| writer: StringWriter, |
| state: [ |
| number, |
| ], |
| ): number { |
| const scope = scopes[index]; |
| const { 0: startLine, 1: startColumn, 2: endLine, 3: endColumn, 4: kind, vars } = scope; |
|
|
| if (index > 0) writer.write(comma); |
|
|
| state[0] = encodeInteger(writer, startLine, state[0]); |
| encodeInteger(writer, startColumn, 0); |
| encodeInteger(writer, kind, 0); |
|
|
| const fields = scope.length === 6 ? 0b0001 : 0; |
| encodeInteger(writer, fields, 0); |
| if (scope.length === 6) encodeInteger(writer, scope[5], 0); |
|
|
| for (const v of vars) { |
| encodeInteger(writer, v, 0); |
| } |
|
|
| for (index++; index < scopes.length; ) { |
| const next = scopes[index]; |
| const { 0: l, 1: c } = next; |
| if (l > endLine || (l === endLine && c >= endColumn)) { |
| break; |
| } |
| index = _encodeOriginalScopes(scopes, index, writer, state); |
| } |
|
|
| writer.write(comma); |
| state[0] = encodeInteger(writer, endLine, state[0]); |
| encodeInteger(writer, endColumn, 0); |
|
|
| return index; |
| } |
|
|
| export function decodeGeneratedRanges(input: string): GeneratedRange[] { |
| const { length } = input; |
| const reader = new StringReader(input); |
| const ranges: GeneratedRange[] = []; |
| const stack: GeneratedRange[] = []; |
|
|
| let genLine = 0; |
| let definitionSourcesIndex = 0; |
| let definitionScopeIndex = 0; |
| let callsiteSourcesIndex = 0; |
| let callsiteLine = 0; |
| let callsiteColumn = 0; |
| let bindingLine = 0; |
| let bindingColumn = 0; |
|
|
| do { |
| const semi = reader.indexOf(';'); |
| let genColumn = 0; |
|
|
| for (; reader.pos < semi; reader.pos++) { |
| genColumn = decodeInteger(reader, genColumn); |
|
|
| if (!hasMoreVlq(reader, semi)) { |
| const last = stack.pop()!; |
| last[2] = genLine; |
| last[3] = genColumn; |
| continue; |
| } |
|
|
| const fields = decodeInteger(reader, 0); |
| const hasDefinition = fields & 0b0001; |
| const hasCallsite = fields & 0b0010; |
| const hasScope = fields & 0b0100; |
|
|
| let callsite: CallSite | null = null; |
| let bindings: Binding[] = EMPTY; |
| let range: GeneratedRange; |
| if (hasDefinition) { |
| const defSourcesIndex = decodeInteger(reader, definitionSourcesIndex); |
| definitionScopeIndex = decodeInteger( |
| reader, |
| definitionSourcesIndex === defSourcesIndex ? definitionScopeIndex : 0, |
| ); |
|
|
| definitionSourcesIndex = defSourcesIndex; |
| range = [genLine, genColumn, 0, 0, defSourcesIndex, definitionScopeIndex] as GeneratedRange; |
| } else { |
| range = [genLine, genColumn, 0, 0] as GeneratedRange; |
| } |
|
|
| range.isScope = !!hasScope; |
|
|
| if (hasCallsite) { |
| const prevCsi = callsiteSourcesIndex; |
| const prevLine = callsiteLine; |
| callsiteSourcesIndex = decodeInteger(reader, callsiteSourcesIndex); |
| const sameSource = prevCsi === callsiteSourcesIndex; |
| callsiteLine = decodeInteger(reader, sameSource ? callsiteLine : 0); |
| callsiteColumn = decodeInteger( |
| reader, |
| sameSource && prevLine === callsiteLine ? callsiteColumn : 0, |
| ); |
|
|
| callsite = [callsiteSourcesIndex, callsiteLine, callsiteColumn]; |
| } |
| range.callsite = callsite; |
|
|
| if (hasMoreVlq(reader, semi)) { |
| bindings = []; |
| do { |
| bindingLine = genLine; |
| bindingColumn = genColumn; |
| const expressionsCount = decodeInteger(reader, 0); |
| let expressionRanges: BindingExpressionRange[]; |
| if (expressionsCount < -1) { |
| expressionRanges = [[decodeInteger(reader, 0)]]; |
| for (let i = -1; i > expressionsCount; i--) { |
| const prevBl = bindingLine; |
| bindingLine = decodeInteger(reader, bindingLine); |
| bindingColumn = decodeInteger(reader, bindingLine === prevBl ? bindingColumn : 0); |
| const expression = decodeInteger(reader, 0); |
| expressionRanges.push([expression, bindingLine, bindingColumn]); |
| } |
| } else { |
| expressionRanges = [[expressionsCount]]; |
| } |
| bindings.push(expressionRanges); |
| } while (hasMoreVlq(reader, semi)); |
| } |
| range.bindings = bindings; |
|
|
| ranges.push(range); |
| stack.push(range); |
| } |
|
|
| genLine++; |
| reader.pos = semi + 1; |
| } while (reader.pos < length); |
|
|
| return ranges; |
| } |
|
|
| export function encodeGeneratedRanges(ranges: GeneratedRange[]): string { |
| if (ranges.length === 0) return ''; |
|
|
| const writer = new StringWriter(); |
|
|
| for (let i = 0; i < ranges.length; ) { |
| i = _encodeGeneratedRanges(ranges, i, writer, [0, 0, 0, 0, 0, 0, 0]); |
| } |
|
|
| return writer.flush(); |
| } |
|
|
| function _encodeGeneratedRanges( |
| ranges: GeneratedRange[], |
| index: number, |
| writer: StringWriter, |
| state: [ |
| number, |
| number, |
| number, |
| number, |
| number, |
| number, |
| number, |
| ], |
| ): number { |
| const range = ranges[index]; |
| const { |
| 0: startLine, |
| 1: startColumn, |
| 2: endLine, |
| 3: endColumn, |
| isScope, |
| callsite, |
| bindings, |
| } = range; |
|
|
| if (state[0] < startLine) { |
| catchupLine(writer, state[0], startLine); |
| state[0] = startLine; |
| state[1] = 0; |
| } else if (index > 0) { |
| writer.write(comma); |
| } |
|
|
| state[1] = encodeInteger(writer, range[1], state[1]); |
|
|
| const fields = |
| (range.length === 6 ? 0b0001 : 0) | (callsite ? 0b0010 : 0) | (isScope ? 0b0100 : 0); |
| encodeInteger(writer, fields, 0); |
|
|
| if (range.length === 6) { |
| const { 4: sourcesIndex, 5: scopesIndex } = range; |
| if (sourcesIndex !== state[2]) { |
| state[3] = 0; |
| } |
| state[2] = encodeInteger(writer, sourcesIndex, state[2]); |
| state[3] = encodeInteger(writer, scopesIndex, state[3]); |
| } |
|
|
| if (callsite) { |
| const { 0: sourcesIndex, 1: callLine, 2: callColumn } = range.callsite!; |
| if (sourcesIndex !== state[4]) { |
| state[5] = 0; |
| state[6] = 0; |
| } else if (callLine !== state[5]) { |
| state[6] = 0; |
| } |
| state[4] = encodeInteger(writer, sourcesIndex, state[4]); |
| state[5] = encodeInteger(writer, callLine, state[5]); |
| state[6] = encodeInteger(writer, callColumn, state[6]); |
| } |
|
|
| if (bindings) { |
| for (const binding of bindings) { |
| if (binding.length > 1) encodeInteger(writer, -binding.length, 0); |
| const expression = binding[0][0]; |
| encodeInteger(writer, expression, 0); |
| let bindingStartLine = startLine; |
| let bindingStartColumn = startColumn; |
| for (let i = 1; i < binding.length; i++) { |
| const expRange = binding[i]; |
| bindingStartLine = encodeInteger(writer, expRange[1]!, bindingStartLine); |
| bindingStartColumn = encodeInteger(writer, expRange[2]!, bindingStartColumn); |
| encodeInteger(writer, expRange[0]!, 0); |
| } |
| } |
| } |
|
|
| for (index++; index < ranges.length; ) { |
| const next = ranges[index]; |
| const { 0: l, 1: c } = next; |
| if (l > endLine || (l === endLine && c >= endColumn)) { |
| break; |
| } |
| index = _encodeGeneratedRanges(ranges, index, writer, state); |
| } |
|
|
| if (state[0] < endLine) { |
| catchupLine(writer, state[0], endLine); |
| state[0] = endLine; |
| state[1] = 0; |
| } else { |
| writer.write(comma); |
| } |
| state[1] = encodeInteger(writer, endColumn, state[1]); |
|
|
| return index; |
| } |
|
|
| function catchupLine(writer: StringWriter, lastLine: number, line: number) { |
| do { |
| writer.write(semicolon); |
| } while (++lastLine < line); |
| } |
|
|