JS transpiler tutorial
Since version 1.5, k1lib has the ability to transpile clisfrom Python into JS code, ready to be built into an interface. Here’re some examples:
Basic example
Source code
data = repeatF(lambda: random.randint(10, 99), 20) | deref()
jsFunc = data | (toJsFunc(("term", int, 5)) | head("term"))
jsFunc
Compiled JS function
<JsFunc _jsF_986_1709898278_10>Generated JS function:
//k1_moveOutStart
//k1_moveOutEnd
_jsD_270_1709898278_474 = [40, 21, 71, 31, 34, 65, 24, 88, 70, 54, 28, 34, 59, 15, 63, 22, 64, 66, 12, 75];
async function _jsF_986_1709898278_10(term) {
_jsF_986_1709898278_9 = (_jsD_270_1709898278_472) => _jsD_270_1709898278_472.head(term, false)
_jsF_986_1709898278_8 = (_jsD_270_1709898278_471) => { return _jsF_986_1709898278_9(_jsD_270_1709898278_471); };
return _jsF_986_1709898278_8(_jsD_270_1709898278_474);
}
Source code
jsFunc.interface("jsone")
Html output
In this example, data
is a list of 20 random numbers. You can then pipe
it into a toJsFunc
-captured block (review capturing concept here).
Every operation that’s captured will be transpiled into JS. and bundled into a
JsFunc
. Then you can inject that function anywhere you
want in your application. A lot of times, you’d want to display a search interface
right away, so you can use the JsFunc.interface()
function, which can
display the interface right within your notebook. If you want to inject into your
custom site, then the entire html can be accessed at jsFunc._repr_html_()
The arguments of toJsFunc
are the argument names for the JS function that
you can use anywhere within the Python clis. Try playing around with the search
box of the “Html output” section.
Clis that take in functions
This also works with clis that are expected to take in a function, like
apply
or filt
:
Source code
jsFunc = range(10) | deref() | (toJsFunc() | apply("x**2"))
jsFunc
Compiled JS function
<JsFunc _jsF_986_1709898278_15>Generated JS function:
//k1_moveOutStart
//k1_moveOutEnd
_jsD_270_1709898278_480 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
async function _jsF_986_1709898278_15() {
_jsF_986_1709898278_14 = (x) => {
return Math.pow(x, 2);
}
_jsD_270_1709898278_477 = {};
_jsF_986_1709898278_13 = (_jsD_270_1709898278_476) => _jsD_270_1709898278_476.apply((_jsD_270_1709898278_478) => _jsF_986_1709898278_14(_jsD_270_1709898278_478), null, _jsD_270_1709898278_477, false)
_jsF_986_1709898278_12 = (_jsD_270_1709898278_475) => { return _jsF_986_1709898278_13(_jsD_270_1709898278_475); };
return _jsF_986_1709898278_12(_jsD_270_1709898278_480);
}
Source code
jsFunc.interface("json")
Html output
More example, this time taking in a js argument:
Source code
jsFunc = range(10) | deref() | (toJsFunc(("divisor", int, 3)) | filt("x % divisor == 1"))
jsFunc
Compiled JS function
<JsFunc _jsF_986_1709898278_20>Generated JS function:
//k1_moveOutStart
//k1_moveOutEnd
_jsD_270_1709898278_486 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
async function _jsF_986_1709898278_20(divisor) {
_jsF_986_1709898278_19 = (x) => {
return ((x%divisor)===1);
}
_jsF_986_1709898278_18 = (_jsD_270_1709898278_483) => _jsD_270_1709898278_483.filt((_jsD_270_1709898278_484) => (_jsF_986_1709898278_19(_jsD_270_1709898278_484)), null)
_jsF_986_1709898278_17 = (_jsD_270_1709898278_482) => { return _jsF_986_1709898278_18(_jsD_270_1709898278_482); };
return _jsF_986_1709898278_17(_jsD_270_1709898278_486);
}
Source code
jsFunc.interface("json")
Html output
It can be crazy complicated, yet still works:
Source code
c = 6 * 2
jsFunc = range(10) | deref() | (toJsFunc() | apply("parseFloat((x//3 + c) ** 4)"))
jsFunc
Compiled JS function
<JsFunc _jsF_986_1709898278_25>Generated JS function:
//k1_moveOutStart
//k1_moveOutEnd
_jsD_270_1709898278_492 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
async function _jsF_986_1709898278_25() {
c = 12;
_jsF_986_1709898278_24 = (x) => {
return parseFloat(Math.pow((Math.floor(x/3)+c), 4));
}
_jsD_270_1709898278_489 = {};
_jsF_986_1709898278_23 = (_jsD_270_1709898278_488) => _jsD_270_1709898278_488.apply((_jsD_270_1709898278_490) => _jsF_986_1709898278_24(_jsD_270_1709898278_490), null, _jsD_270_1709898278_489, false)
_jsF_986_1709898278_22 = (_jsD_270_1709898278_487) => { return _jsF_986_1709898278_23(_jsD_270_1709898278_487); };
return _jsF_986_1709898278_22(_jsD_270_1709898278_492);
}
Source code
jsFunc.interface("json")
Html output
If you noticed, the transpiled JS code for (x//3 + 6) ** 4
is actually
Math.pow((Math.floor(x/3)+6), 4)
. The transpiler understands your code
written in Python, and translates operations like a ** b
into Math.pow(a, b)
automatically. How cool is that! And as demonstrated in the previous example,
you can also use JS variables too (divisor
), instead of just Python variables.
Notice how it also figures out that c
must be a Python variable, so the
transpiler will auto convert that to json and injects it into the JS code. Also
notice how you can freely mix JS and Python code a little bit. For basic operations,
use Python syntax, while for function calling, you can use JS functions.
Lambda functions with lots of variables and op
works too:
Source code
jsFunc = range(10) | deref() | (toJsFunc() | insertIdColumn() | ~apply("lambda x,y: x*y"))
jsFunc
Compiled JS function
<JsFunc _jsF_986_1709898278_31>Generated JS function:
//k1_moveOutStart
//k1_moveOutEnd
_jsD_270_1709898278_499 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
async function _jsF_986_1709898278_31() {
_jsF_986_1709898278_28 = (_jsD_270_1709898278_494) => _jsD_270_1709898278_494.insertIdColumn(false, true)
_jsF_986_1709898278_30 = (x, y) => {
return (x*y);
}
_jsD_270_1709898278_496 = {};
_jsF_986_1709898278_29 = (_jsD_270_1709898278_495) => _jsD_270_1709898278_495.apply((_jsD_270_1709898278_497) => _jsF_986_1709898278_30(..._jsD_270_1709898278_497), null, _jsD_270_1709898278_496, false)
_jsF_986_1709898278_27 = (_jsD_270_1709898278_493) => { return _jsF_986_1709898278_29(_jsF_986_1709898278_28(_jsD_270_1709898278_493)); };
return _jsF_986_1709898278_27(_jsD_270_1709898278_499);
}
Source code
jsFunc.interface("json")
Html output
Source code
jsFunc = range(10) | deref() | (toJsFunc() | apply(op()**2))
jsFunc
Compiled JS function
<JsFunc _jsF_986_1709898278_36>Generated JS function:
//k1_moveOutStart
//k1_moveOutEnd
_jsD_270_1709898278_507 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
async function _jsF_986_1709898278_36() {
_jsF_986_1709898278_35 = (_jsD_270_1709898278_505) => {
return Math.pow(_jsD_270_1709898278_505, 2);
}
_jsD_270_1709898278_503 = {};
_jsF_986_1709898278_34 = (_jsD_270_1709898278_502) => _jsD_270_1709898278_502.apply((_jsD_270_1709898278_504) => _jsF_986_1709898278_35(_jsD_270_1709898278_504), null, _jsD_270_1709898278_503, false)
_jsF_986_1709898278_33 = (_jsD_270_1709898278_501) => { return _jsF_986_1709898278_34(_jsD_270_1709898278_501); };
return _jsF_986_1709898278_33(_jsD_270_1709898278_507);
}
Source code
jsFunc.interface("json")
Html output
Let’s see a more complex example:
Lots of moving parts
Source code
data = repeatF(lambda: random.randint(1000, 9999), 100) | deref()
f1 = grep("${term}") | apply(str) | batched(5, True) | head(10)
f2 = pretty() | join("\n") | aS(fmt.pre)
jsFunc = data | (toJsFunc("term") | f1 | f2)
jsFunc
Compiled JS function
<JsFunc _jsF_986_1709898278_49>Generated JS function:
//k1_moveOutStart
//k1_moveOutEnd
_jsD_270_1709898278_525 = [7176, 9375, 2886, 8557, 6347, 8332, 4699, 8164, 5244, 2331, 2844, 7882, 1435, 6400, 5447, 6510, 6222, 8191, 1054, 3278, 2601, 2223, 6994, 6344, 3927, 5423, 4354, 2345, 8432, 4985, 5396, 2089, 2655, 4466, 8157, 4280, 4832, 8290, 3503, 9649, 6260, 1604, 3402, 1335, 5274, 5978, 6300, 5758, 1185, 8460, 8269, 4013, 8737, 5316, 5174, 4453, 7286, 8934, 8381, 1085, 6130, 5354, 6633, 5377, 6552, 4780, 5342, 4594, 2077, 6336, 3427, 6829, 1200, 4928, 8049, 9518, 3498, 3655, 7352, 6714, 4659, 9324, 3112, 2972, 7992, 2089, 1259, 3589, 2117, 9385, 9981, 2240, 5505, 3427, 8446, 5051, 8869, 8088, 9442, 9355];
async function _jsF_986_1709898278_49(term) {
_jsF_986_1709898278_40 = (_jsD_270_1709898278_511) => _jsD_270_1709898278_511.grep(`${term}`, {col: null, inv: false})
_jsD_270_1709898278_514 = {};
_jsF_986_1709898278_41 = (_jsD_270_1709898278_513) => _jsD_270_1709898278_513.apply((_jsD_270_1709898278_515) => String(_jsD_270_1709898278_515), null, _jsD_270_1709898278_514, false)
_jsF_986_1709898278_42 = (_jsD_270_1709898278_516) => _jsD_270_1709898278_516.batched(5, true)
_jsF_986_1709898278_43 = (_jsD_270_1709898278_517) => _jsD_270_1709898278_517.head(10, false)
_jsF_986_1709898278_39 = (_jsD_270_1709898278_510) => { return _jsF_986_1709898278_43(_jsF_986_1709898278_42(_jsF_986_1709898278_41(_jsF_986_1709898278_40(_jsD_270_1709898278_510)))); };
_jsF_986_1709898278_45 = (_jsD_270_1709898278_520) => _jsD_270_1709898278_520.pretty("", false)
_jsF_986_1709898278_46 = (_jsD_270_1709898278_521) => _jsD_270_1709898278_521.join("\n")
_jsF_986_1709898278_48 = (_jsD_270_1709898278_524) => `<pre style='font-family: monospace'>${_jsD_270_1709898278_524}</pre>`
_jsF_986_1709898278_44 = (_jsD_270_1709898278_519) => { return _jsF_986_1709898278_48(_jsF_986_1709898278_46(_jsF_986_1709898278_45(_jsD_270_1709898278_519))); };
_jsF_986_1709898278_38 = (_jsD_270_1709898278_509) => { return _jsF_986_1709898278_44(_jsF_986_1709898278_39(_jsD_270_1709898278_509)); };
return _jsF_986_1709898278_38(_jsD_270_1709898278_525);
}
Source code
jsFunc.interface("html")
Html output
Just a reminder, you can specify toJsFunc
at any point in
the pipeline. As long as the data you pipe into the toJsFunc
-captured
block can be converted into json, you’re good.
Source code
data = range(10) | deref()
jsFunc = data | (toJsFunc() | filt("x%3 == 0") & filt("x%2 == 0"))
jsFunc
Compiled JS function
<JsFunc _jsF_986_1709898278_57>Generated JS function:
//k1_moveOutStart
//k1_moveOutEnd
_jsD_270_1709898278_534 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
async function _jsF_986_1709898278_57() {
_jsF_986_1709898278_54 = (x) => {
return ((x%3)===0);
}
_jsF_986_1709898278_53 = (_jsD_270_1709898278_528) => _jsD_270_1709898278_528.filt((_jsD_270_1709898278_529) => (_jsF_986_1709898278_54(_jsD_270_1709898278_529)), null)
_jsF_986_1709898278_56 = (x) => {
return ((x%2)===0);
}
_jsF_986_1709898278_55 = (_jsD_270_1709898278_531) => _jsD_270_1709898278_531.filt((_jsD_270_1709898278_532) => (_jsF_986_1709898278_56(_jsD_270_1709898278_532)), null)
_jsF_986_1709898278_52 = (_jsD_270_1709898278_527) => [_jsF_986_1709898278_53(_jsD_270_1709898278_527), _jsF_986_1709898278_55(_jsD_270_1709898278_527)];
_jsF_986_1709898278_51 = (_jsD_270_1709898278_526) => { return _jsF_986_1709898278_52(_jsD_270_1709898278_526); };
return _jsF_986_1709898278_51(_jsD_270_1709898278_534);
}
Source code
jsFunc.interface("json")
Html output
Custom transpiler logic
You can write transpiler logic for any custom class/functions that you desire! Let’s imagine the use case to be writing a function that calculates the factorial sequence, where an initial number is multiplied by increments of itself, and getting the first n elements out of it. Let’s see an example using classes:
Source code
class Factorio(BaseCli): # yes, the spelling is not the math func, but the game. But I love Factorio so
def __init__(self, n):
self.n = n
def __ror__(self, start):
value = start
for i in range(self.n):
yield value
value *= start+i+1
def _jsF(self, meta):
fIdx = f"f_{random.randint(0, 1_000_000)}_{round(time.time())}"
vIdx = f"f_{random.randint(0, 1_000_000)}_{round(time.time())}"
return f"""
const {fIdx} = ({vIdx}) => {{
const ans = []; let value = {vIdx};
for (let i = 0; i < {self.n}; i++) {{
ans.push(value);
value = value * ({vIdx} + i + 1);
}}
return ans;
}};
""", fIdx
2 | Factorio(5) | deref() # returns [2, 6, 24, 120, 720]
jsFunc = 2 | (toJsFunc(("size", int, 3)) | Factorio("size"))
jsFunc
Compiled JS function
<JsFunc _jsF_986_1709898278_60>Generated JS function:
//k1_moveOutStart
//k1_moveOutEnd
_jsD_270_1709898278_537 = 2;
async function _jsF_986_1709898278_60(size) {
const f_340585_1709898291 = (f_124211_1709898291) => {
const ans = []; let value = f_124211_1709898291;
for (let i = 0; i < size; i++) {
ans.push(value);
value = value * (f_124211_1709898291 + i + 1);
}
return ans;
};
_jsF_986_1709898278_59 = (_jsD_270_1709898278_536) => { return f_340585_1709898291(_jsD_270_1709898278_536); };
return _jsF_986_1709898278_59(_jsD_270_1709898278_537);
}
Source code
jsFunc.interface()
Html output
You can also write transpiler functions for any functions you want. Let’s write one for the inverse square root function:
Source code
def inv_sqrt(x, numerator=1):
return numerator/math.sqrt(x)
def _jsF_inv_sqrt(meta, numerator=1):
fIdx = f"f_{random.randint(0, 1_000_000)}_{round(time.time())}"
vIdx = f"f_{random.randint(0, 1_000_000)}_{round(time.time())}"
return f"const {fIdx} = ({vIdx}) => {{ return {numerator}/Math.sqrt({vIdx}); }}", fIdx
settings.cli.kjs.jsF[inv_sqrt] = _jsF_inv_sqrt
inv_sqrt(4) # returns number close to 0.5
jsFunc = range(1, 10) | deref() | (toJsFunc(("someNum", int, 1)) | apply(inv_sqrt, numerator="someNum") | apply(round, ndigits=2))
jsFunc
Compiled JS function
<JsFunc _jsF_986_1709898278_66>Generated JS function:
//k1_moveOutStart
//k1_moveOutEnd
_jsD_270_1709898278_547 = [1, 2, 3, 4, 5, 6, 7, 8, 9];
async function _jsF_986_1709898278_66(someNum) {
const f_53993_1709898291 = (f_751658_1709898291) => { return someNum/Math.sqrt(f_751658_1709898291); }
_jsD_270_1709898278_541 = {"numerator": "someNum"};
_jsF_986_1709898278_63 = (_jsD_270_1709898278_540) => _jsD_270_1709898278_540.apply((_jsD_270_1709898278_542) => f_53993_1709898291(_jsD_270_1709898278_542), null, _jsD_270_1709898278_541, false)
_jsF_986_1709898278_65 = (_jsD_270_1709898278_546) => Math.round((_jsD_270_1709898278_546)*Math.pow(10, 2)+Number.EPSILON)/Math.pow(10, 2)
_jsD_270_1709898278_544 = {"ndigits": 2};
_jsF_986_1709898278_64 = (_jsD_270_1709898278_543) => _jsD_270_1709898278_543.apply((_jsD_270_1709898278_545) => _jsF_986_1709898278_65(_jsD_270_1709898278_545), null, _jsD_270_1709898278_544, false)
_jsF_986_1709898278_62 = (_jsD_270_1709898278_539) => { return _jsF_986_1709898278_64(_jsF_986_1709898278_63(_jsD_270_1709898278_539)); };
return _jsF_986_1709898278_62(_jsD_270_1709898278_547);
}
Source code
jsFunc.interface("json")
Html output
If your function is really simple and exists in JS natively, you can simplify it way down:
Source code
settings.cli.kjs.jsF[math.sqrt] = lambda meta: ("", "Math.sqrt")
range(10) | deref() | (toJsFunc() | apply(math.sqrt) | apply(round, ndigits=2)) | op().interface("json")