| | 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); |
| | } |
| |
|