@effectful/transducers-loose
Version:
@effectful/transducers built with faster generators
209 lines (204 loc) • 6.31 kB
JavaScript
"use strict";
exports.__esModule = true;
exports.emitConst = emitConst;
exports.parse = parse;
exports.template = template;
exports.toks = toks;
exports.toksRaw = toksRaw;
var _parser = require("@babel/parser");
var _core = require("./core");
var _types = require("./types");
var M = require("@effectful/es-rt/opts/loose");
const memo = new Map();
function parse(jsCode) {
return (0, _parser.parse)(jsCode, {
sourceType: "module",
allowReturnOutsideFunction: true,
plugins: ["dynamicImport"]
});
}
/**
* parses string `s` and outputs its token value
* following prefixes are supported in the string
* - '^' - module's top level
* - '=' - the string is JS expression
* - '*' - the string is a list of statements
* - '>' - variable declarator
* - otherwise it is considered to be a statement
*
* if in the code in `s` identifier starts with `$` it has special meaning
* if next character is:
* - digit - it will replaces symbol of identifier by `syms`
* at the corresponding position (1 based)
* - `$` - keeps only one `$`
* - `I` - unshifts a symbol from `syms` and replaces the symbol of
* the identifier
*/
function toks(pos, s, ...syms) {
return toksRaw(pos, s, syms);
}
/** same as `toks` but avoiding variable length arguments */
function toksRaw(pos, s, syms) {
if (s.substr != null) {
let r = memo.get(s);
if (r == null) {
let topLevel = false;
if (s[0] === "^") {
s = s.slice(1);
topLevel = true;
}
let mod = null;
let js = s;
switch (s[0]) {
case "=": // expression
case "*": // list of statements
case ">":
// var declarator
mod = s[0];
js = s.slice(1);
}
js = topLevel ? js : `function main() { for(;;) {${js}} }`;
const b = parse(js);
const body = topLevel ? b.program.body : b.program.body[0].body.body[0].body.body;
switch (mod) {
case "=":
r = body[0].expression;
break;
case ">":
const s = body[0].expression;
r = {
type: "VariableDeclarator",
id: s.left,
init: s.right
};
break;
case "*":
break;
default:
r = body[0];
}
if (mod !== "=" && mod !== ">") {
if (mod === "*") {
r = body;
} else {
r = body[0];
}
}
memo.set(s, r = (0, _core.produceNode)(pos, r));
}
s = r;
}
function replace(root) {
if (!syms || !syms.length) return root;
var loop = M.iterator((0, _core.dft)(root));
for (; !(loop = loop.step()).done;) {
const i = loop.value;
const {
node
} = i;
if (!Array.isArray(node)) node.loc = node.start = node.end = null;
if (i.type === _types.Tag.Identifier && node.name[0] === "$") {
const {
name
} = node;
const rest = name.substr(1);
switch (rest[0]) {
case "$":
node.name = rest;
break;
case "I":
node.name = (i.sym = syms.shift()).name;
break;
default:
if (rest.length && !isNaN(rest)) node.name = (i.sym = syms[rest - 1]).name;
}
}
}
return root;
}
s = (0, _core.clone)(s);
s.pos = pos;
return replace(s);
}
/**
* same as `toks` but extends formats with `$_`/'$E` identifier to mark
* a position where the output stream must be stopped
*
* returns an array with the first item containing the whole node
* and the next ones are placeholders
*
* in expression statements $E focuses only the expression, while `$_` is for
* the statement, in other contexts they do matches the same
*/
function template(pos, code, ...syms) {
const root = toks(pos, code, ...syms);
const ret = [root];
var loop = M.iterator((0, _core.dft)(root));
for (; !(loop = loop.step()).done;) {
const i = loop.value;
switch (i.type) {
case _types.Tag.ExpressionStatement:
const child = i.firstChild;
if (child.type === _types.Tag.Identifier && child.node.name === "$_") ret.push(i);
break;
case _types.Tag.Identifier:
const name = i.node.name;
if (name === "$_" || name === "$E") ret.push(i);
break;
}
}
return ret;
}
/** converts compile time constant into a list of tokens */
function emitConst(pos, value) {
if (value === void 0) {
const res = (0, _core.tok)(pos, _types.Tag.UnaryExpression, {
operator: "void"
});
(0, _core.append)(res, (0, _core.num)(_types.Tag.argument, 0));
return res;
}
switch (typeof value) {
case "number":
return (0, _core.num)(pos, value);
case "string":
return (0, _core.tok)(pos, _types.Tag.StringLiteral, {
value
});
case "boolean":
return (0, _core.tok)(pos, _types.Tag.BooleanLiteral, {
value
});
case "object":
if (value === null) return (0, _core.node)(pos, _types.Tag.NullLiteral);
if (value.emitConstMethod) return value.emitConstMethod(pos);
if (Array.isArray(value)) {
const res = (0, _core.node)(pos, _types.Tag.ArrayExpression);
const els = (0, _core.arr)(_types.Tag.elements);
var loop = M.iterator(value);
for (; !(loop = loop.step()).done;) {
const j = loop.value;
(0, _core.append)(els, emitConst(_types.Tag.push, j));
}
(0, _core.append)(res, els);
return res;
}
const res = (0, _core.node)(pos, _types.Tag.ObjectExpression);
const props = (0, _core.arr)(_types.Tag.properties);
for (const name in value) {
const v = value[name];
if (v === void 0) continue;
const prop = (0, _core.node)(_types.Tag.push, _types.Tag.ObjectProperty);
(0, _core.append)(prop, (0, _core.tok)(_types.Tag.key, _types.Tag.Identifier, {
name
}));
(0, _core.append)(prop, emitConst(_types.Tag.value, v));
(0, _core.append)(props, prop);
}
(0, _core.append)(res, props);
return res;
}
return (0, _core.append)((0, _core.node)(pos, _types.Tag.UnaryExpression, {
operator: "void"
}), (0, _core.num)(_types.Tag.argument, 0)).firstChild;
}