@effectful/transducers
Version:
JS syntax transformation framework for @effectful/js
770 lines (757 loc) • 25.1 kB
JavaScript
"use strict";
exports.__esModule = true;
exports.argumentsSym = exports.SymbolSym = exports.ObjectSym = exports.ArraySym = void 0;
exports.assignBody = assignBody;
exports.assignSym = void 0;
exports.calcRefScopes = calcRefScopes;
exports.cloneSym = cloneSym;
exports.collectBlockDirectives = collectBlockDirectives;
exports.emitTempVar = emitTempVar;
exports.emitUndefined = emitUndefined;
exports.namePos = namePos;
exports.newSym = newSym;
exports.prepare = void 0;
exports.replaceUndefined = replaceUndefined;
exports.resolveTempVars = exports.resolve = exports.reset = exports.reserved = void 0;
exports.tempNames = tempNames;
exports.undefinedSym = exports.tempVar = void 0;
var Kit = require("./kit");
var _core = require("./core");
(0, _core.symInfo)(_core.Tag.ClassDeclaration).funDecl = true;
(0, _core.symInfo)(_core.Tag.FunctionDeclaration).funDecl = true;
let symNum = 0;
const nameOpts = ["a", "b", "c", "d", "e", "f", "g", "h", "k", "m", "n", "x", "y", "z"];
function namePos(n, pos) {
if (!n.length) {
const len = nameOpts.length;
if (pos < len) return nameOpts[pos];
return `${nameOpts[pos % len]}${Math.floor(pos / len)}`;
}
if (n[n.length - 1] === "_") return n + (pos + 1);
if (pos === 0) {
return reserved.get(n) || n;
}
if (pos === 1) return "_" + n;
return `${n}${pos - 1}`;
}
const reserved = exports.reserved = new Map([["arguments", "args"]]);
// String -> Sym
function newSym(name, strict = false, decl) {
if (!name) name = "";
return {
name,
orig: name,
id: `${name}_${symNum++}`,
strict,
decl,
num: symNum++,
type: null,
emitConstMethod: emitSym
};
}
const undefinedSym = exports.undefinedSym = newSym("undefined", true);
const argumentsSym = exports.argumentsSym = newSym("arguments", true);
const ObjectSym = exports.ObjectSym = newSym("Object", true);
const ArraySym = exports.ArraySym = newSym("Array", true);
const SymbolSym = exports.SymbolSym = newSym("Symbol", true);
const globals = new Map([["undefined", undefinedSym], ["arguments", argumentsSym], ["Object", ObjectSym], ["Array", ArraySym], ["Symbol", SymbolSym]]);
/** `sort` comparator */
function byNum(a, b) {
return a.num - b.num;
}
function cloneSym(sym) {
const res = newSym(sym.orig);
res.declScope = sym.declScope;
res.declLoop = sym.declLoop;
res.captLoop = sym.captLoop;
res.declBlock = sym.declBlock;
res.decl = sym.decl;
res.param = sym.param;
return res;
}
(0, _core.symInfo)(_core.Tag.ClassDeclaration).funDecl = true;
(0, _core.symInfo)(_core.Tag.FunctionDeclaration).funDecl = true;
function* emitSym(pos) {
yield (0, _core.tok)(pos, _core.Tag.Identifier, {
node: {},
sym: this
});
}
/**
* sets temporal `node.name` for each Identifier for debug dumps outputs
*/
function* tempNames(s) {
for (const i of s) {
if (i.enter && i.type === _core.Tag.Identifier && i.value.sym != null && !i.value.node.name) {
i.value.node.name = i.value.sym.strict ? i.value.sym.name : i.value.sym.id;
}
yield i;
}
}
/**
* This assigns unique Symbol object for each variable declaration and usage.
* It stores it in `sym` fields, for root value stores map syms mapping the
* sym object to a SymbolInfo structure
*
* interface Sym {
* name: String;
* orig: String;
* num: number;
* sym: Symbol;
* unordered: boolean;
* declScope: TokenValue;
* declLoop?: TokenValue; -- loop scope
* captLoop?: TokenValue; -- loop to be captured in
* declBlock?: TokenValue;
* decl?: TokenValue;
* param?: TokenValue;
* }
*
* for each identifier referening variable:
*
* type Value = Value & {sym:Sym,decl?:true}
* & {decls?:Map<string,Sym>}
* & {root?:boolean}
*
*/
const assignSym = exports.assignSym = Kit.pipe(_core.resetFieldInfo, Kit.toArray,
// collecting each declaration in each block before resolving
// because function's may use the ones declared after
function collectDecls(si) {
const sa = Kit.toArray(si);
const s = Kit.auto(sa);
const report = s.first.type === _core.Tag.File && !s.first.value.scopeDone;
s.first.value.scopeDone = true;
function _collectDecls(func, block, funcSyms, funcVars, blockSyms, loop) {
function checkScope(val, syms) {
// checking the scope only the first time
if (report) {
const m = new Map();
for (const i of syms) {
if (!i.strict || i.func || i.unordered || i.declScope == null) continue;
let l = m.get(i.orig);
if (l == null) m.set(i.orig, l = []);
l.push(i);
}
for (const i of m.values()) {
if (i.length > 1) {
throw s.error(`Identifier ${i[0].orig} has already been declared`, i[i.length - 1].decl);
}
}
}
val.decls = new Set(syms);
}
function pat(sw, param, params, unordered, nextSyms) {
for (const k of sw) {
if (k.enter) {
// TODO: make it stronger
if (k.pos === _core.Tag.key) {
if (s.curLev()) _collectDecls(func, block, funcSyms, funcVars, blockSyms, loop);
continue;
}
switch (k.type) {
case _core.Tag.Identifier:
if (!k.value.fieldInfo.declVar) break;
const sym = id(k, nextSyms, unordered, loop);
if (sym && params) {
params.push(sym);
sym.param = param;
}
break;
case _core.Tag.AssignmentPattern:
pat(s.one(), param, params, unordered, nextSyms);
_collectDecls(func, block, funcSyms, funcVars, blockSyms, loop);
break;
}
}
}
}
function id(i, syms, unordered, loop) {
const fi = i.value.fieldInfo;
if (fi.declVar) {
i.value.decl = true;
let {
node: {
name
},
sym
} = i.value;
if (sym != null) name = sym.orig;else {
if (unordered && name && name.length) sym = i.value.sym = funcVars.get(name);
if (!sym) sym = i.value.sym = newSym(name, true, i.value);
}
if (unordered) funcVars.set(name, sym);
syms.push(sym);
sym.unordered = unordered;
sym.declScope = func;
sym.declBlock = block;
sym.captLoop = sym.declLoop = unordered ? null : loop;
sym.declRange = unordered ? func : block;
sym.param = null;
sym.func = null;
sym.decl = i;
return sym;
} else if (fi.expr || fi.lval) {
i.value.decl = false;
}
return null;
}
for (const i of s.sub()) {
if (i.enter) {
switch (i.type) {
case _core.Tag.ThisExpression:
break;
case _core.Tag.ForStatement:
{
const nextSyms = [];
const ini = s.cur();
if (ini.pos === _core.Tag.init && ini.type === _core.Tag.VariableDeclaration && ini.value.node.kind !== "var") {
s.take();
_collectDecls(func, i.value, funcSyms, funcVars, nextSyms, loop);
for (const j of nextSyms) j.captLoop = i.value;
s.close(ini);
}
_collectDecls(func, i.value, funcSyms, funcVars, nextSyms, i.value);
checkScope(i.value, nextSyms);
}
break;
case _core.Tag.ForInStatement:
case _core.Tag.ForAwaitStatement:
case _core.Tag.ForOfStatement:
{
const nextSyms = [];
_collectDecls(func, i.value, funcSyms, funcVars, nextSyms, i.value);
checkScope(i.value, nextSyms);
}
break;
case _core.Tag.BlockStatement:
case _core.Tag.SwitchStatement:
{
const nextSyms = [];
_collectDecls(func, i.value, funcSyms, funcVars || new Map(), nextSyms, loop);
checkScope(i.value, nextSyms);
}
break;
case _core.Tag.ArrowFunctionExpression:
if (!i.leave && s.curLev()) {
const nextSyms = [];
(0, _core.invariant)(s.cur().pos === _core.Tag.params);
const params = [];
for (const j of s.one()) {
if (j.enter) pat(s.sub(), i.value, params, false, nextSyms);
}
const block = s.cur();
for (const k of params) {
k.declScope = i.value;
k.declBlock = block.value;
}
if (block.type === _core.Tag.BlockStatement) s.take();
_collectDecls(i.value, block.value, nextSyms, new Map(), nextSyms);
i.value.body = block.value;
i.value.paramSyms = params;
i.value.parScope = func;
checkScope(block.value, nextSyms);
if (block.type === _core.Tag.BlockStatement) s.close(block);
s.close(i);
}
break;
case _core.Tag.ClassMethod:
case _core.Tag.ObjectMethod:
const k = s.take();
(0, _core.invariant)(k.pos === _core.Tag.key);
if (!k.leave) {
_collectDecls(func, block, funcSyms, funcVars, blockSyms, loop);
s.close(k);
}
case _core.Tag.File:
case _core.Tag.FunctionExpression:
case _core.Tag.FunctionDeclaration:
if (!i.leave && s.curLev()) {
const nextSyms = [];
const ti = (0, _core.symInfo)(i.type);
let j = s.peel();
let funcId;
if (j.pos === _core.Tag.id) {
const fd = ti.funDecl;
id(j, fd && funcSyms != null ? func.sloppy ? funcSyms : blockSyms : nextSyms, fd);
(0, _core.invariant)(j.value.sym);
Kit.skip(s.one());
Kit.skip(s.leave());
funcId = j.value.sym;
j = s.peel();
}
const params = [];
if (j.pos === _core.Tag.params) {
pat(s.sub(), i.value, params, false, nextSyms);
Kit.skip(s.leave());
j = s.peel();
}
(0, _core.invariant)(j.pos === _core.Tag.body || j.pos === _core.Tag.program);
let sloppy = func && func.sloppy;
const st = j.value.node.sourceType;
if (st === "script") sloppy = true;else if (st === "module") sloppy = false;
if (j.value.blockDirs && j.value.blockDirs.has("use strict")) sloppy = false;
i.value.sloppy = sloppy;
j.value.root = true;
_collectDecls(i.value, j.value, nextSyms, new Map(), nextSyms);
for (const k of params) {
k.declScope = i.value;
k.declBlock = j.value;
}
if (funcId) {
if (!ti.funDecl) {
funcId.declScope = i.value;
funcId.declBlock = j.value;
}
funcId.func = i.value;
}
if (!i.value.funcId) i.value.funcId = funcId;
i.value.paramSyms = params;
i.value.parScope = func;
checkScope(j.value, nextSyms);
Kit.skip(s.leave());
}
break;
case _core.Tag.VariableDeclaration:
const unordered = i.value.node.kind === "var";
const dstSyms = unordered ? funcSyms : blockSyms;
for (const j of s.sub()) {
if (j.enter && !j.leave && j.type === _core.Tag.VariableDeclarator) {
const k = s.curLev();
if (k && k.pos === _core.Tag.id) pat(s.one(), null, null, unordered, dstSyms);
_collectDecls(func, block, funcSyms, funcVars, blockSyms, loop);
}
}
break;
case _core.Tag.CatchClause:
if (s.cur().pos === _core.Tag.param) {
const nextSyms = [];
pat(s.one(), null, null, undefined, nextSyms);
_collectDecls(func, i.value, funcSyms, funcVars, nextSyms, loop);
checkScope(i.value, nextSyms);
}
break;
case _core.Tag.JSXIdentifier:
i.value.decl = false;
break;
case _core.Tag.Identifier:
const fi = i.value.fieldInfo;
if (fi.declVar) {
id(i, blockSyms, undefined, loop);
} else if (fi.expr || fi.lval) i.value.decl = false;
break;
}
}
}
}
_collectDecls(s.first.value.body);
return sa;
}, function assignSym(si) {
const sa = Kit.toArray(si);
const s = Kit.auto(sa);
function decls(sw, func, scope) {
for (const i of sw) {
if (i.enter) {
switch (i.type) {
case _core.Tag.JSXIdentifier:
const name = i.value.node.name;
if (i.pos !== _core.Tag.property && name[0].toLowerCase() === name[0]) break;
case _core.Tag.Identifier:
let {
sym
} = i.value;
if (i.value.decl === true) {
if (sym.strict && (!sym.unordered || sym.funcId)) scope.set(sym.name, sym);
} else if (i.value.decl === false) {
if (sym == null) {
const {
name
} = i.value.node;
let sym = scope.get(name);
if (sym == null) {
let undef = globals.get(name);
if (undef == null) {
globals.set(name, undef = newSym(name, true, i.value));
undef.num = -1;
undef.unordered = false;
undef.declScope = null;
}
i.value.sym = undef;
break;
}
i.value.sym = sym;
}
}
break;
case _core.Tag.Program:
case _core.Tag.BlockStatement:
case _core.Tag.SwitchStatement:
case _core.Tag.CatchClause:
case _core.Tag.ForStatement:
case _core.Tag.ForInStatement:
case _core.Tag.ForAwaitStatement:
case _core.Tag.ForOfStatement:
const bscope = new Map(scope);
if (i.value.decls) {
for (const sym of i.value.decls) {
if (sym.strict) bscope.set(sym.name, sym);
}
}
decls(s.sub(), func, bscope);
break;
case _core.Tag.ObjectMethod:
decls(s.one(), func, scope);
case _core.Tag.ArrowFunctionExpression:
case _core.Tag.FunctionExpression:
case _core.Tag.File:
case _core.Tag.FunctionDeclaration:
case _core.Tag.ClassMethod:
case _core.Tag.ClassPrivateMethod:
decls(s.sub(), i.value, new Map(scope));
break;
}
}
}
}
decls(s, s.first.value, new Map(), new Map());
return sa;
});
/** assigns field `body` for each function pointing to its `Tag.body` value */
function assignBody(si) {
const sa = Kit.toArray(si);
const s = Kit.auto(sa);
for (const i of s) {
if (i.enter) {
switch (i.type) {
case _core.Tag.FunctionDeclaration:
case _core.Tag.Program:
case _core.Tag.FunctionExpression:
case _core.Tag.ObjectMethod:
case _core.Tag.ClassMethod:
case _core.Tag.ClassPrivateMethod:
case _core.Tag.ArrowFunctionExpression:
for (const j of s) {
if (j.enter && j.pos === _core.Tag.body) {
i.value.body = j.value;
break;
}
}
}
}
}
return sa;
}
/**
* for each variable sets its usages scope (list of functions where the
* variable is used except declaration function)
*
* type Sym = Sym & { refScopes: Set<TokenValue> }
*/
function calcRefScopes(si) {
const sa = Kit.toArray(si);
const s = Kit.auto(sa);
function scope(root) {
for (const i of s.sub()) {
if (i.enter) {
switch (i.type) {
case _core.Tag.FunctionDeclaration:
Kit.skip(s.one());
case _core.Tag.FunctionExpression:
case _core.Tag.ObjectMethod:
case _core.Tag.ClassMethod:
case _core.Tag.ClassPrivateMethod:
case _core.Tag.ArrowFunctionExpression:
scope(i.value);
break;
case _core.Tag.Identifier:
const si = i.value.sym;
if (si != null && si.declScope !== root) (si.refScopes || (si.refScopes = new Set())).add(root);
}
}
}
}
scope(s.first.value);
return sa;
}
/**
* for each block calculates a set of variables referenced in it
*
* type Value = Value * { varRefs?: Set<Sym> }
*/
function calcBlockRefs(si) {
const sa = Kit.toArray(si);
const s = Kit.auto(sa);
function walk(totDecls) {
const use = new Set();
for (const i of s.sub()) {
if (i.enter) {
if (i.value.decls != null && !i.leave) {
const decls = new Set(i.value.decls);
const nextDecls = new Set(totDecls);
decls.forEach(nextDecls.add, nextDecls);
const used = walk(nextDecls);
const cur = i.value.varRefs = new Set();
for (const j of decls) cur.add(j);
for (const j of used) {
cur.add(j);
if (!decls.has(j)) use.add(j);
}
}
if (i.type === _core.Tag.Identifier && i.value.sym != null && i.value.decl === false) {
use.add(i.value.sym);
}
}
}
return use;
}
walk(new Set());
return sa;
}
/**
* after adding some names, there may be some naming conflicts
* this pass resolves them by looking for same name but different symbols ids
* and renaming them accordingly
*/
function solve(si) {
const sa = Kit.toArray(si);
const allIds = new Set();
const idToks = [];
const decls = new Set();
const scopes = [];
for (const i of sa) {
if (i.enter) {
if (i.value.varRefs != null) scopes.push(i.value);
if (i.type === _core.Tag.Identifier && i.value.sym) {
if (i.value.decl) decls.add(i.value.sym);
idToks.push(i.value);
}
}
}
// each block where name is used
// Map<string,Set<BlockValue>>
const namesStore = new Map();
// each block where Sym is referred
// Map<Sym,Set<BlockValue>>
const symsStore = new Map();
// blocks with symbol names conflicts
// Map<string,{syms:Sym[],block:BlockValue}[]>
const conflicts = new Map();
for (const i of scopes) {
const names = new Map();
for (const j of i.varRefs) {
allIds.add(j);
Kit.mapAdd(symsStore, j, i);
j.hasDecl = decls.has(j);
Kit.mapPush(names, j.orig, j);
}
for (const [name, syms] of names) {
if (syms.length > 1 || !name.length || reserved.has(name)) Kit.mapPush(conflicts, name, {
syms,
block: i
});
}
}
for (const j of allIds) {
j.name = j.orig;
}
if (conflicts.size) {
const table = [];
for (const [name, i] of conflicts) {
const allSyms = new Set();
const cur = {
name,
syms: allSyms,
rawSets: []
};
table.push(cur);
for (const {
syms
} of i) {
cur.rawSets.push(syms);
syms.forEach(allSyms.add, allSyms);
}
for (const j of allSyms) j.name = null;
}
for (const i of allIds) {
if (i.name) {
let cur = namesStore.get(i.name);
if (!cur) namesStore.set(i.name, cur = new Set());
symsStore.get(i).forEach(cur.add, cur);
}
}
for (const {
name,
rawSets
} of table) {
for (const tup of rawSets) {
tup.sort(byNum);
let pos = 0;
for (const i of tup) {
if (i.name != null) continue;
let result = i.orig;
let nameScopes;
const symScopes = symsStore.get(i);
if (i.hasDecl) {
lookup: for (;;) {
result = namePos(name, pos++);
nameScopes = namesStore.get(result);
if (!nameScopes) break;
for (const j of symScopes) {
if (nameScopes.has(j)) continue lookup;
}
break;
}
}
if (!nameScopes) namesStore.set(result, nameScopes = new Set());
symScopes.forEach(nameScopes.add, nameScopes);
i.name = result;
}
}
}
}
for (const j of allIds) {
if (!j.hasDecl && !j.strict && !j.global) {
j.name = `${j.name}_UNDECL_${j.num}`;
const opts = sa[0].value.opts;
const fn = opts && opts.file && opts.file.filename;
console.warn(`INTERNAL ERROR: not declared generated symbol name ${j.name}(${fn})`);
}
}
for (const i of idToks) {
if (!i.sym.name) {
const opts = sa[0].value.opts;
const fn = opts && opts.file && opts.file.filename;
i.sym.name = `UNKNOWN_${i.sym.id}`;
console.warn(`INTERNAL ERROR: not resolved symbol name ${i.sym.name}(${fn})`);
}
i.node.name = i.sym.name || (i.sym.name = i.sym.orig);
}
return sa;
}
const prepare = exports.prepare = Kit.pipe(collectBlockDirectives, assignSym);
const reset = exports.reset = assignSym;
const resolve = exports.resolve = Kit.pipe(reset, calcBlockRefs, replaceUndefined, solve);
/** emits `void 0` at `pos` */
function* emitUndefined(pos) {
(0, _core.invariant)(pos);
const value = {
node: {
operator: "void",
prefix: true
}
};
yield (0, _core.enter)(pos, _core.Tag.UnaryExpression, value);
yield (0, _core.tok)(_core.Tag.argument, _core.Tag.NumericLiteral, {
node: {
value: "0"
}
});
yield (0, _core.leave)(pos, _core.Tag.UnaryExpression, value);
}
/** replaces `undefined` -> `void 0` for generated `undefined` */
function* replaceUndefined(s) {
let p;
for (const i of s) {
if (i.type === _core.Tag.Identifier && i.value.sym === undefinedSym && !i.value.node.loc) {
if (p && i.pos === _core.Tag.argument) {
switch (p.type) {
case _core.Tag.ReturnStatement:
case _core.Tag.YieldExpression:
i.value.node.argument = null;
continue;
}
}
if (i.enter) yield* emitUndefined(i.pos);
continue;
}
yield p = i;
}
}
const tempVar = exports.tempVar = (0, _core.symbol)("tempVar");
function* emitTempVar() {
const sym = newSym("_temp");
yield (0, _core.tok)(tempVar, tempVar, {
sym
});
return sym;
}
/** emit `var` declarations for each `tempVar` */
const resolveTempVars = exports.resolveTempVars = Kit.pipe(function collectTempVars(si) {
const s = Kit.auto(si);
function* walk(b) {
for (const i of s.sub()) {
if (i.enter) {
switch (i.type) {
case _core.Tag.BlockStatement:
case _core.Tag.Program:
yield i;
walk(i.value.tempVars = []);
continue;
case tempVar:
b.push(i.value.sym);
s.close(i);
continue;
}
}
yield i;
}
}
return walk([]);
}, Kit.toArray, function* emplaceTempVars(si) {
const s = Kit.auto(si);
for (const i of s) {
yield i;
if (i.enter) {
switch (i.type) {
case _core.Tag.BlockStatement:
case _core.Tag.Program:
if (i.value.tempVars && i.value.tempVars.length) {
const lab = s.label();
yield* s.peelTo(_core.Tag.body);
yield s.enter(_core.Tag.push, _core.Tag.VariableDeclaration, {
node: {
kind: "var"
}
});
yield s.enter(_core.Tag.declarations, _core.Tag.Array);
for (const sym of i.value.tempVars) {
yield s.enter(_core.Tag.push, _core.Tag.VariableDeclarator);
yield s.tok(_core.Tag.id, _core.Tag.Identifier, {
sym
});
yield* s.leave();
}
yield* lab();
}
}
}
}
});
/** collects JS directives (strings in beginnigs of the block),
* to block's blockDirs field
*/
function collectBlockDirectives(si) {
const sa = Kit.toArray(si);
const s = Kit.auto(sa);
_collectDirectives();
return sa;
function _collectDirectives(block, dirs) {
for (const i of s.sub()) {
if (i.enter) {
i.value.parentBlock = block;
switch (i.type) {
case _core.Tag.Program:
case _core.Tag.BlockStatement:
_collectDirectives(i.value, i.value.blockDirs = new Set());
break;
case _core.Tag.DirectiveLiteral:
dirs.add(i.value.node.value);
break;
case _core.Tag.ExpressionStatement:
if (s.cur().type === _core.Tag.StringLiteral) dirs.add(i, s.cur().value.node.value);
break;
}
}
}
}
}