UNPKG

@effectful/transducers

Version:

JS syntax transformation framework for @effectful/js

770 lines (757 loc) 25.1 kB
"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; } } } } }