def jsify_for(node: ast.For) -> typing.List[unholy.Compilable]: # 'target', 'iter', 'body', 'orelse', 'type_comment', output = [] var_name = unholy.jsify_node(node.target) if isinstance(node.iter, ast.Call) and isinstance( node.iter.func, ast.Name) and node.iter.func.id == 'range': # range loop start, end, step = _parse_range_arguments(node.iter) output.append( unholy.JSStatement('for ', [ '(let ', *var_name, ' = ', *start, '; (', *step, ' >= 0 ? (', *var_name, ' < ', *end, ') : ', *var_name, ' > ', *end, '); ', *var_name, '+=', *step, ')' ], has_semicolon=False)) else: iterator = unholy.jsify_node(node.iter) output.append( unholy.JSStatement('for ', [ '(const ', *var_name, ' of ', f'{unholy.map_name("iter")}(', *iterator, '))' ], has_semicolon=False)) body = [] for i in node.body: body.append(unholy.JSStatement('', unholy.jsify_node(i))) output.append(unholy.JSBlock(body)) return output
def jsify_assign( node: typing.Union[ast.Assign, ast.AnnAssign] ) -> typing.List[unholy.Compilable]: # targets?, value, type_comment if isinstance(node, ast.Assign): t = node.targets else: t = [node.target] targets = [] for i in t: targets.extend(unholy.jsify_node(i)) if len(targets) == 1: return [ unholy.JSExpression( [targets[0], ' = ', *unholy.jsify_node(node.value)]) ] else: output = ['['] for i in targets: output.append(i) output.append(', ') output.append('] =') output.extend(unholy.jsify_node(node.value)) return [unholy.JSExpression(output)]
def jsify_dict(node: ast.Dict) -> List[Compilable]: # keys, values output = [] for k, v in zip(node.keys, node.values): jk = jsify_node(k) jv = jsify_node(v) if len(jk) != 1: raise CompilationError( f'Dict key: Expected only one JSExpression (or Compilable) while got ' f'{len(jk)}: {jk!r}') if len(jv) != 1: raise CompilationError( f'Dict value: Expected only one JSExpression (or Compilable) while got ' f'{len(jv)}: {jv!r}') output.append( JSExpression([ JSExpression(['[', jk[0], ']', ':']), jv[0], ])) return [JSExpression([ '{', JSExpression(output), '}', ])]
def jsify_formatted_value(node: ast.FormattedValue) -> typing.List[Compilable]: value = jsify_node(node.value) if node.format_spec: fspec = jsify_node(node.format_spec) return [ JSExpression(['unholy_js.py__format(', *value, ', ', *fspec, ')']) ] else: return value
def jsify_call(node: ast.Call) -> typing.List[unholy.Compilable]: func_name = unholy.jsify_node(node.func) args = [] for i in node.args: args.extend(unholy.jsify_node(i)) if node.keywords: warnings.warn( f'keyword arguments were ignored while calling {func_name}') return [unholy.JSExpression([func_name[0], '(', *args, ')'])]
def jsify_if(node: ast.If) -> typing.List[unholy.Compilable]: # test, body, orelse, body = [] for i in node.body: body.extend(unholy.jsify_node(i)) return [ unholy.JSStatement('if ', ['(', *unholy.jsify_node(node.test), ')'], has_semicolon=False), unholy.JSBlock(body) ]
def jsify_fstring(node: ast.JoinedStr) -> typing.List[Compilable]: values = [] for i in node.values: if isinstance(i, ast.FormattedValue): values.append(JSExpression(['${', *jsify_node(i), '}'])) else: if isinstance(i, ast.Constant): values.append(JSExpression([i.value])) else: values.extend(jsify_node(i)) return [JSExpression(['`', *values, '`'])]
def jsify_subscript(node: ast.Subscript) -> typing.List[unholy.Compilable]: # value, slice, ctx, v = unholy.jsify_node(node.value) # node.slice: typing.Union[ast.Slice, ast.Index] if isinstance(node.slice, ast.Slice): raise unholy.CompilationError('Slicing is not yet implemented') elif isinstance(node.slice, ast.Index): return [ unholy.JSExpression([*v, *unholy.jsify_node(node.slice.value)]) ] elif isinstance(node.slice, (ast.Constant, ast.Name)): return [ unholy.JSExpression([*v, '[', *unholy.jsify_node(node.slice), ']']) ] else: raise unholy.CompilationError( f'Unknown index type: {type(node.slice)}. No way to js-ify. Aborting' )
def _jsify_sequential_container(node, prefix, suffix) -> List[Compilable]: elts = [] for i in node.elts: v = jsify_node(i) if len(v) != 1: raise CompilationError( f'Expected only one JSExpression (or Compilable) while got {len(v)}: {v!r}' ) elts.append(v[0]) elts.append(', ') return [JSExpression([prefix, JSExpression(elts), suffix])]
def jsify_module(node: ast.Module) -> typing.List[unholy.Compilable]: body = [] for i in node.body: body.extend(unholy.jsify_node(i)) logging.debug(body) return [ unholy.JSBlock([ unholy.JSStatement( 'const unholy_js = require("./not_python/unholy_js.js")'), *body ], has_braces=False) ]
def jsify_function_definition( node: typing.Union[ast.FunctionDef, ast.AsyncFunctionDef] ) -> typing.List[unholy.Compilable]: name = node.name decorators = [] for i in node.decorator_list: decorators.extend(unholy.jsify_node(i)) decorators.append('(') if decorators: decorators.pop(-1) # remove unnecessary '(' arguments: typing.List[ast.arg] = node.args.args arg_list = ', '.join([i.arg for i in arguments]) if node.args.kwarg: arg_list += f', options' if node.args.vararg: arg_list += f', ...{node.args.vararg.arg}' body = [] for i in node.body: body.extend(unholy.jsify_node(i)) if isinstance(node, ast.FunctionDef): keyword = 'function' elif isinstance(node, ast.AsyncFunctionDef): keyword = 'async function' else: raise unholy.CompilationError( 'Expected a FunctionDev or AsyncFunctionDef. Something broke.') return [ # unholy.JSStatement(f'let {name} = {decorators}(function {name}({arg_list})', has_semicolon=False), unholy.JSStatement('let ', [ name, ' = ', *decorators, f'({keyword}', name, '(', arg_list, ')' ], has_semicolon=False), unholy.JSBlock(body), unholy.JSStatement(')' * (len(node.decorator_list) + 1), force_concat=True) ]
def jsify_lambda(node: ast.Lambda) -> typing.List[unholy.Compilable]: # args, body arguments: typing.List[ast.arg] = node.args.args arg_list = ', '.join([i.arg for i in arguments]) if node.args.kwarg: arg_list += f', options' if node.args.vararg: arg_list += f', ...{node.args.vararg.arg}' return [ unholy.JSExpression([ unholy.JSExpression([f'({arg_list}) =>']), unholy.JSBlock(unholy.jsify_node(node.body)) ]) ]
def _parse_range_arguments(call: ast.Call): if len(call.args) == 1: return [unholy.JSExpression(['0'])], unholy.jsify_node( call.args[0]), [unholy.JSExpression(['1'])] elif len(call.args) == 2: return unholy.jsify_node(call.args[0]), unholy.jsify_node( call.args[1]), [unholy.JSExpression(['1'])] elif len(call.args) == 3: return unholy.jsify_node(call.args[0]), unholy.jsify_node( call.args[1]), unholy.jsify_node(call.args[2]) else: raise unholy.CompilationError( 'Bad arguments for range() function in for loop.')
import ast from typing import List # noinspection PyUnresolvedReferences from typechecker import ensure_typecheck from unholy import jsify_node # noinspection PyUnresolvedReferences from unholy.classes import Compilable, CompilationError, JSExpression PY_TO_JS_OPERATORS = { ast.Mult: lambda l, r: JSExpression([ *jsify_node(l), '*', *jsify_node(r), ]), ast.Div: lambda l, r: JSExpression([ *jsify_node(l), '/', *jsify_node(r), ]), ast.Add: lambda l, r: JSExpression([ *jsify_node(l), '+', *jsify_node(r), ]), ast.Pow: lambda l, r: JSExpression([ *jsify_node(l), '**', *jsify_node(r), ]), ast.USub: lambda l, r: JSExpression([
def jsify_return(node: ast.Return) -> typing.List[unholy.Compilable]: # 'value', # noinspection PyTypeChecker v: unholy.JSStatement = unholy.jsify_node(node.value)[0] v.force_concat = True return [unholy.JSStatement(f'return'), v]
def jsify_delete(node: ast.Delete) -> typing.List[unholy.Compilable]: # 'targets', return [ unholy.JSStatement('delete ', unholy.jsify_node(i)) for i in node.targets ]
def jsify_attribute(node: ast.Attribute) -> typing.List[unholy.Compilable]: # value, attr, ctx return [ unholy.JSExpression([*unholy.jsify_node(node.value), '.', node.attr]) ]
def jsify_expr(node: ast.Expr) -> typing.List[unholy.Compilable]: # 'value', return unholy.jsify_node(node.value)
def jsify_await(node: ast.Await) -> typing.List[unholy.Compilable]: return [unholy.JSExpression(['await', *unholy.jsify_node(node.value)])]