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_606_1709898773_10>
Generated JS function:

//k1_moveOutStart

//k1_moveOutEnd

_jsD_292_1709898773_474 = [34, 52, 35, 64, 47, 70, 80, 74, 73, 88, 42, 41, 34, 31, 93, 56, 86, 40, 20, 20];
async function _jsF_606_1709898773_10(term) {
    _jsF_606_1709898773_9 = (_jsD_292_1709898773_472) => _jsD_292_1709898773_472.head(term, false)
    _jsF_606_1709898773_8 = (_jsD_292_1709898773_471) => { return _jsF_606_1709898773_9(_jsD_292_1709898773_471); };
    return _jsF_606_1709898773_8(_jsD_292_1709898773_474);
}

Source code

jsFunc.interface("jsone")

Html output

term

 

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_606_1709898773_15>
Generated JS function:

//k1_moveOutStart

//k1_moveOutEnd

_jsD_292_1709898773_480 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
async function _jsF_606_1709898773_15() {
    
    _jsF_606_1709898773_14 = (x) => {
        return Math.pow(x, 2);
    }
    _jsD_292_1709898773_477 = {};
    _jsF_606_1709898773_13 = (_jsD_292_1709898773_476) => _jsD_292_1709898773_476.apply((_jsD_292_1709898773_478) => _jsF_606_1709898773_14(_jsD_292_1709898773_478), null, _jsD_292_1709898773_477, false)
    _jsF_606_1709898773_12 = (_jsD_292_1709898773_475) => { return _jsF_606_1709898773_13(_jsD_292_1709898773_475); };
    return _jsF_606_1709898773_12(_jsD_292_1709898773_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_606_1709898773_20>
Generated JS function:

//k1_moveOutStart

//k1_moveOutEnd

_jsD_292_1709898773_486 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
async function _jsF_606_1709898773_20(divisor) {
    
    _jsF_606_1709898773_19 = (x) => {
        return ((x%divisor)===1);
    }
    _jsF_606_1709898773_18 = (_jsD_292_1709898773_483) => _jsD_292_1709898773_483.filt((_jsD_292_1709898773_484) => (_jsF_606_1709898773_19(_jsD_292_1709898773_484)), null)
    _jsF_606_1709898773_17 = (_jsD_292_1709898773_482) => { return _jsF_606_1709898773_18(_jsD_292_1709898773_482); };
    return _jsF_606_1709898773_17(_jsD_292_1709898773_486);
}

Source code

jsFunc.interface("json")

Html output

divisor

 

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_606_1709898773_25>
Generated JS function:

//k1_moveOutStart

//k1_moveOutEnd

_jsD_292_1709898773_492 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
async function _jsF_606_1709898773_25() {
    c = 12;
    _jsF_606_1709898773_24 = (x) => {
        return parseFloat(Math.pow((Math.floor(x/3)+c), 4));
    }
    _jsD_292_1709898773_489 = {};
    _jsF_606_1709898773_23 = (_jsD_292_1709898773_488) => _jsD_292_1709898773_488.apply((_jsD_292_1709898773_490) => _jsF_606_1709898773_24(_jsD_292_1709898773_490), null, _jsD_292_1709898773_489, false)
    _jsF_606_1709898773_22 = (_jsD_292_1709898773_487) => { return _jsF_606_1709898773_23(_jsD_292_1709898773_487); };
    return _jsF_606_1709898773_22(_jsD_292_1709898773_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_606_1709898773_31>
Generated JS function:

//k1_moveOutStart

//k1_moveOutEnd

_jsD_292_1709898773_499 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
async function _jsF_606_1709898773_31() {
    _jsF_606_1709898773_28 = (_jsD_292_1709898773_494) => _jsD_292_1709898773_494.insertIdColumn(false, true)
    
    _jsF_606_1709898773_30 = (x, y) => {
        return (x*y);
    }
    _jsD_292_1709898773_496 = {};
    _jsF_606_1709898773_29 = (_jsD_292_1709898773_495) => _jsD_292_1709898773_495.apply((_jsD_292_1709898773_497) => _jsF_606_1709898773_30(..._jsD_292_1709898773_497), null, _jsD_292_1709898773_496, false)
    _jsF_606_1709898773_27 = (_jsD_292_1709898773_493) => { return _jsF_606_1709898773_29(_jsF_606_1709898773_28(_jsD_292_1709898773_493)); };
    return _jsF_606_1709898773_27(_jsD_292_1709898773_499);
}

Source code

jsFunc.interface("json")

Html output


 

Source code

jsFunc = range(10) | deref() | (toJsFunc() | apply(op()**2))
jsFunc

Compiled JS function

<JsFunc _jsF_606_1709898773_36>
Generated JS function:

//k1_moveOutStart

//k1_moveOutEnd

_jsD_292_1709898773_507 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
async function _jsF_606_1709898773_36() {
    
    _jsF_606_1709898773_35 = (_jsD_292_1709898773_505) => {
        return Math.pow(_jsD_292_1709898773_505, 2);
    }
    _jsD_292_1709898773_503 = {};
    _jsF_606_1709898773_34 = (_jsD_292_1709898773_502) => _jsD_292_1709898773_502.apply((_jsD_292_1709898773_504) => _jsF_606_1709898773_35(_jsD_292_1709898773_504), null, _jsD_292_1709898773_503, false)
    _jsF_606_1709898773_33 = (_jsD_292_1709898773_501) => { return _jsF_606_1709898773_34(_jsD_292_1709898773_501); };
    return _jsF_606_1709898773_33(_jsD_292_1709898773_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_606_1709898773_49>
Generated JS function:

//k1_moveOutStart

//k1_moveOutEnd

_jsD_292_1709898773_525 = [9731, 7941, 9908, 4916, 4809, 4657, 3974, 4072, 9857, 3865, 1241, 1901, 9983, 8082, 8683, 2156, 4292, 4496, 7594, 1135, 3561, 3979, 5986, 1490, 2828, 8131, 3937, 5223, 3060, 4438, 1106, 4538, 1138, 9980, 5818, 2887, 7277, 7345, 3353, 5659, 6337, 4546, 8646, 4909, 6078, 2694, 6900, 7451, 4134, 3686, 7060, 9975, 9090, 3917, 2095, 1017, 9628, 5730, 9268, 7724, 7860, 8773, 6711, 7901, 9742, 6848, 2243, 6676, 4191, 1466, 1159, 6393, 5657, 6672, 2338, 3364, 4020, 9716, 4989, 7239, 8996, 5741, 1642, 5941, 5787, 2811, 3395, 3103, 6462, 6177, 1823, 6834, 2585, 4098, 1302, 9867, 4818, 2504, 3560, 3341];
async function _jsF_606_1709898773_49(term) {
    _jsF_606_1709898773_40 = (_jsD_292_1709898773_511) => _jsD_292_1709898773_511.grep(`${term}`, {col: null, inv: false})
    
    _jsD_292_1709898773_514 = {};
    _jsF_606_1709898773_41 = (_jsD_292_1709898773_513) => _jsD_292_1709898773_513.apply((_jsD_292_1709898773_515) => String(_jsD_292_1709898773_515), null, _jsD_292_1709898773_514, false)
    _jsF_606_1709898773_42 = (_jsD_292_1709898773_516) => _jsD_292_1709898773_516.batched(5, true)
    _jsF_606_1709898773_43 = (_jsD_292_1709898773_517) => _jsD_292_1709898773_517.head(10, false)
    _jsF_606_1709898773_39 = (_jsD_292_1709898773_510) => { return _jsF_606_1709898773_43(_jsF_606_1709898773_42(_jsF_606_1709898773_41(_jsF_606_1709898773_40(_jsD_292_1709898773_510)))); };
    _jsF_606_1709898773_45 = (_jsD_292_1709898773_520) => _jsD_292_1709898773_520.pretty("", false)
    _jsF_606_1709898773_46 = (_jsD_292_1709898773_521) => _jsD_292_1709898773_521.join("\n")
    _jsF_606_1709898773_48 = (_jsD_292_1709898773_524) => `<pre style='font-family: monospace'>${_jsD_292_1709898773_524}</pre>`
    _jsF_606_1709898773_44 = (_jsD_292_1709898773_519) => { return _jsF_606_1709898773_48(_jsF_606_1709898773_46(_jsF_606_1709898773_45(_jsD_292_1709898773_519))); };
    _jsF_606_1709898773_38 = (_jsD_292_1709898773_509) => { return _jsF_606_1709898773_44(_jsF_606_1709898773_39(_jsD_292_1709898773_509)); };
    return _jsF_606_1709898773_38(_jsD_292_1709898773_525);
}

Source code

jsFunc.interface("html")

Html output

term

 

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_606_1709898773_57>
Generated JS function:

//k1_moveOutStart

//k1_moveOutEnd

_jsD_292_1709898773_534 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
async function _jsF_606_1709898773_57() {
    
    _jsF_606_1709898773_54 = (x) => {
        return ((x%3)===0);
    }
    _jsF_606_1709898773_53 = (_jsD_292_1709898773_528) => _jsD_292_1709898773_528.filt((_jsD_292_1709898773_529) => (_jsF_606_1709898773_54(_jsD_292_1709898773_529)), null)
    
    _jsF_606_1709898773_56 = (x) => {
        return ((x%2)===0);
    }
    _jsF_606_1709898773_55 = (_jsD_292_1709898773_531) => _jsD_292_1709898773_531.filt((_jsD_292_1709898773_532) => (_jsF_606_1709898773_56(_jsD_292_1709898773_532)), null)
    _jsF_606_1709898773_52 = (_jsD_292_1709898773_527) => [_jsF_606_1709898773_53(_jsD_292_1709898773_527), _jsF_606_1709898773_55(_jsD_292_1709898773_527)];
    _jsF_606_1709898773_51 = (_jsD_292_1709898773_526) => { return _jsF_606_1709898773_52(_jsD_292_1709898773_526); };
    return _jsF_606_1709898773_51(_jsD_292_1709898773_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_606_1709898773_60>
Generated JS function:

//k1_moveOutStart

//k1_moveOutEnd

_jsD_292_1709898773_537 = 2;
async function _jsF_606_1709898773_60(size) {
    
      const f_255653_1709898786 = (f_504563_1709898786) => {
          const ans = []; let value = f_504563_1709898786;
          for (let i = 0; i < size; i++) {
              ans.push(value);
              value = value * (f_504563_1709898786 + i + 1);
          }
          return ans;
      };
            
    _jsF_606_1709898773_59 = (_jsD_292_1709898773_536) => { return f_255653_1709898786(_jsD_292_1709898773_536); };
    return _jsF_606_1709898773_59(_jsD_292_1709898773_537);
}

Source code

jsFunc.interface()

Html output

size

 

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_606_1709898773_66>
Generated JS function:

//k1_moveOutStart

//k1_moveOutEnd

_jsD_292_1709898773_547 = [1, 2, 3, 4, 5, 6, 7, 8, 9];
async function _jsF_606_1709898773_66(someNum) {
    const f_980472_1709898786 = (f_870931_1709898786) => { return someNum/Math.sqrt(f_870931_1709898786); }
    _jsD_292_1709898773_541 = {"numerator": "someNum"};
    _jsF_606_1709898773_63 = (_jsD_292_1709898773_540) => _jsD_292_1709898773_540.apply((_jsD_292_1709898773_542) => f_980472_1709898786(_jsD_292_1709898773_542), null, _jsD_292_1709898773_541, false)
    _jsF_606_1709898773_65 = (_jsD_292_1709898773_546) => Math.round((_jsD_292_1709898773_546)*Math.pow(10, 2)+Number.EPSILON)/Math.pow(10, 2)
    _jsD_292_1709898773_544 = {"ndigits": 2};
    _jsF_606_1709898773_64 = (_jsD_292_1709898773_543) => _jsD_292_1709898773_543.apply((_jsD_292_1709898773_545) => _jsF_606_1709898773_65(_jsD_292_1709898773_545), null, _jsD_292_1709898773_544, false)
    _jsF_606_1709898773_62 = (_jsD_292_1709898773_539) => { return _jsF_606_1709898773_64(_jsF_606_1709898773_63(_jsD_292_1709898773_539)); };
    return _jsF_606_1709898773_62(_jsD_292_1709898773_547);
}

Source code

jsFunc.interface("json")

Html output

someNum

 

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")

Html output