2021-01-22 23:12:11 +00:00
|
|
|
import { Token, TokenType, Item, Items, isGroup } from './tokens.js';
|
2021-01-14 12:09:53 +00:00
|
|
|
import { Pos, startPos, advancePos } from './position.js';
|
|
|
|
import { vlqEncode } from './vlq.js';
|
2021-01-22 23:12:11 +00:00
|
|
|
import { SpanInfo } from './span.js';
|
2021-01-14 12:09:53 +00:00
|
|
|
|
|
|
|
export interface SourceMap {
|
|
|
|
version: 3;
|
|
|
|
file?: string;
|
|
|
|
sourceRoot?: string, // default: ""
|
|
|
|
sources: Array<string>;
|
|
|
|
sourcesContent?: Array<string | null>; // default: null at each entry
|
|
|
|
names: Array<string>;
|
|
|
|
mappings: string;
|
|
|
|
}
|
|
|
|
|
2021-01-15 12:38:15 +00:00
|
|
|
export interface NoSourceMapping {
|
|
|
|
generatedStartColumn: number; // zero-based
|
|
|
|
}
|
|
|
|
export interface SourceMapping extends NoSourceMapping {
|
|
|
|
sourceIndex: number;
|
|
|
|
sourceStartLine: number; // zero-based (!!)
|
|
|
|
sourceStartColumn: number; // zero-based
|
|
|
|
}
|
|
|
|
export interface SourceNameMapping extends SourceMapping {
|
|
|
|
nameIndex: number;
|
2021-01-14 12:09:53 +00:00
|
|
|
}
|
|
|
|
|
2021-01-15 12:38:15 +00:00
|
|
|
export type NonEmptyMapping = NoSourceMapping | SourceMapping | SourceNameMapping;
|
|
|
|
export type Mapping = {} | NonEmptyMapping;
|
|
|
|
|
|
|
|
function encodeMapping(entry: NonEmptyMapping): Array<number> {
|
2021-01-14 12:09:53 +00:00
|
|
|
const a = [entry.generatedStartColumn];
|
|
|
|
if ('sourceIndex' in entry) {
|
|
|
|
a.push(entry.sourceIndex);
|
|
|
|
a.push(entry.sourceStartLine);
|
|
|
|
a.push(entry.sourceStartColumn);
|
|
|
|
if ('nameIndex' in entry) {
|
|
|
|
a.push(entry.nameIndex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
|
2021-01-15 12:38:15 +00:00
|
|
|
function maybeDelta(newValue: number, oldValue: number | undefined): number {
|
2021-01-14 12:09:53 +00:00
|
|
|
// console.log('maybeDelta', oldValue, newValue);
|
2021-01-15 12:38:15 +00:00
|
|
|
return (oldValue === void 0) ? newValue : newValue - oldValue;
|
2021-01-14 12:09:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export class CodeWriter {
|
|
|
|
readonly file: string | null;
|
|
|
|
readonly pos: Pos;
|
|
|
|
readonly sources: Array<string> = [];
|
|
|
|
readonly chunks: Array<string> = [];
|
2021-01-15 12:38:15 +00:00
|
|
|
readonly mappings: Array<Array<NonEmptyMapping>> = [];
|
2021-01-22 23:12:11 +00:00
|
|
|
readonly targetToSourceMap = new SpanInfo<Token>();
|
|
|
|
readonly sourceToTargetMap = new SpanInfo<number>();
|
2021-01-15 12:38:15 +00:00
|
|
|
previous: Partial<SourceNameMapping> = {};
|
2021-01-14 12:09:53 +00:00
|
|
|
previousPos: Pos | null = null;
|
|
|
|
|
|
|
|
constructor(file: string | null) {
|
|
|
|
this.file = file;
|
|
|
|
this.pos = startPos(this.file ?? '');
|
|
|
|
}
|
|
|
|
|
|
|
|
get text(): string {
|
|
|
|
return this.chunks.join('');
|
|
|
|
}
|
|
|
|
|
|
|
|
get map(): SourceMap {
|
|
|
|
// console.log(this.mappings.map(segs => segs.map(encodeMapping)));
|
|
|
|
const mappings = this.mappings.map(segments =>
|
|
|
|
segments.map(encodeMapping).map(vlqEncode).join(',')).join(';');
|
|
|
|
const m: SourceMap = {
|
|
|
|
version: 3,
|
|
|
|
sources: [... this.sources],
|
|
|
|
names: [],
|
|
|
|
mappings,
|
|
|
|
};
|
|
|
|
if (this.file !== null) m.file = this.file;
|
|
|
|
return m;
|
|
|
|
}
|
|
|
|
|
|
|
|
finishLine() {
|
|
|
|
// console.log('newline');
|
|
|
|
this.mappings.push([]);
|
|
|
|
this.previous.generatedStartColumn = undefined;
|
|
|
|
this.previousPos = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
sourceIndexFor(name: string) {
|
|
|
|
let i = this.sources.indexOf(name);
|
|
|
|
if (i === -1) {
|
|
|
|
this.sources.push(name);
|
|
|
|
i = this.sources.length - 1;
|
|
|
|
}
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
addMapping(p: Pos, type: TokenType) {
|
|
|
|
// console.log('considering', p, type);
|
|
|
|
|
|
|
|
const oldPos = this.previousPos;
|
|
|
|
|
|
|
|
if ((oldPos === null || oldPos.name === p.name) &&
|
|
|
|
(type === TokenType.SPACE || type === TokenType.NEWLINE))
|
|
|
|
{
|
|
|
|
// console.log('whitespace skip');
|
|
|
|
if (this.previousPos !== null) {
|
|
|
|
this.previousPos = p;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.previousPos = p;
|
|
|
|
|
|
|
|
if ((oldPos?.name === p.name) &&
|
|
|
|
((p.name === null) ||
|
|
|
|
((oldPos?.column === p.column) && (oldPos?.line === p.line))))
|
|
|
|
{
|
|
|
|
// console.log('skipping', this.previous, oldPos, p);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-01-15 12:38:15 +00:00
|
|
|
let n: NonEmptyMapping = {
|
|
|
|
generatedStartColumn: maybeDelta(this.pos.column, this.previous.generatedStartColumn),
|
|
|
|
};
|
2021-01-14 12:09:53 +00:00
|
|
|
this.previous.generatedStartColumn = this.pos.column;
|
|
|
|
|
|
|
|
if (p.name !== null) {
|
|
|
|
const sourceIndex = this.sourceIndexFor(p.name);
|
2021-01-15 12:38:15 +00:00
|
|
|
n = {
|
|
|
|
... n,
|
|
|
|
sourceIndex: maybeDelta(sourceIndex, this.previous.sourceIndex),
|
|
|
|
sourceStartColumn: maybeDelta(p.column, this.previous.sourceStartColumn),
|
|
|
|
sourceStartLine: maybeDelta(p.line - 1, this.previous.sourceStartLine),
|
|
|
|
};
|
2021-01-14 12:09:53 +00:00
|
|
|
this.previous.sourceIndex = sourceIndex;
|
|
|
|
this.previous.sourceStartColumn = p.column;
|
|
|
|
this.previous.sourceStartLine = p.line - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// console.log('pushing',
|
|
|
|
// n,
|
|
|
|
// this.previous,
|
|
|
|
// oldPos?.line + ':' + oldPos?.column,
|
|
|
|
// p.line + ':' + p.column);
|
|
|
|
this.mappings[this.mappings.length - 1].push(n);
|
|
|
|
}
|
|
|
|
|
|
|
|
chunk(p: Pos, s: string, type: TokenType) {
|
|
|
|
p = { ... p };
|
|
|
|
this.chunks.push(s);
|
|
|
|
if (this.mappings.length === 0) this.finishLine();
|
|
|
|
this.addMapping(p, type);
|
|
|
|
for (const ch of s) {
|
|
|
|
advancePos(p, ch);
|
|
|
|
if (advancePos(this.pos, ch)) {
|
|
|
|
this.finishLine();
|
|
|
|
this.addMapping(p, type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
emit(i: Item | Items) {
|
|
|
|
if (Array.isArray(i)) {
|
|
|
|
i.forEach(j => this.emit(j));
|
|
|
|
} else if (isGroup(i)) {
|
2021-01-18 22:11:53 +00:00
|
|
|
this.emit(i.open);
|
2021-01-14 12:09:53 +00:00
|
|
|
this.emit(i.items);
|
2021-01-18 22:11:53 +00:00
|
|
|
if (i.close) this.emit(i.close);
|
2021-01-14 12:09:53 +00:00
|
|
|
} else if (i === null) {
|
|
|
|
// Do nothing.
|
|
|
|
} else {
|
2021-01-22 23:12:11 +00:00
|
|
|
const targetStart = this.pos.pos;
|
|
|
|
if (!i.synthetic) this.sourceToTargetMap.add(i.start.pos, i.end.pos, targetStart);
|
2021-01-14 12:09:53 +00:00
|
|
|
this.chunk(i.start, i.text, i.type);
|
2021-01-22 23:12:11 +00:00
|
|
|
this.targetToSourceMap.add(targetStart, this.pos.pos, i);
|
2021-01-14 12:09:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|