futurescript
Version:
A functional style, but highly readable language that compiles to JavaScript.
178 lines (165 loc) • 5.85 kB
JavaScript
import * as $tools from "./tools.mjs";
export class JsBuilder {
// Form 1: a single string argument
// Form 2: 1 or 2 arguments, where the first is an array
// In form 2, the second argument (if exists) is a string
// that brings "join" effect. Each element of the array must be either string
// or JsBuilder instance.
// Note: Using an array argument whose only element is a string will be treated as
// identical to form 1.
constructor() {
if (Array.isArray(arguments[0])) {
if (arguments.length === 1) {
this.children = arguments[0];
}
else {
let t = [];
for (let i = 0; i < arguments[0].length - 1; i++) {
t.push(arguments[0][i]);
t.push(arguments[1]);
}
if (arguments[0].length > 0) {
t.push(arguments[0][arguments[0].length - 1]);
}
this.children = t;
}
let childJsBuilders = this.childJsBuilders();
childJsBuilders.forEach(m => m.parent = this);
let js = "";
this.children.forEach(child => {
if (child instanceof JsBuilder) {
child.position = js.length; // the child's JS string position in its parent string
js += child.js;
}
else if (typeof child === "string") {
js += child;
}
else {
throw new Error("Child can only be string or JsBuilder instance.");
}
});
this.js = js;
}
else {
this.children = [arguments[0]];
this.js = arguments[0];
}
this.parent = null;
this.position = 0;
}
childJsBuilders() {
return this.children.filter(m => m instanceof JsBuilder);
}
setLexPart(lexPart) {
this._lexPart = lexPart;
return this;
}
getLexPart() {
return this._lexPart;
}
// Share the same lex part with the given mappable node
shareLexPart(mappableNode) {
return this.setLexPart(mappableNode.getLexPart());
}
allMappableJsBuilders() {
let r = [];
let traverse = thing => {
if (thing.children.length === 1 && typeof thing.children[0] === "string") {
r.push(thing);
}
thing.childJsBuilders().forEach(child => {
traverse(child);
});
};
traverse(this);
return r;
}
ancestors() {
let r = [];
let next = this.parent;
while (next !== null) {
r.push(next);
next = next.parent;
}
return r;
}
generateLexMap() {
let r = [];
this.allMappableJsBuilders().filter(m => m.getLexPart()).forEach(jb => {
let sum = jb.position;
jb.ancestors().forEach(ancestor => sum += ancestor.position);
r.push({
jsStartIndex: sum,
jsEndIndex: sum + jb.js.length - 1,
lexPart: jb.getLexPart()
});
});
return r;
}
// Because our JS output is always one-line, we don't need the ";" in source map.
generateSourceMap() {
let lexMap = this.generateLexMap();
let lexes = $tools.distinct(lexMap.map(m => m.lexPart.lex));
lexes.forEach((lex, index) => {
lexMap.forEach(mapItem => {
if (mapItem.lexPart.lex === lex) {
mapItem.lexIndex = index; // this property is redundant, just for assistance
}
});
});
let lastJsStartIndex = 0;
let lastLexIndex = 0;
let lastRawLine = 0;
let lastRawColumn = 0;
let mappings = lexMap.filter(m => {
return m.lexPart.token().rawStartIndex !== undefined;
}).map(m => {
let lineColumn = m.lexPart.lex.rawLineColumn(m.lexPart.token().rawStartIndex);
let r = (
JsBuilder._encodeVlq(m.jsStartIndex - lastJsStartIndex) +
JsBuilder._encodeVlq(m.lexIndex - lastLexIndex) +
JsBuilder._encodeVlq(lineColumn[0] - lastRawLine) +
JsBuilder._encodeVlq(lineColumn[1] - lastRawColumn)
);
lastJsStartIndex = m.jsStartIndex;
lastLexIndex = m.lexIndex;
lastRawLine = lineColumn[0];
lastRawColumn = lineColumn[1];
return r;
});
return JSON.stringify({
version: 3,
sources: lexes.map(m => m.path),
names: [],
mappings: mappings.join(",")
});
}
// `_encodeVlq` is translated from coffeescript/src/sourcemap.litcoffee
// Copyright (c) Jeremy Ashkenas
// MIT License
static _encodeVlq(value) {
let answer = "";
let signBit = value < 0 ? 1 : 0;
let valueToEncode = (Math.abs(value) << 1) + signBit;
while (valueToEncode || !answer) {
let nextChunk = valueToEncode & 31;
valueToEncode = valueToEncode >> 5;
if (valueToEncode) {
nextChunk |= 32;
}
answer += this._encodeBase64(nextChunk);
}
return answer;
}
// `_encodeBase64` is translated from coffeescript/src/sourcemap.litcoffee
// Copyright (c) Jeremy Ashkenas
// MIT License
static _encodeBase64(value) {
let char = this.BASE64_CHARS[value];
if (!char) {
throw new Error("Cannot encode Base64.");
}
return char;
}
}
JsBuilder.BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";