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 & 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, // GenColumn ], ): 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, // GenLine number, // GenColumn number, // DefSourcesIndex number, // DefScopesIndex number, // CallSourcesIndex number, // CallLine number, // CallColumn ], ): 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); }