refactor: implement source mapping in javascript, instead of java
This commit is contained in:
parent
398d88c0a5
commit
8a34db833c
@ -1,15 +1,13 @@
|
|||||||
import { createDocumentRegistry, createLanguageService, ModuleKind, ScriptSnapshot, ScriptTarget, type Diagnostic, type CompilerOptions, type IScriptSnapshot, flattenDiagnosticMessageText, CompilerHost, LanguageService } from "typescript";
|
import { createDocumentRegistry, createLanguageService, ModuleKind, ScriptSnapshot, ScriptTarget, type Diagnostic, type CompilerOptions, type IScriptSnapshot, flattenDiagnosticMessageText, CompilerHost, LanguageService } from "typescript";
|
||||||
|
import { SourceMap } from "./map.ts";
|
||||||
|
|
||||||
declare function getResource(name: string): string | undefined;
|
declare function getResource(name: string): string | undefined;
|
||||||
declare function print(...args: any[]): void;
|
declare function print(...args: any[]): void;
|
||||||
declare function register(factory: CompilerFactory): void;
|
declare function register(factory: CompilerFactory): void;
|
||||||
declare function parseVLQ(compiled: string, original: string, map: string): (loc: Location) => Location;
|
|
||||||
declare function chainMaps(...mappers: ((loc: Location) => Location)[]): (loc: Location) => Location;
|
|
||||||
declare function registerSource(filename: string, src: string): void;
|
declare function registerSource(filename: string, src: string): void;
|
||||||
|
|
||||||
type Location = readonly [filename: string, line: number, col: number];
|
|
||||||
type CompilerFactory = (next: Compiler) => Compiler;
|
type CompilerFactory = (next: Compiler) => Compiler;
|
||||||
type Compiler = (filename: string, src: string, mapper: (loc: Location) => Location) => Function;
|
type Compiler = (filename: string, src: string, mapper: SourceMap) => Function;
|
||||||
|
|
||||||
const resources: Record<string, string | undefined> = {};
|
const resources: Record<string, string | undefined> = {};
|
||||||
|
|
||||||
@ -108,11 +106,15 @@ register(next => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const rawMap = JSON.parse(outputs["/src.js.map"]);
|
const rawMap = JSON.parse(outputs["/src.js.map"]);
|
||||||
const map = parseVLQ(filename, filename, rawMap.mappings);
|
const map = SourceMap.parse({
|
||||||
|
file: "ts-internal://" + filename,
|
||||||
|
mappings: rawMap.mappings,
|
||||||
|
sources: [filename],
|
||||||
|
});
|
||||||
const result = outputs["/src.js"];
|
const result = outputs["/src.js"];
|
||||||
const declaration = outputs["/src.d.ts"];
|
const declaration = outputs["/src.d.ts"];
|
||||||
|
|
||||||
const compiled = next(filename, result, chainMaps(prevMap, map));
|
const compiled = next("ts-internal://" + filename, result, SourceMap.chain(prevMap, map));
|
||||||
registerSource(filename, code);
|
registerSource(filename, code);
|
||||||
|
|
||||||
return function (this: any) {
|
return function (this: any) {
|
||||||
|
178
src/lib/ts/map.ts
Normal file
178
src/lib/ts/map.ts
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
const map: number[] = [];
|
||||||
|
let j = 0;
|
||||||
|
|
||||||
|
for (let i = 65; i <= 90; i++) map[i] = j++;
|
||||||
|
for (let i = 97; i <= 122; i++) map[i] = j++;
|
||||||
|
map[43] = j++;
|
||||||
|
map[47] = j++;
|
||||||
|
|
||||||
|
export type Location = readonly [file: string, line: number, start: number];
|
||||||
|
|
||||||
|
export function decodeVLQ(val: string): number[][][] {
|
||||||
|
const lines: number[][][] = [];
|
||||||
|
|
||||||
|
for (const line of val.split(";", -1)) {
|
||||||
|
const elements: number[][] = [];
|
||||||
|
|
||||||
|
for (const el of line.split(",", -1)) {
|
||||||
|
if (el.length === 0) elements.push([]);
|
||||||
|
else {
|
||||||
|
const list: number[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < el.length;) {
|
||||||
|
let sign = 1;
|
||||||
|
let curr = map[el.charCodeAt(i++)];
|
||||||
|
let cont = (curr & 0x20) === 0x20;
|
||||||
|
if ((curr & 1) === 1) sign = -1;
|
||||||
|
let res = (curr & 0b11110) >> 1;
|
||||||
|
let n = 4;
|
||||||
|
|
||||||
|
for (; i < el.length && cont;) {
|
||||||
|
curr = map[el.charCodeAt(i++)];
|
||||||
|
cont = (curr & 0x20) == 0x20;
|
||||||
|
res |= (curr & 0b11111) << n;
|
||||||
|
n += 5;
|
||||||
|
if (!cont) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.push(res * sign);
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.push(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Location {
|
||||||
|
export function compare(a: Location, b: Location) {
|
||||||
|
const { 0: filenameA, 1: lineA, 2: startA } = a;
|
||||||
|
const { 0: filenameB, 1: lineB, 2: startB } = b;
|
||||||
|
|
||||||
|
if (filenameA < filenameB) return -1;
|
||||||
|
if (filenameA > filenameB) return 1;
|
||||||
|
|
||||||
|
const lineI = lineA - lineB;
|
||||||
|
if (lineI !== 0) return lineI;
|
||||||
|
|
||||||
|
return startA - startB;
|
||||||
|
}
|
||||||
|
export function comparePoints(a: Location, b: Location) {
|
||||||
|
const { 1: lineA, 2: startA } = a;
|
||||||
|
const { 1: lineB, 2: startB } = b;
|
||||||
|
|
||||||
|
const lineI = lineA - lineB;
|
||||||
|
if (lineI !== 0) return lineI;
|
||||||
|
|
||||||
|
return startA - startB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SourceMap {
|
||||||
|
(loc: Location): Location | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class VLQSourceMap {
|
||||||
|
public constructor(
|
||||||
|
public readonly array: Map<string, [start: number, dst: Location][][]>,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public converter() {
|
||||||
|
return (src: Location) => {
|
||||||
|
const file = this.array.get(src[0]);
|
||||||
|
if (file == null) return src;
|
||||||
|
|
||||||
|
const line = file[src[1]];
|
||||||
|
if (line == null || line.length === 0) return undefined;
|
||||||
|
|
||||||
|
let a = 0;
|
||||||
|
let b = line.length;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const done = b - a <= 1;
|
||||||
|
|
||||||
|
const mid = (a + b) >> 1;
|
||||||
|
const el = line[mid];
|
||||||
|
|
||||||
|
const cmp = el[0] - src[1];
|
||||||
|
|
||||||
|
if (cmp < 0) {
|
||||||
|
if (done) {
|
||||||
|
if (b >= line.length) return undefined;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
a = mid;
|
||||||
|
}
|
||||||
|
else if (cmp > 0) {
|
||||||
|
if (done) {
|
||||||
|
if (a <= 0) return undefined;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
b = mid;
|
||||||
|
}
|
||||||
|
else return el[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return line[b][1];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static parseVLQ(compiled: string, filenames: string[], raw: string): VLQSourceMap {
|
||||||
|
const mapping = decodeVLQ(raw);
|
||||||
|
const res = new Map<string, [start: number, dst: Location][][]>();
|
||||||
|
|
||||||
|
let originalRow = 0;
|
||||||
|
let originalCol = 0;
|
||||||
|
let originalFile = 0;
|
||||||
|
|
||||||
|
for (let compiledRow = 0; compiledRow < mapping.length; compiledRow++) {
|
||||||
|
let compiledCol = 0;
|
||||||
|
|
||||||
|
for (const rawSeg of mapping[compiledRow]) {
|
||||||
|
compiledCol += rawSeg.length > 0 ? rawSeg[0] : 0;
|
||||||
|
originalFile += rawSeg.length > 1 ? rawSeg[1] : 0;
|
||||||
|
originalRow += rawSeg.length > 2 ? rawSeg[2] : 0;
|
||||||
|
originalCol += rawSeg.length > 3 ? rawSeg[3] : 0;
|
||||||
|
|
||||||
|
let file = res.get(compiled);
|
||||||
|
if (file == null) res.set(compiled, file = []);
|
||||||
|
|
||||||
|
const line = file[compiledRow] ??= [];
|
||||||
|
line[line.length] = [compiledCol, [filenames[originalFile], originalRow, originalCol]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new VLQSourceMap(res);
|
||||||
|
}
|
||||||
|
public static parse(raw: string | { file: string, mappings: string, sources: string[] }) {
|
||||||
|
if (typeof raw === "string") raw = JSON.parse(raw) as { file: string, mappings: string, sources: string[] };
|
||||||
|
|
||||||
|
const compiled = raw.file;
|
||||||
|
const mapping = raw.mappings;
|
||||||
|
let filenames = raw.sources;
|
||||||
|
if (filenames.length === 0 || filenames.length === 1 && filenames[0] === "") filenames = [compiled];
|
||||||
|
|
||||||
|
return this.parseVLQ(compiled, filenames, mapping);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace SourceMap {
|
||||||
|
export function parse(raw: string | { file: string, mappings: string, sources: string[] }) {
|
||||||
|
return VLQSourceMap.parse(raw).converter();
|
||||||
|
}
|
||||||
|
export function chain(...maps: SourceMap[]): SourceMap {
|
||||||
|
return loc => {
|
||||||
|
for (const el of maps) {
|
||||||
|
const tmp = el(loc);
|
||||||
|
if (tmp == null) return undefined;
|
||||||
|
else loc = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return loc;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -56,10 +56,14 @@ public class FunctionMap {
|
|||||||
var newBreakpoints = new HashMap<Location, BreakpointType>();
|
var newBreakpoints = new HashMap<Location, BreakpointType>();
|
||||||
|
|
||||||
for (var key : sourceMap.keySet()) {
|
for (var key : sourceMap.keySet()) {
|
||||||
newSourceMaps.put(key, mapper.apply(sourceMap.get(key)));
|
var mapped = mapper.apply(sourceMap.get(key));
|
||||||
|
if (mapped == null) continue;
|
||||||
|
newSourceMaps.put(key, mapped);
|
||||||
}
|
}
|
||||||
for (var key : breakpoints.keySet()) {
|
for (var key : breakpoints.keySet()) {
|
||||||
newBreakpoints.put(mapper.apply(key), breakpoints.get(key));
|
var mapped = mapper.apply(key);
|
||||||
|
if (mapped == null) continue;
|
||||||
|
newBreakpoints.put(mapped, breakpoints.get(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceMap.clear();
|
sourceMap.clear();
|
||||||
|
@ -25,6 +25,7 @@ public class NativeMapper extends FunctionValue {
|
|||||||
);
|
);
|
||||||
|
|
||||||
var res = mapper.apply(loc);
|
var res = mapper.apply(loc);
|
||||||
|
if (res == null) return Value.UNDEFINED;
|
||||||
|
|
||||||
return new ArrayValue(
|
return new ArrayValue(
|
||||||
StringValue.of(res.filename().toString()),
|
StringValue.of(res.filename().toString()),
|
||||||
@ -52,12 +53,14 @@ public class NativeMapper extends FunctionValue {
|
|||||||
NumberValue.of(loc.start())
|
NumberValue.of(loc.start())
|
||||||
);
|
);
|
||||||
|
|
||||||
var rawRes = (ArrayLikeValue)func.apply(env, Value.UNDEFINED, rawLoc);
|
var rawRes = func.apply(env, Value.UNDEFINED, rawLoc);
|
||||||
return Location.of(
|
if (rawRes instanceof ArrayLikeValue arr) return Location.of(
|
||||||
Filename.parse(rawRes.get(0).toString(env)),
|
Filename.parse(arr.get(0).toString(env)),
|
||||||
rawRes.get(1).toNumber(env).getInt(),
|
arr.get(1).toNumber(env).getInt(),
|
||||||
rawRes.get(2).toNumber(env).getInt()
|
arr.get(2).toNumber(env).getInt()
|
||||||
);
|
);
|
||||||
|
else if (rawRes == Value.UNDEFINED || rawRes == Value.NULL) return null;
|
||||||
|
else throw EngineException.ofType("Location must be an array, null or undefined");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,123 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.repl.mapping;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.json.JSON;
|
|
||||||
import me.topchetoeu.jscript.common.parsing.Filename;
|
|
||||||
import me.topchetoeu.jscript.common.parsing.Location;
|
|
||||||
|
|
||||||
public class SourceMap {
|
|
||||||
private final TreeMap<Long, Long> origToComp = new TreeMap<>();
|
|
||||||
private final TreeMap<Long, Long> compToOrig = new TreeMap<>();
|
|
||||||
private final Filename compiled, original;
|
|
||||||
|
|
||||||
public Location toCompiled(Location loc) { return convert(original, compiled, loc, origToComp); }
|
|
||||||
public Location toOriginal(Location loc) { return convert(compiled, original, loc, compToOrig); }
|
|
||||||
|
|
||||||
private void add(long orig, long comp) {
|
|
||||||
var a = origToComp.remove(orig);
|
|
||||||
var b = compToOrig.remove(comp);
|
|
||||||
|
|
||||||
if (b != null) origToComp.remove(b);
|
|
||||||
if (a != null) compToOrig.remove(a);
|
|
||||||
|
|
||||||
origToComp.put(orig, comp);
|
|
||||||
compToOrig.put(comp, orig);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SourceMap apply(SourceMap map) {
|
|
||||||
var res = new SourceMap(map.compiled, map.original);
|
|
||||||
|
|
||||||
for (var el : new ArrayList<>(origToComp.entrySet())) {
|
|
||||||
var mapped = convert(el.getValue(), map.origToComp);
|
|
||||||
res.origToComp.put(el.getKey(), mapped);
|
|
||||||
}
|
|
||||||
for (var el : new ArrayList<>(compToOrig.entrySet())) {
|
|
||||||
var mapped = convert(el.getKey(), map.compToOrig);
|
|
||||||
res.compToOrig.put(mapped, el.getValue());
|
|
||||||
res.add(el.getValue(), mapped);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SourceMap clone() {
|
|
||||||
var res = new SourceMap(this.compiled, this.original);
|
|
||||||
res.origToComp.putAll(this.origToComp);
|
|
||||||
res.compToOrig.putAll(this.compToOrig);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SourceMap(Filename compiled, Filename original) {
|
|
||||||
this.compiled = compiled;
|
|
||||||
this.original = original;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SourceMap parse(Filename compiled, Filename original, String raw) {
|
|
||||||
var mapping = VLQ.decodeMapping(raw);
|
|
||||||
var res = new SourceMap(compiled, original);
|
|
||||||
|
|
||||||
var compRow = 0l;
|
|
||||||
var compCol = 0l;
|
|
||||||
|
|
||||||
for (var origRow = 0; origRow < mapping.length; origRow++) {
|
|
||||||
var origCol = 0;
|
|
||||||
|
|
||||||
for (var rawSeg : mapping[origRow]) {
|
|
||||||
if (rawSeg.length > 1 && rawSeg[1] != 0) throw new IllegalArgumentException("Source mapping is to more than one files.");
|
|
||||||
origCol += rawSeg.length > 0 ? rawSeg[0] : 0;
|
|
||||||
compRow += rawSeg.length > 2 ? rawSeg[2] : 0;
|
|
||||||
compCol += rawSeg.length > 3 ? rawSeg[3] : 0;
|
|
||||||
|
|
||||||
var compPacked = ((long)compRow << 32) | compCol;
|
|
||||||
var origPacked = ((long)origRow << 32) | origCol;
|
|
||||||
|
|
||||||
res.add(origPacked, compPacked);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
public static List<String> getSources(String raw) {
|
|
||||||
var json = JSON.parse(null, raw).map();
|
|
||||||
return json
|
|
||||||
.list("sourcesContent")
|
|
||||||
.stream()
|
|
||||||
.map(v -> v.string())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SourceMap chain(SourceMap ...maps) {
|
|
||||||
if (maps.length == 0) return null;
|
|
||||||
var res = maps[0];
|
|
||||||
|
|
||||||
for (var i = 1; i < maps.length; i++) res = res.apply(maps[i]);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Long convert(long packed, TreeMap<Long, Long> map) {
|
|
||||||
if (map.containsKey(packed)) return map.get(packed);
|
|
||||||
var key = map.floorKey(packed);
|
|
||||||
if (key == null) return null;
|
|
||||||
else return map.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Location convert(Filename src, Filename dst, Location loc, TreeMap<Long, Long> map) {
|
|
||||||
if (!loc.filename().equals(src)) return loc;
|
|
||||||
|
|
||||||
var packed = ((loc.line()) << 32) | (loc.start());
|
|
||||||
var resPacked = convert(packed, map);
|
|
||||||
|
|
||||||
if (resPacked == null) return null;
|
|
||||||
else return Location.of(dst, (int)(resPacked >> 32), (int)(resPacked & 0xFFFF));
|
|
||||||
}
|
|
||||||
|
|
||||||
// public static SourceMap of(String filename, String raw) {
|
|
||||||
// var json = JSON.parse(Filename.parse(filename), raw);
|
|
||||||
// return new SourceMap();
|
|
||||||
// }
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.repl.mapping;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class VLQ {
|
|
||||||
private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
||||||
|
|
||||||
private static long[] toArray(List<Long> list) {
|
|
||||||
var arr = new long[list.size()];
|
|
||||||
for (var i = 0; i < list.size(); i++) arr[i] = list.get(i);
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String encode(long... arr) {
|
|
||||||
var raw = new StringBuilder();
|
|
||||||
|
|
||||||
for (var data : arr) {
|
|
||||||
var b = data < 0 ? 1 : 0;
|
|
||||||
data = Math.abs(data);
|
|
||||||
b |= (int)(data & 0b1111) << 1;
|
|
||||||
data >>= 4;
|
|
||||||
b |= data > 0 ? 0x20 : 0;;
|
|
||||||
raw.append(ALPHABET.charAt(b));
|
|
||||||
|
|
||||||
while (data > 0) {
|
|
||||||
b = (int)(data & 0b11111);
|
|
||||||
data >>= 5;
|
|
||||||
b |= data > 0 ? 0x20 : 0;
|
|
||||||
raw.append(ALPHABET.charAt(b));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return raw.toString();
|
|
||||||
}
|
|
||||||
public static long[] decode(String val) {
|
|
||||||
if (val.length() == 0) return new long[0];
|
|
||||||
|
|
||||||
var list = new ArrayList<Long>();
|
|
||||||
|
|
||||||
for (var i = 0; i < val.length();) {
|
|
||||||
var sign = 1;
|
|
||||||
var curr = ALPHABET.indexOf(val.charAt(i++));
|
|
||||||
var cont = (curr & 0x20) == 0x20;
|
|
||||||
if ((curr & 1) == 1) sign = -1;
|
|
||||||
long res = (curr & 0b11110) >> 1;
|
|
||||||
var n = 4;
|
|
||||||
|
|
||||||
for (; i < val.length() && cont;) {
|
|
||||||
curr = ALPHABET.indexOf(val.charAt(i++));
|
|
||||||
cont = (curr & 0x20) == 0x20;
|
|
||||||
res |= (curr & 0b11111) << n;
|
|
||||||
n += 5;
|
|
||||||
if (!cont) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
list.add(res * sign);
|
|
||||||
}
|
|
||||||
|
|
||||||
return toArray(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String encodeMapping(long[][][] arr) {
|
|
||||||
var res = new StringBuilder();
|
|
||||||
var semicolon = false;
|
|
||||||
|
|
||||||
for (var line : arr) {
|
|
||||||
var comma = false;
|
|
||||||
|
|
||||||
if (semicolon) res.append(";");
|
|
||||||
semicolon = true;
|
|
||||||
|
|
||||||
for (var el : line) {
|
|
||||||
if (comma) res.append(",");
|
|
||||||
comma = true;
|
|
||||||
res.append(encode(el));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.toString();
|
|
||||||
}
|
|
||||||
public static long[][][] decodeMapping(String val) {
|
|
||||||
var lines = new ArrayList<long[][]>();
|
|
||||||
|
|
||||||
for (var line : val.split(";", -1)) {
|
|
||||||
var elements = new ArrayList<long[]>();
|
|
||||||
for (var el : line.split(",", -1)) {
|
|
||||||
elements.add(decode(el));
|
|
||||||
}
|
|
||||||
lines.add(elements.toArray(new long[0][]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines.toArray(new long[0][][]);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user