# AUTOGENERATED FILE! PLEASE DON'T EDIT HERE. EDIT THE SOURCE NOTEBOOKS INSTEAD
"""Small module with helper functions to analyze and manipulate Python abstract syntax tree"""
import k1lib, math, numpy as np, random, base64, json, time, ast; from typing import List, Iterator
from collections import defaultdict, deque; import k1lib.cli as cli
__all__ = ["walk", "pretty", "py2js", "asyncGuard"]
class JSTranspileError(Exception): pass                                          # JSTranspileError
def _walk(m, parent, accessor="", depth=0): # yields (elem, parent, parent accessor, depth). This function don't need to recurse leaf nodes, which is a pretty good time saver # _walk
    yield m, parent, accessor, depth                                             # _walk
    if isinstance(m, (ast.Module, ast.ClassDef, ast.FunctionDef)):               # _walk
        for expr in m.body: yield from _walk(expr, m, f".body[{len(m.body)}]", depth+1) # _walk
    elif isinstance(m, ast.Expr):                                                # _walk
        yield from _walk(m.value, m, ".value", depth+1)                          # _walk
    elif isinstance(m, ast.Lambda):                                              # _walk
        yield from _walk(m.body, m, ".body", depth+1)                            # _walk
    elif isinstance(m, ast.BinOp):                                               # _walk
        yield from _walk(m.left, m, ".left", depth+1)                            # _walk
        yield from _walk(m.op, m, ".op", depth+1)                                # _walk
        yield from _walk(m.right, m, ".right", depth+1)                          # _walk
    elif isinstance(m, ast.Assign):                                              # _walk
        yield from _walk(m.value, m, ".value", depth+1)                          # _walk
        for e in m.targets: yield from _walk(e, m, f".targets[{len(m.targets)}]", depth+1) # _walk
    elif isinstance(m, ast.If):                                                  # _walk
        yield from _walk(m.test, m, ".test", depth+1)                            # _walk
        for e in m.body: yield from _walk(e, m, f".body[{len(m.body)}]", depth+1) # _walk
        for e in m.orelse: yield from _walk(e, m, f".orelse[{len(m.orelse)}]", depth+1) # _walk
    elif isinstance(m, ast.Call):                                                # _walk
        yield from _walk(m.func, m, ".func", depth+1)                            # _walk
        for e in m.args: yield from _walk(e, m, f".args[{len(m.args)}]", depth+1) # _walk
        for e in m.keywords: yield from _walk(e, m, f".keywords[{len(m.keywords)}]", depth+1) # _walk
    elif isinstance(m, ast.Attribute):                                           # _walk
        yield from _walk(m.value, m, ".value", depth+1)                          # _walk
    elif isinstance(m, ast.UnaryOp):                                             # _walk
        yield from _walk(m.op, m, ".op", depth+1)                                # _walk
        yield from _walk(m.operand, m, ".operand", depth+1)                      # _walk
    elif isinstance(m, ast.Subscript):                                           # _walk
        yield from _walk(m.value, m, ".value", depth+1)                          # _walk
        yield from _walk(m.slice, m, ".slice", depth+1)                          # _walk
        yield from _walk(m.ctx, m, ".ctx", depth+1)                              # _walk
    # elif isinstance(m, ast.Store):                                             # _walk
    #     yield from _walk(m.parent, m, ".parent", depth+1)                      # _walk
    elif isinstance(m, ast.Compare):                                             # _walk
        yield from _walk(m.left, m, ".left", depth+1)                            # _walk
        for e in m.ops: yield from _walk(e, m, f".ops[{len(m.ops)}]", depth+1)   # _walk
        for e in m.comparators: yield from _walk(e, m, f".comparators[{len(m.comparators)}]", depth+1) # _walk
    elif isinstance(m, ast.List):                                                # _walk
        for e in m.elts: yield from _walk(e, m, f".elts[{len(m.elts)}]", depth+1) # _walk
    elif isinstance(m, ast.Tuple):                                               # _walk
        for e in m.elts: yield from _walk(e, m, f".elts[{len(m.elts)}]", depth+1) # _walk
    elif isinstance(m, ast.Set):                                                 # _walk
        for e in m.elts: yield from _walk(e, m, f".elts[{len(m.elts)}]", depth+1) # _walk
    elif isinstance(m, ast.comprehension):                                       # _walk
        yield from _walk(m.is_async, m, ".is_async", depth+1)                    # _walk
        yield from _walk(m.iter, m, ".iter", depth+1)                            # _walk
        yield from _walk(m.target, m, ".target", depth+1)                        # _walk
        for e in m.ifs: yield from _walk(e, m, f".ifs[{len(m.ifs)}]", depth+1)   # _walk
    elif isinstance(m, (ast.ListComp, ast.GeneratorExp, ast.SetComp)):           # _walk
        yield from _walk(m.elt, m, ".elt", depth+1)                              # _walk
        for e in m.generators: yield from _walk(e, m, f".generators[{len(m.generators)}]", depth+1) # _walk
    elif isinstance(m, ast.DictComp):                                            # _walk
        yield from _walk(m.key, m, ".key", depth+1)                              # _walk
        yield from _walk(m.value, m, ".value", depth+1)                          # _walk
        for e in m.generators: yield from _walk(e, m, f".generators[{len(m.generators)}]", depth+1) # _walk
    elif isinstance(m, ast.Starred):                                             # _walk
        yield from _walk(m.ctx, m, ".ctx", depth+1)                              # _walk
        yield from _walk(m.value, m, ".value", depth+1)                          # _walk
    elif isinstance(m, ast.keyword):                                             # _walk
        yield from _walk(m.value, m, ".value", depth+1)                          # _walk
    elif isinstance(m, ast.Dict):                                                # _walk
        for e in m.keys: yield from _walk(e, m, f".keys[{len(m.keys)}]", depth+1) # _walk
        for e in m.values: yield from _walk(e, m, f".values[{len(m.values)}]", depth+1) # _walk
    elif isinstance(m, ast.IfExp):                                               # _walk
        yield from _walk(m.body, m, ".body", depth+1)                            # _walk
        yield from _walk(m.test, m, ".test", depth+1)                            # _walk
        yield from _walk(m.orelse, m, ".orelse", depth+1)                        # _walk
    elif isinstance(m, ast.Slice):                                               # _walk
        yield from _walk(m.lower, m, ".lower", depth+1)                          # _walk
        yield from _walk(m.upper, m, ".upper", depth+1)                          # _walk
        yield from _walk(m.step, m, ".step", depth+1)                            # _walk
    elif isinstance(m, ast.FormattedValue):                                      # _walk
        yield from _walk(m.value, m, ".value", depth+1)                          # _walk
    elif isinstance(m, ast.JoinedStr):                                           # _walk
        for e in m.values: yield from _walk(e, m, f".values[{len(m.values)}]", depth+1) # _walk
class walk(cli.BaseCli):                                                         # walk
    def __init__(self):                                                          # walk
        """Walks the AST tree, returning (elem, parent, parent accessor, depth) tuple.
Example::

    "abc.py" | cat() | join("\n") | kast.walk() # returns generator of tuples
"""                                                                              # walk
        pass                                                                     # walk
    def __ror__(self, m:str):                                                    # walk
        if isinstance(m, str): m = ast.parse(m)                                  # walk
        return _walk(m, None, "", 0)                                             # walk
class pretty(cli.BaseCli):                                                       # pretty
    def __init__(self):                                                          # pretty
        """Returns list[str] containing the prettified tree structure of the AST. Mainly
for asthetic, not for parsing by other tools downstream. Example::

    "x+3" | kast.pretty()
    "lambda x: x+3" | kast.pretty()
"""                                                                              # pretty
        pass                                                                     # pretty
    def __ror__(self, it:str):                                                   # pretty
        m = it; res = []                                                         # pretty
        if isinstance(m, str): m = ast.parse(m)                                  # pretty
        for m, parent, accessor, depth in _walk(m, None, "", 0):                 # pretty
            pad = "  "*depth; res.append(f"{pad}{accessor} {m}")                 # pretty
            if isinstance(m, ast.arguments): res[-1] += f" {[a.arg for a in m.args]}" # pretty
            elif isinstance(m, ast.Import): res[-1] += f" .names[{len(m.names)}].name={', '.join([e.name for e in m.names])}" # pretty
            elif isinstance(m, ast.ImportFrom): res[-1] += f" .module={repr(m.module)} .names[{len(m.names)}].name={', '.join([e.name for e in m.names])}" # pretty
            elif isinstance(m, ast.BinOp): res[-1] += f" .op={m.op}"             # pretty
            elif isinstance(m, (ast.ClassDef, ast.FunctionDef)): res[-1] += f" .name={repr(m.name)}" # pretty
            elif isinstance(m, ast.Name): res[-1] += f" .id={repr(m.id)}"        # pretty
            elif isinstance(m, ast.Constant): res[-1] += f" .value={repr(m.value)}" # pretty
            elif isinstance(m, ast.Attribute): res[-1] += f" .attr={repr(m.attr)}" # pretty
            elif isinstance(m, ast.keyword): res[-1] += f" .arg={repr(m.arg)}"   # pretty
        return res                                                               # pretty
jstCallDict = {}                                                                 # pretty
def _jstGuardNArgs(name, ns, f): # common guard across all short and simple functions # _jstGuardNArgs
    ns = set(ns)                                                                 # _jstGuardNArgs
    if len(ns) == 0: jstCallDict[name] = f                                       # _jstGuardNArgs
    else:                                                                        # _jstGuardNArgs
        def inner(m, lcs):                                                       # _jstGuardNArgs
            if len(m.args) not in ns: raise JSTranspileError(f"Function `{name}` only support these number of arguments: {ns}. You have {len(m.args)} arguments total!") # _jstGuardNArgs
            return f(m, lcs)                                                     # _jstGuardNArgs
        jstCallDict[name] = inner                                                # _jstGuardNArgs
_jstGuardNArgs("abs", [1], lambda m, lcs: f"Math.abs({_py2js(m.args[0], lcs)})") # _jstGuardNArgs
_jstGuardNArgs("all", [1], lambda m, lcs: f"{_py2js(m.args[0], lcs)}.every((x) => x)") # _jstGuardNArgs
_jstGuardNArgs("any", [1], lambda m, lcs: f"{_py2js(m.args[0], lcs)}.some((x) => x)") # _jstGuardNArgs
def _jst_divmod(m, lcs):                                                         # _jst_divmod
    if len(m.args) != 2: raise JSTranspileError(f"Only support divmod() with 2 arguments!") # _jst_divmod
    a = _py2js(m.args[0], lcs); b = _py2js(m.args[1], lcs)                       # _jst_divmod
    return f"[Math.floor(a/b), a-b*Math.floor(a/b)]"                             # _jst_divmod
jstCallDict["divmod"] = _jst_divmod                                              # _jst_divmod
def _jst_enumerate(m, lcs):                                                      # _jst_enumerate
    if len(m.args) != 1: raise JSTranspileError("Currently don't support enumerate() with the start parameter") # _jst_enumerate
    return f"{_py2js(m.args[0], lcs)}.map((x, i) => [x, i])"                     # _jst_enumerate
jstCallDict["enumerate"] = _jst_enumerate                                        # _jst_enumerate
_jstGuardNArgs("float", [1], lambda m, lcs: f"parseFloat({_py2js(m.args[0], lcs)})") # _jst_enumerate
def _jst_getattr(m, lcs):                                                        # _jst_getattr
    a = _py2js(m.args[0], lcs); b = _py2js(m.args[1], lcs)                       # _jst_getattr
    if len(m.args) == 2: return f"{a}[{b}]"                                      # _jst_getattr
    else: return f"{a}[{b}] ?? {_py2js(m.args[2], lcs)}"                         # _jst_getattr
jstCallDict["getattr"] = _jst_getattr                                            # _jst_getattr
_jstGuardNArgs("hasattr", [2], lambda m, lcs: f"{_py2js(m.args[0], lcs)}[{_py2js(m.args[1], lcs)}] !== undefined") # _jst_getattr
_jstGuardNArgs("int", [1], lambda m, lcs: f"parseInt({_py2js(m.args[0], lcs)})") # _jst_getattr
_jstGuardNArgs("iter", [1], lambda m, lcs: f"Array.from({_py2js(m.args[0], lcs)})") # _jst_getattr
_jstGuardNArgs("len", [1], lambda m, lcs: f"{_py2js(m.args[0], lcs)}.length")    # _jst_getattr
_jstGuardNArgs("list", [1], lambda m, lcs: f"Array.from({_py2js(m.args[0], lcs)})") # _jst_getattr
def _jst_max(m, lcs):                                                            # _jst_max
    if len(m.args) == 0: raise JSTranspileError("Only support max() with 1 or more parameters") # _jst_max
    if len(m.args) == 1: return f"Math.max(...({_py2js(m.args[0], lcs)}))"       # _jst_max
    return f"Math.max({', '.join([_py2js(e, lcs) for e in m.args])})"            # _jst_max
jstCallDict["max"] = _jst_max                                                    # _jst_max
def _jst_min(m, lcs):                                                            # _jst_min
    if len(m.args) == 0: raise JSTranspileError("Only support min() with 1 or more parameters") # _jst_min
    if len(m.args) == 1: return f"Math.min(...({_py2js(m.args[0], lcs)}))"       # _jst_min
    return f"Math.min({', '.join([_py2js(e, lcs) for e in m.args])})"            # _jst_min
jstCallDict["min"] = _jst_min                                                    # _jst_min
_jstGuardNArgs("pow", [2], lambda m, lcs: f"Math.pow({_py2js(m.args[0], lcs)}, {_py2js(m.args[1], lcs)})") # _jst_min
_jstGuardNArgs("print", [], lambda m, lcs: f"console.log({', '.join([_py2js(e, lcs) for e in m.args])})") # _jst_min
def _jst_range(m, lcs):                                                          # _jst_range
    n = len(m.args)                                                              # _jst_range
    if n == 1: start = None; stop = m.args[0]                                    # _jst_range
    elif n == 2: start, stop = m.args                                            # _jst_range
    else: raise JSTranspileError("Only support range() without the step variable") # _jst_range
    ans = f"[...Array({_py2js(stop, lcs)}).keys()]"                              # _jst_range
    if start is not None: ans = f"{ans}.slice({_py2js(start, lcs)})"             # _jst_range
    return ans                                                                   # _jst_range
jstCallDict["range"] = _jst_range                                                # _jst_range
_jstGuardNArgs("reversed", [1], lambda m, lcs: f"({_py2js(m.args[0], lcs)}).toReversed()") # _jst_range
def _jst_round(m, lcs):                                                          # _jst_round
    if len(m.args) > 2 or len(m.args) <= 0: raise JSTranspileError("Only support round() with 1 or 2 arguments!") # _jst_round
    if len(m.args) == 1: return f"Math.round({_py2js(m.args[0], lcs)})"          # _jst_round
    else: b = _py2js(m.args[1], lcs); return f"Math.round(({_py2js(m.args[0], lcs)})*Math.pow(10, {b})+Number.EPSILON)/Math.pow(10, {b})" # _jst_round
jstCallDict["round"] = _jst_round                                                # _jst_round
_jstGuardNArgs("set", [1], lambda m, lcs: f"new Set({_py2js(m.args[0], lcs)})")  # _jst_round
_jstGuardNArgs("setattr", [3], lambda m, lcs: f"({_py2js(m.args[0], lcs)}[{_py2js(m.args[1], lcs)}] = {_py2js(m.args[2], lcs)})") # _jst_round
# sorted, ignored cause user can just use sort() cli instead! Python's sort() has weird args # _jst_round
_jstGuardNArgs("str", [1], lambda m, lcs: f"({_py2js(m.args[0], lcs)}).toString()") # _jst_round
_jstGuardNArgs("sum", [1], lambda m, lcs: f"({_py2js(m.args[0], lcs)}).reduce((partialSum, a) => partialSum + a, 0)") # _jst_round
_jstGuardNArgs("tuple", [1], lambda m, lcs: f"Array.from({_py2js(m.args[0], lcs)})") # _jst_round
_jstGuardNArgs("type", [1], lambda m, lcs: f"typeof({_py2js(m.args[0], lcs)})")  # _jst_round
_jstGuardNArgs("zip", [], lambda m, lcs: f"[{', '.join([_py2js(e, lcs) for e in m.args])}].transpose()") # _jst_round
def flattenAttribute(x) -> str:                                                  # flattenAttribute
    if isinstance(x, ast.Name): return x.id                                      # flattenAttribute
    elif isinstance(x, ast.Attribute): return f"{flattenAttribute(x.value)}.{x.attr}" # flattenAttribute
    else: raise JSTranspileError("kast.flattenAttribute() only supports attributes that only contains ast.Attribute and ast.Name") # flattenAttribute
def pyLambParse(s:str) -> str: # parses code, get rid of "lambda: " in front if exists # pyLambParse
    # s might contain stringified                                                # pyLambParse
    m = ast.parse(s)                                                             # pyLambParse
    if len(m.body) == 0: raise JSTranspileError(f"No expression inside passed in code `{s}`") # pyLambParse
    if len(m.body) > 1: raise JSTranspileError(f"py2js() function can only work with relatively simple operations. The code passed in is too complicated: `{s}`") # pyLambParse
    expr = m.body[0].value                                                       # pyLambParse
    if isinstance(expr, ast.Lambda): expr = expr.body                            # pyLambParse
    return expr                                                                  # pyLambParse
def py2js(m):                                                                    # py2js
    """Converts simple python expression into js code.
Example::

    kast.py2js("x+3")                 # returns ('(x+3)', {'x'})
    kast.py2js("lambda x: x+3")       # returns ('(x+3)', {'x'})
    kast.py2js("lambda y: y+3")       # returns ('(y+3)', {'y'})
    kast.py2js("lambda y: y+3*x")     # returns ('(y+(3*x))', {'x', 'y'})
    kast.py2js("x<3 and 45>10")       # returns ('((x<3)&&(45>10))', {'x'})
    kast.py2js("abs((x//5.1) * 2.3)") # returns ('Math.abs((Math.floor(x/5.1)*2.3))', {'x'})
    kast.py2js("not x > 3")           # returns ('!(x>3)', {'x'})

This will return a tuple of 2 objects. First is the JS code, and second is the
set of unknown variables in the JS code. If the code is a lambda function,
then it only considers what's inside the function. More complex examples::

    # returns ('[...Array(10).keys()].map((x, i) => [x, i]).filter(([x, y]) => ((x%3)===1)).map(([x, y]) => (Math.pow(x, y)+z))', {'z'})
    kast.py2js("[x**y + z for x, y in enumerate(range(10)) if x % 3 == 1]")

Both JS and Python code when run should return [1, 256, 823543] for z = 0.

A lot of builtin Python functions are transpiled automatically, including::

    abs, all, any, divmod, enumerate, float, getattr, hasattr, int, iter
    len, list, max, min, pow, print, range, reversed, round, set, setattr
    str, sum, tuple, type, zip

Other builtin operations don't quite make sense though. But the neat thing
is, any functions beyond these builtin functions are not transpiled, and
instead just passes along to the JS code. This allows you to kinda mix and
match code styles from both Python and JS. For example::

    # returns ("(document.querySelector(('#' + someIdx + '_tail')).style['color'] = (((Math.round(Math.pow(x, 2))===Math.abs(-12))) ? ('red') : ('green')))", {'someIdx', 'x'})
    kast.py2js("setattr(document.querySelector(f'#{someIdx}_tail').style, 'color', 'red' if round(x**2) == Math.abs(-12) else 'green')")

See how we're both using the Python transpiled ``**`` (to ``Math.pow(...)``),
yet uses ``Math.abs(...)`` directly, which is not defined in Python, but is
defined in JS, without any trouble. Also notice that ``document.querySelector``
and f-strings work too. So in short, overall syntax is Python's, but you can
call random JS functions!

This can do lots of transformations, but don't expect it to be airtight. I've
done all I can to make the thing airtight, but I'm sure you can come up with
some complex scenario where it would fail. This is meant to be applied on
short pieces of code only."""                                                    # py2js
    lcs = set(); ans = _py2js(m, lcs); return ans, lcs                           # py2js
def _listCompTarget(m, lcs): # extracts target of list comprehensions, like [... for x, [y, z] in ...] # _listCompTarget
    if isinstance(m, ast.Name): return _py2js(m, lcs)                            # _listCompTarget
    if isinstance(m, (ast.Tuple, ast.List)): s = ", ".join([_listCompTarget(e, lcs) for e in m.elts]); return f"[{s}]" # _listCompTarget
    raise JSTranspileError(f"While trying to expand target assignment in list/set/dict comprehensions, encountered unknown type: {type(m)}") # _listCompTarget
def _py2js(m, lcs:"list[str]"): # lcs for list of variable names, only used in ast.Name # _py2js
    if isinstance(m, str): m = pyLambParse(m)                                    # _py2js
    if isinstance(m, ast.BinOp):                                                 # _py2js
        left = _py2js(m.left, lcs); right = _py2js(m.right, lcs)                 # _py2js
        if isinstance(m.op, ast.FloorDiv): return f"Math.floor({left}/{right})" # TODO: might be weird dealing with negative floors # _py2js
        if isinstance(m.op, ast.Pow): return f"Math.pow({left}, {right})"        # _py2js
        return f"({left}{_py2js(m.op, lcs)}{right})"                             # _py2js
    if isinstance(m, ast.Compare): # only supports a<b or a<b<c, dont support more elems, like a<b<c<d # _py2js
        nops = len(m.ops); ncomps = len(m.comparators)                           # _py2js
        if nops != ncomps: raise JSTranspileError(f"Number of operations {nops} differed from number of comparators {ncomps}") # _py2js
        if nops == 0: raise JSTranspileError("Number of operations can't be zero") # _py2js
        if nops > 2: raise JSTranspileError("Don't support Compares with more than 2 comparisons. Change `a<b<c<d` into `(a<b<c) and (c<d)`") # _py2js
        left = _py2js(m.left, lcs); comps = [_py2js(e, lcs) for e in m.comparators]; ops = [_py2js(o, lcs) for o in m.ops] # _py2js
        if nops == 1: return f"({left}{ops[0]}{comps[0]})"                       # _py2js
        if nops == 2: return f"({left}{ops[0]}{comps[0]}) && ({comps[0]}{ops[1]}{comps[1]})" # _py2js
        return m                                                                 # _py2js
    if isinstance(m, ast.BoolOp): return f"({_py2js(m.op, lcs).join([_py2js(v, lcs) for v in m.values])})" # _py2js
    if isinstance(m, ast.UnaryOp):                                               # _py2js
        if isinstance(m.op, ast.Not): return f"!{_py2js(m.operand, lcs)}"        # _py2js
        if isinstance(m.op, ast.Invert): raise JSTranspileError(f"No notion of inversion '~' exists in JS") # _py2js
        if isinstance(m.op, ast.USub): return f"-{_py2js(m.operand, lcs)}"       # _py2js
        if isinstance(m.op, ast.UAdd): return f"{_py2js(m.operand, lcs)}"        # _py2js
        raise JSTranspileError(f"kast.py2js() doesn't know how to parse UnaryOp `{m.op}`") # _py2js
    if isinstance(m, ast.Name): lcs.add(m.id); return f"{m.id}"                  # _py2js
    if isinstance(m, ast.Constant):                                              # _py2js
        if m.value is None: return f"null"                                       # _py2js
        if isinstance(m.value, str): s = m.value.replace("'", "\\'"); return f"'{s}'" # _py2js
        return f"{m.value}"                                                      # _py2js
    if isinstance(m, ast.Add): return f"+"                                       # _py2js
    if isinstance(m, ast.Sub): return f"-"                                       # _py2js
    if isinstance(m, ast.Mult): return f"*"                                      # _py2js
    if isinstance(m, ast.MatMult): raise JSTranspileError("No notion of matrix multiplication '@' exists in JS") # _py2js
    if isinstance(m, ast.Div): return f"/"                                       # _py2js
    if isinstance(m, ast.Mod): return f"%"                                       # _py2js
    if isinstance(m, ast.Pow): return f"^"                                       # _py2js
    if isinstance(m, ast.Eq): return f"==="                                      # _py2js
    if isinstance(m, ast.NotEq): return f"!=="                                   # _py2js
    if isinstance(m, ast.Gt): return f">"                                        # _py2js
    if isinstance(m, ast.GtE): return f">="                                      # _py2js
    if isinstance(m, ast.Lt): return f"<"                                        # _py2js
    if isinstance(m, ast.LtE): return f"<="                                      # _py2js
    if isinstance(m, ast.LShift): return "<<"                                    # _py2js
    if isinstance(m, ast.RShift): return ">>"                                    # _py2js
    if isinstance(m, ast.And): return "&&"                                       # _py2js
    if isinstance(m, ast.Or): return "||"                                        # _py2js
    if isinstance(m, ast.Attribute): return f"{_py2js(m.value, lcs)}.{m.attr}"   # _py2js
    if isinstance(m, ast.Call):                                                  # _py2js
        if len(m.keywords) > 0: raise JSTranspileError(f"keyword arguments don't exist in JS, please use normal arguments in your function calls only, at `{m}`") # _py2js
        funcName = flattenAttribute(m.func)                                      # _py2js
        if funcName in jstCallDict: return jstCallDict[funcName](m, lcs)         # _py2js
        return f"{funcName}({', '.join([_py2js(a, lcs) for a in m.args])})"      # _py2js
    if isinstance(m, (ast.List, ast.Tuple)): return f"[{', '.join([_py2js(e, lcs) for e in m.elts])}]" # _py2js
    if isinstance(m, ast.Set): return f"new Set([{', '.join([_py2js(e, lcs) for e in m.elts])}])" # _py2js
    if isinstance(m, ast.Dict):                                                  # _py2js
        keys = [f"{_py2js(k, lcs)}" if isinstance(k, ast.Constant) else (f"[{_py2js(k, lcs)}]" if k is not None else None) for k in m.keys] # _py2js
        values = [f"{_py2js(v, lcs)}" for v in m.values]                         # _py2js
        a = [f"{k}: {v}" for k, v in zip(keys, values) if k is not None]         # _py2js
        b = [f"...{v}" for k, v in zip(keys, values) if k is None]               # _py2js
        return "{" + ", ".join([*a, *b]) + "}"                                   # _py2js
    if isinstance(m, ast.Starred): return f"...{_py2js(m.value, lcs)}"           # _py2js
    if isinstance(m, ast.IfExp): return f"(({_py2js(m.test, lcs)}) ? ({_py2js(m.body, lcs)}) : ({_py2js(m.orelse, lcs)}))" # _py2js
    if isinstance(m, ast.Subscript):                                             # _py2js
        tail = ""; slices = m.slice.elts if isinstance(m.slice, ast.Tuple) else [m.slice] # _py2js
        for s in reversed(slices):                                               # _py2js
            if isinstance(s, ast.Slice):                                         # _py2js
                if s.step is not None: pr = '\n'.join(m | pretty()); raise JSTranspileError(f"JS does not support stepped slices! This can be augmented on my side later on though. kast.pretty() on the element:\n\n{pr}") # _py2js
                l = 'undefined' if s.lower is None else _py2js(s.lower, lcs)     # _py2js
                u = 'undefined' if s.upper is None else _py2js(s.upper, lcs)     # _py2js
                if tail: dataIdx = k1lib.cli.init._jsDAuto(); tail = f".slice({l}, {u}).map(({dataIdx}) => {dataIdx}{tail})" # _py2js
                else: tail = f".slice({l}, {u})" # short path                    # _py2js
            else: tail = f".at({_py2js(s, lcs)}){tail}"                          # _py2js
        return f"{_py2js(m.value, lcs)}{tail}"                                   # _py2js
    if isinstance(m, (ast.ListComp, ast.GeneratorExp, ast.SetComp)):             # _py2js
        if len(m.generators) != 1: raise JSTranspileError("Currently only supports 1 generator in list/set/generator comprehensions") # _py2js
        g = m.generators[0]; targetLcs = set(); oldLcs = set(lcs)                # _py2js
        if g.is_async: raise JSTranspileError("Does not support async functions yet") # _py2js
        ans = _py2js(g.iter, lcs); target = _listCompTarget(g.target, targetLcs) # _py2js
        for if_ in g.ifs: ans = f"{ans}.filter(({target}) => {_py2js(if_, lcs)})" # _py2js
        ans = f"{ans}.map(({target}) => {_py2js(m.elt, lcs)})"                   # _py2js
        for x in list(lcs): # try removing unknown variables in the .elt expression that appears in the target, cause those are not unknown variables! # _py2js
            if x not in oldLcs and x in targetLcs: lcs.remove(x)                 # _py2js
        return f"new Set({ans})" if isinstance(m, ast.SetComp) else ans          # _py2js
    if isinstance(m, ast.DictComp):                                              # _py2js
        if len(m.generators) != 1: raise JSTranspileError("Currently only supports 1 generator in dict comprehensions") # _py2js
        g = m.generators[0]; targetLcs = set(); oldLcs = set(lcs)                # _py2js
        if g.is_async: raise JSTranspileError("Does not support async functions yet") # _py2js
        ans = _py2js(g.iter, lcs); target = _listCompTarget(g.target, targetLcs) # _py2js
        for if_ in g.ifs: ans = f"{ans}.filter(({target}) => {_py2js(if_, lcs)})" # _py2js
        ans = f"{ans}.map(({target}) => [{_py2js(m.key, lcs)}, {_py2js(m.value, lcs)}]).toDict()" # _py2js
        for x in list(lcs): # try removing unknown variables in the .elt expression that appears in the target, cause those are not unknown variables! # _py2js
            if x not in oldLcs and x in targetLcs: lcs.remove(x)                 # _py2js
        return ans                                                               # _py2js
    if isinstance(m, ast.FormattedValue): return _py2js(m.value, lcs)            # _py2js
    if isinstance(m, ast.JoinedStr): return f"({' + '.join([_py2js(e, lcs) for e in m.values])})" # _py2js
    raise JSTranspileError(f"kast.py2js() doesn't know how to parse {type(m)}")  # _py2js
def prepareFunc(fn:"str|cli.op", extraVars:"list[str]"=None, checks=True):       # prepareFunc
    """for toJsFunc() transpilation subsystem.
Returns (transpiled function, variable declarations, argument variables).
If can't understand the object passed in, return None

:param fn: assumes this is a cli.op(), a lambda function, or an expression inside a lambda function
:param extraVars: available variables that can be resolved from js
    function. Can come from arguments, or from js function declaration,
    or other places. Specify this to shut up the variable resolver checks"""     # prepareFunc
    g = cli.init._k1_global_frame(); avaiVals = dict(); reqVals = set(); extraVars = extraVars or [] # prepareFunc
    if isinstance(fn, cli.op):                                                   # prepareFunc
        fn, vs = fn.ab_fastFS()                                                  # prepareFunc
        for v, vv in vs.items(): reqVals.add(v); avaiVals[v] = vv                # prepareFunc
    elif not isinstance(fn, str): return None; raise Exception(f"Function inside apply() has to either be a cli.op(), or a string containg a Python expression. Instead, it's {type(fn)}") # prepareFunc
    # adds args from lambda func to available vars                               # prepareFunc
    argVars = kast_lambda(fn); extraVars.extend(argVars)                         # prepareFunc
    # transpiles to js                                                           # prepareFunc
    fn, vs = py2js(fn)                                                           # prepareFunc
    # try to resolve variables                                                   # prepareFunc
    for v in vs:                                                                 # prepareFunc
        reqVals.add(v)                                                           # prepareFunc
        if v == "x": continue # just ignore "x" variable, cause that's the default # prepareFunc
        try: avaiVals[v] = g[v]                                                  # prepareFunc
        except: pass                                                             # prepareFunc
    # removing available variables from required variables                       # prepareFunc
    for v in avaiVals: reqVals.remove(v)                                         # prepareFunc
    extraVars = set(extraVars)                                                   # prepareFunc
    if checks:                                                                   # prepareFunc
        for v in reqVals:                                                        # prepareFunc
            if v not in extraVars:                                               # prepareFunc
                raise Exception(f"Expression depends on the variables {v}, but they couldn't be resolved") # prepareFunc
    # forming declaration statements                                             # prepareFunc
    return fn, "\n".join([f"const {k} = {json.dumps(v)};" for k, v in avaiVals.items()]), argVars # prepareFunc
def kast_lambda(fn:"str|cli.op") -> "list[str]":                                 # kast_lambda
    """Grab args of the input lambda func. If it's just a simple
expression then return ['x']"""                                                  # kast_lambda
    l = ast.parse(fn).body[0].value                                              # kast_lambda
    if isinstance(l, ast.Lambda): return [a.arg for a in l.args.args]            # kast_lambda
    return ["x"]                                                                 # kast_lambda
def prepareFunc2(fn, checks=True) -> "(header, fIdx) | None":                    # prepareFunc2
    res = prepareFunc(fn, [], checks)                                            # prepareFunc2
    if res is None: return None                                                  # prepareFunc2
    expr, avaiVars, args = res                                                   # prepareFunc2
    fIdx = k1lib.cli.init._jsFAuto()                                             # prepareFunc2
    dataIdx = k1lib.cli.init._jsDAuto()                                          # prepareFunc2
    return f"""{avaiVars}
const {fIdx} = ({', '.join(args)}) => {{
    return {expr};
}}""", fIdx                                                                      # prepareFunc2
def prepareFunc3(fn, meta, kwargs=None, args=None) -> "(header, fIdx)":          # prepareFunc3
    """Kinda similar to prepareFunc2(), but this time uses 3 different
strategies to transpile:

- Using prepareFunc2(), works for string with lambda functions and cli.op()
- Using the object's builtin ._jsF() function
- Using k1lib.settings.cli.kjs.jsF dictionary of different custom _jsF() functions

Throws error if can't transpile the function

:param kwargs: keyword arguments and arguments provided by aS(), apply(), filt(), and so on.
"""                                                                              # prepareFunc3
    res = prepareFunc2(fn, False) # tries to compile str/cli.op                  # prepareFunc3
    if res:                                                                      # prepareFunc3
        if not(kwargs is None or len(kwargs) == 0): raise Exception(f"Keyword arguments doesn't exist in JS! Please convert the function `{fn}` into positional arguments") # prepareFunc3
        if args is None or len(args) == 0: return res                            # prepareFunc3
        # prepare extra code here if extra args are available                    # prepareFunc3
        fIdx = cli.init._jsFAuto(); dataIdx = cli.init._jsDAuto(); argIdx = cli.init._jsDAuto() # prepareFunc3
        _header, _fIdx = res; return f"""{_header}\nconst {argIdx} = {json.dumps(args)};\nconst {fIdx} = ({dataIdx}) => {_fIdx}({dataIdx}, ...{argIdx})""", fIdx # prepareFunc3
    elif hasattr(fn, "_jsF"): return fn._jsF(meta, *(args or ()), **(kwargs or {})) # if not str/cli.op, then lookup the funcs, then pass through the kwargs and whatnot # prepareFunc3
    elif fn in k1lib.settings.cli.kjs.jsF:                                       # prepareFunc3
        try: return k1lib.settings.cli.kjs.jsF[fn](meta, *(args or ()), **(kwargs or {})) # prepareFunc3
        except Exception as e: print(f"Function `{fn}` exist in system, but throws the below error when executed"); raise e # prepareFunc3
    else: raise Exception(f"*._jsF() right now doesn't know how to transpile to JS this func: {fn}\n\nIt could be you're using regular lambda functions. Convert it to use cli.op(), or just enter the lambda function as a string, so `lambda x: x**2` turns into the string `\"lambda x: x**2\"`\n\nOr, if it's a function you use often, add to `settings.cli.kjs.jsF`") # prepareFunc3
def asyncGuard(data:"(header, fIdx)") -> "(header, fIdx, async?)":               # asyncGuard
    """Converts ._jsF() function signatures from (header, fIdx) into (header, fIdx, is fIdx async?).
Example::

    # returns tuple (header, fIdx, async?)
    kast.asyncGuard(apply("x**2")._jsF(None))
"""                                                                              # asyncGuard
    if data is NotImplemented: return NotImplemented                             # asyncGuard
    header, fIdx = data                                                          # asyncGuard
    g1 = cli.grep(f"(?P<a>(const)|(let)) {fIdx}[ ]*=[ ]*(?P<g>(async)|((?!\(*)))[ ]*\(", extract="g") # asyncGuard
    g2 = cli.grep(f"(?P<g>async)[ ]*function[ ]*{fIdx}\(", extract="g")          # asyncGuard
    x = header.split("\n"); return [header, fIdx, len(list(g1(x))) + len(list(g2(x))) > 0] # asyncGuard
