Files
bikini-patchwork/patchwork/abyss.py
T
2026-06-17 20:10:17 -05:00

966 lines
33 KiB
Python

from __future__ import annotations
import ast
import base64
import json
import random
from dataclasses import dataclass
from typing import Any
from .util import gen_bytes
DISPATCH_NAME = "__pw_ab_dispatch__"
ASSETS_NAME = "__pw_ab_assets__"
_OPS = (
"CONST",
"LOAD",
"STORE",
"POP",
"DUP",
"BIN",
"UNARY",
"COMPARE_CHAIN",
"JUMP",
"JUMP_IF_FALSE",
"JUMP_IF_TRUE_KEEP",
"JUMP_IF_FALSE_KEEP",
"CALL",
"GET_ATTR",
"SUBSCR",
"BUILD_SLICE",
"BUILD_LIST",
"BUILD_TUPLE",
"BUILD_SET",
"BUILD_DICT",
"RETURN",
"GET_ITER",
"FOR_ITER",
"UNPACK",
"BUILD_STRING",
"FORMAT_VALUE",
)
_BIN_OPS: tuple[tuple[type[ast.operator], str], ...] = (
(ast.Add, "add"),
(ast.Sub, "sub"),
(ast.Mult, "mul"),
(ast.MatMult, "matmul"),
(ast.Div, "truediv"),
(ast.FloorDiv, "floordiv"),
(ast.Mod, "mod"),
(ast.Pow, "pow"),
(ast.LShift, "lshift"),
(ast.RShift, "rshift"),
(ast.BitOr, "or"),
(ast.BitXor, "xor"),
(ast.BitAnd, "and"),
)
_UNARY_OPS: tuple[tuple[type[ast.unaryop], str], ...] = (
(ast.Invert, "invert"),
(ast.Not, "not"),
(ast.UAdd, "pos"),
(ast.USub, "neg"),
)
_COMPARE_OPS: tuple[tuple[type[ast.cmpop], str], ...] = (
(ast.Eq, "eq"),
(ast.NotEq, "ne"),
(ast.Lt, "lt"),
(ast.LtE, "le"),
(ast.Gt, "gt"),
(ast.GtE, "ge"),
(ast.Is, "is"),
(ast.IsNot, "is_not"),
(ast.In, "in"),
(ast.NotIn, "not_in"),
)
class UnsupportedAbyssNode(ValueError):
pass
@dataclass(frozen=True)
class EncodedAbyssAssets:
payload: str
key: str
opcodes: dict[str, int]
class _Label:
def __init__(self) -> None:
self.index: int | None = None
class _Emitter:
def __init__(self) -> None:
self.instructions: list[list[Any]] = []
def label(self) -> _Label:
return _Label()
def mark(self, label: _Label) -> None:
label.index = len(self.instructions)
def emit(self, op: str, *args: Any) -> None:
self.instructions.append([op, *args])
def resolve(self) -> list[list[Any]]:
resolved: list[list[Any]] = []
for inst in self.instructions:
op, *args = inst
new_args: list[Any] = []
for arg in args:
if isinstance(arg, _Label):
if arg.index is None:
raise UnsupportedAbyssNode("internal unresolved VM label")
new_args.append(arg.index)
else:
new_args.append(arg)
resolved.append([op, *new_args])
return resolved
def _op_name(op: ast.AST, table: tuple[tuple[type[Any], str], ...]) -> str:
for cls, name in table:
if isinstance(op, cls):
return name
raise UnsupportedAbyssNode(f"unsupported operator {type(op).__name__}")
def _strip_docstring(body: list[ast.stmt]) -> list[ast.stmt]:
if (
body
and isinstance(body[0], ast.Expr)
and isinstance(body[0].value, ast.Constant)
and isinstance(body[0].value.value, str)
):
return body[1:]
return body
def _argument_names(args: ast.arguments) -> set[str]:
names = {arg.arg for arg in args.posonlyargs + args.args + args.kwonlyargs}
if args.vararg is not None:
names.add(args.vararg.arg)
if args.kwarg is not None:
names.add(args.kwarg.arg)
return names
def _target_names(target: ast.AST) -> set[str]:
if isinstance(target, ast.Name):
return {target.id}
if isinstance(target, (ast.Tuple, ast.List)):
names: set[str] = set()
for elt in target.elts:
names.update(_target_names(elt))
return names
return set()
class _ScopeCollector(ast.NodeVisitor):
def __init__(self, args: ast.arguments) -> None:
self.local_names = set(_argument_names(args))
self.global_names: set[str] = set()
def visit_Global(self, node: ast.Global) -> None:
self.global_names.update(node.names)
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
return
visit_AsyncFunctionDef = visit_FunctionDef
def visit_ClassDef(self, node: ast.ClassDef) -> None:
return
def visit_Lambda(self, node: ast.Lambda) -> None:
return
def visit_Assign(self, node: ast.Assign) -> None:
for target in node.targets:
self.local_names.update(_target_names(target))
self.visit(node.value)
def visit_AnnAssign(self, node: ast.AnnAssign) -> None:
self.local_names.update(_target_names(node.target))
if node.value is not None:
self.visit(node.value)
def visit_AugAssign(self, node: ast.AugAssign) -> None:
self.local_names.update(_target_names(node.target))
self.visit(node.value)
def visit_For(self, node: ast.For) -> None:
self.local_names.update(_target_names(node.target))
self.visit(node.iter)
for stmt in node.body + node.orelse:
self.visit(stmt)
def visit_NamedExpr(self, node: ast.NamedExpr) -> None:
self.local_names.update(_target_names(node.target))
self.visit(node.value)
def finalize(self) -> tuple[set[str], set[str]]:
return self.local_names - self.global_names, self.global_names
class AbyssCompiler:
def __init__(self) -> None:
self.emitter = _Emitter()
self.constants: list[Any] = []
self.local_names: set[str] = set()
self.global_names: set[str] = set()
self.external_names: set[str] = set()
self.loop_stack: list[tuple[_Label, _Label, int]] = []
def compile(self, node: ast.FunctionDef) -> dict[str, Any]:
self._reject_function_shape(node)
collector = _ScopeCollector(node.args)
for stmt in node.body:
collector.visit(stmt)
self.local_names, self.global_names = collector.finalize()
body = _strip_docstring(node.body)
if not body:
self.emitter.emit("CONST", self._const(None))
self.emitter.emit("RETURN")
else:
for stmt in body:
self._stmt(stmt)
self.emitter.emit("CONST", self._const(None))
self.emitter.emit("RETURN")
return {
"name": node.name,
"code": self.emitter.resolve(),
"consts": [_encode_const(value) for value in self.constants],
"globals": sorted(self.global_names),
"locals": sorted(self.local_names),
"externals": sorted(self.external_names),
}
def _reject_function_shape(self, node: ast.FunctionDef) -> None:
for child in ast.walk(node):
if isinstance(child, (ast.Yield, ast.YieldFrom, ast.Await)):
raise UnsupportedAbyssNode("generators, coroutines, and await are not supported")
if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Lambda)):
if child is not node:
raise UnsupportedAbyssNode("nested functions/classes are not supported")
def _const(self, value: Any) -> int:
idx = len(self.constants)
self.constants.append(value)
return idx
def _stmt(self, node: ast.stmt) -> None:
if isinstance(node, ast.Return):
if node.value is None:
self.emitter.emit("CONST", self._const(None))
else:
self._expr(node.value)
self.emitter.emit("RETURN")
return
if isinstance(node, ast.Assign):
self._expr(node.value)
for index, target in enumerate(node.targets):
if index < len(node.targets) - 1:
self.emitter.emit("DUP")
self._store_target(target)
return
if isinstance(node, ast.AnnAssign):
if node.value is not None:
self._expr(node.value)
self._store_target(node.target)
return
if isinstance(node, ast.AugAssign):
if not isinstance(node.target, ast.Name):
raise UnsupportedAbyssNode("augmented assignment only supports local/global names")
self._expr(ast.Name(id=node.target.id, ctx=ast.Load()))
self._expr(node.value)
self.emitter.emit("BIN", _op_name(node.op, _BIN_OPS))
self.emitter.emit("STORE", node.target.id)
return
if isinstance(node, ast.Expr):
self._expr(node.value)
self.emitter.emit("POP")
return
if isinstance(node, ast.Pass):
return
if isinstance(node, ast.Global):
return
if isinstance(node, ast.If):
else_label = self.emitter.label()
end_label = self.emitter.label()
self._expr(node.test)
self.emitter.emit("JUMP_IF_FALSE", else_label)
for stmt in node.body:
self._stmt(stmt)
self.emitter.emit("JUMP", end_label)
self.emitter.mark(else_label)
for stmt in node.orelse:
self._stmt(stmt)
self.emitter.mark(end_label)
return
if isinstance(node, ast.While):
start_label = self.emitter.label()
end_label = self.emitter.label()
self.emitter.mark(start_label)
self._expr(node.test)
self.emitter.emit("JUMP_IF_FALSE", end_label)
self.loop_stack.append((start_label, end_label, 0))
for stmt in node.body:
self._stmt(stmt)
self.loop_stack.pop()
self.emitter.emit("JUMP", start_label)
self.emitter.mark(end_label)
if node.orelse:
raise UnsupportedAbyssNode("while/else is not supported")
return
if isinstance(node, ast.For):
if node.orelse:
raise UnsupportedAbyssNode("for/else is not supported")
start_label = self.emitter.label()
end_label = self.emitter.label()
self._expr(node.iter)
self.emitter.emit("GET_ITER")
self.emitter.mark(start_label)
self.emitter.emit("FOR_ITER", end_label)
self._store_target(node.target)
self.loop_stack.append((start_label, end_label, 1))
for stmt in node.body:
self._stmt(stmt)
self.loop_stack.pop()
self.emitter.emit("JUMP", start_label)
self.emitter.mark(end_label)
return
if isinstance(node, ast.Break):
if not self.loop_stack:
raise UnsupportedAbyssNode("break outside loop")
_, end_label, cleanup = self.loop_stack[-1]
for _ in range(cleanup):
self.emitter.emit("POP")
self.emitter.emit("JUMP", end_label)
return
if isinstance(node, ast.Continue):
if not self.loop_stack:
raise UnsupportedAbyssNode("continue outside loop")
start_label, _, _ = self.loop_stack[-1]
self.emitter.emit("JUMP", start_label)
return
raise UnsupportedAbyssNode(f"unsupported statement {type(node).__name__}")
def _store_target(self, target: ast.AST) -> None:
if isinstance(target, ast.Name):
self.emitter.emit("STORE", target.id)
return
if isinstance(target, (ast.Tuple, ast.List)):
self.emitter.emit("UNPACK", len(target.elts))
for elt in target.elts:
self._store_target(elt)
return
raise UnsupportedAbyssNode(f"unsupported assignment target {type(target).__name__}")
def _expr(self, node: ast.expr) -> None:
if isinstance(node, ast.Constant):
self.emitter.emit("CONST", self._const(node.value))
return
if isinstance(node, ast.Name):
if isinstance(node.ctx, ast.Load):
if node.id not in self.local_names or node.id in self.global_names:
self.external_names.add(node.id)
self.emitter.emit("LOAD", node.id)
return
raise UnsupportedAbyssNode("name expression is not load context")
if isinstance(node, ast.BinOp):
self._expr(node.left)
self._expr(node.right)
self.emitter.emit("BIN", _op_name(node.op, _BIN_OPS))
return
if isinstance(node, ast.UnaryOp):
self._expr(node.operand)
self.emitter.emit("UNARY", _op_name(node.op, _UNARY_OPS))
return
if isinstance(node, ast.BoolOp):
self._bool_op(node)
return
if isinstance(node, ast.Compare):
self._expr(node.left)
for comparator in node.comparators:
self._expr(comparator)
self.emitter.emit("COMPARE_CHAIN", [_op_name(op, _COMPARE_OPS) for op in node.ops])
return
if isinstance(node, ast.IfExp):
else_label = self.emitter.label()
end_label = self.emitter.label()
self._expr(node.test)
self.emitter.emit("JUMP_IF_FALSE", else_label)
self._expr(node.body)
self.emitter.emit("JUMP", end_label)
self.emitter.mark(else_label)
self._expr(node.orelse)
self.emitter.mark(end_label)
return
if isinstance(node, ast.Call):
if any(isinstance(arg, ast.Starred) for arg in node.args):
raise UnsupportedAbyssNode("*args calls are not supported")
if any(keyword.arg is None for keyword in node.keywords):
raise UnsupportedAbyssNode("**kwargs calls are not supported")
self._expr(node.func)
for arg in node.args:
self._expr(arg)
keyword_names: list[str] = []
for keyword in node.keywords:
if keyword.arg is None:
raise UnsupportedAbyssNode("**kwargs calls are not supported")
keyword_names.append(keyword.arg)
self._expr(keyword.value)
self.emitter.emit("CALL", len(node.args), keyword_names)
return
if isinstance(node, ast.Attribute):
self._expr(node.value)
self.emitter.emit("GET_ATTR", node.attr)
return
if isinstance(node, ast.Subscript):
self._expr(node.value)
self._expr(node.slice)
self.emitter.emit("SUBSCR")
return
if isinstance(node, ast.Slice):
for part in (node.lower, node.upper, node.step):
if part is None:
self.emitter.emit("CONST", self._const(None))
else:
self._expr(part)
self.emitter.emit("BUILD_SLICE")
return
if isinstance(node, ast.List):
self._sequence(node.elts, "BUILD_LIST")
return
if isinstance(node, ast.Tuple):
self._sequence(node.elts, "BUILD_TUPLE")
return
if isinstance(node, ast.Set):
self._sequence(node.elts, "BUILD_SET")
return
if isinstance(node, ast.Dict):
for key, value in zip(node.keys, node.values):
if key is None:
raise UnsupportedAbyssNode("dictionary unpacking is not supported")
self._expr(key)
self._expr(value)
self.emitter.emit("BUILD_DICT", len(node.keys))
return
if isinstance(node, ast.JoinedStr):
for value in node.values:
if isinstance(value, ast.Constant) and isinstance(value.value, str):
self.emitter.emit("CONST", self._const(value.value))
elif isinstance(value, ast.FormattedValue):
self._formatted_value(value)
else:
raise UnsupportedAbyssNode("unsupported f-string part")
self.emitter.emit("BUILD_STRING", len(node.values))
return
if isinstance(node, ast.FormattedValue):
self._formatted_value(node)
return
if isinstance(node, ast.NamedExpr):
if not isinstance(node.target, ast.Name):
raise UnsupportedAbyssNode("walrus target must be a name")
self._expr(node.value)
self.emitter.emit("DUP")
self._store_target(node.target)
return
raise UnsupportedAbyssNode(f"unsupported expression {type(node).__name__}")
def _bool_op(self, node: ast.BoolOp) -> None:
if not node.values:
raise UnsupportedAbyssNode("empty boolean operation")
end_label = self.emitter.label()
jump_op = "JUMP_IF_TRUE_KEEP" if isinstance(node.op, ast.Or) else "JUMP_IF_FALSE_KEEP"
for index, value in enumerate(node.values):
self._expr(value)
if index < len(node.values) - 1:
self.emitter.emit(jump_op, end_label)
self.emitter.mark(end_label)
def _sequence(self, elts: list[ast.expr], op: str) -> None:
for elt in elts:
if isinstance(elt, ast.Starred):
raise UnsupportedAbyssNode("starred literals are not supported")
self._expr(elt)
self.emitter.emit(op, len(elts))
def _formatted_value(self, node: ast.FormattedValue) -> None:
self._expr(node.value)
if node.format_spec is not None:
self._expr(node.format_spec)
self.emitter.emit("FORMAT_VALUE", node.conversion, True)
else:
self.emitter.emit("FORMAT_VALUE", node.conversion, False)
class AbyssTransformer(ast.NodeTransformer):
def __init__(
self,
rng: random.Random,
*,
targets: set[str] | None = None,
auto: bool = False,
dispatch_name: str = DISPATCH_NAME,
) -> None:
self.rng = rng
self.targets = set(targets or ())
self.auto = auto
self.dispatch_name = dispatch_name
self.assets: list[dict[str, Any]] = []
self.keep_names: set[str] = {dispatch_name}
self.protected: list[str] = []
self.skipped: list[tuple[str, str]] = []
self._matched_targets: set[str] = set()
self._class_stack: list[str] = []
self._function_depth = 0
@property
def explicit(self) -> bool:
return bool(self.targets)
def protect(self, tree: ast.Module) -> ast.Module:
new_tree = self.visit(tree)
if not isinstance(new_tree, ast.Module):
raise TypeError("expected module")
missing = sorted(self.targets - self._matched_targets)
if missing:
raise ValueError(f"abyss target(s) not found: {', '.join(missing)}")
return new_tree
def visit_ClassDef(self, node: ast.ClassDef) -> ast.AST:
self._class_stack.append(node.name)
node.body = [self.visit(stmt) for stmt in node.body]
self._class_stack.pop()
return node
def visit_FunctionDef(self, node: ast.FunctionDef) -> ast.AST:
qualname = ".".join([*self._class_stack, node.name]) if self._class_stack else node.name
target_names = {node.name, qualname}
targeted = bool(self.targets & target_names)
desired = self._function_depth == 0 and (targeted or (self.auto and not self.explicit))
if targeted:
self._matched_targets.update(self.targets & target_names)
if not desired:
self._function_depth += 1
node = self.generic_visit(node)
self._function_depth -= 1
return node
try:
asset = AbyssCompiler().compile(node)
except UnsupportedAbyssNode as exc:
if targeted:
raise ValueError(f"abyss target {qualname} is unsupported: {exc}") from exc
self.skipped.append((qualname, str(exc)))
self._function_depth += 1
node = self.generic_visit(node)
self._function_depth -= 1
return node
fid = len(self.assets)
self.assets.append(asset)
self.keep_names.update(asset["externals"])
self.protected.append(qualname)
node.body = self._wrapper_body(fid, _function_docstring(node))
return node
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> ast.AST:
qualname = ".".join([*self._class_stack, node.name]) if self._class_stack else node.name
target_names = {node.name, qualname}
targeted = bool(self.targets & target_names)
if targeted:
self._matched_targets.update(self.targets & target_names)
raise ValueError(f"abyss target {qualname} is unsupported: async functions are not supported")
return node
def _wrapper_body(self, fid: int, docstring: str | None) -> list[ast.stmt]:
call = ast.Call(
func=ast.Name(id=self.dispatch_name, ctx=ast.Load()),
args=[
ast.Constant(value=fid),
ast.Call(func=ast.Name(id="locals", ctx=ast.Load()), args=[], keywords=[]),
],
keywords=[],
)
body: list[ast.stmt] = []
if docstring is not None:
body.append(ast.Expr(value=ast.Constant(value=docstring)))
body.append(ast.Return(value=call))
return body
def _function_docstring(node: ast.FunctionDef) -> str | None:
if (
node.body
and isinstance(node.body[0], ast.Expr)
and isinstance(node.body[0].value, ast.Constant)
and isinstance(node.body[0].value.value, str)
):
return node.body[0].value.value
return None
def _encode_const(value: Any) -> dict[str, Any]:
if value is None:
return {"t": "none"}
if value is Ellipsis:
return {"t": "ellipsis"}
if isinstance(value, bool):
return {"t": "bool", "v": value}
if isinstance(value, int):
return {"t": "int", "v": str(value)}
if isinstance(value, float):
return {"t": "float", "v": repr(value)}
if isinstance(value, str):
return {"t": "str", "v": value}
if isinstance(value, bytes):
return {"t": "bytes", "v": base64.b85encode(value).decode("ascii")}
raise UnsupportedAbyssNode(f"unsupported constant type {type(value).__name__}")
def encode_assets(assets: list[dict[str, Any]], rng: random.Random) -> EncodedAbyssAssets:
values = rng.sample(range(1, 251), len(_OPS))
opcodes = dict(zip(_OPS, values))
encoded_funcs: list[dict[str, Any]] = []
for asset in assets:
encoded_code = [[opcodes[inst[0]], *inst[1:]] for inst in asset["code"]]
encoded_funcs.append(
{
"n": asset["name"],
"c": asset["consts"],
"g": asset["globals"],
"l": asset["locals"],
"b": encoded_code,
}
)
document = json.dumps({"v": 1, "f": encoded_funcs}, separators=(",", ":"), sort_keys=True).encode("utf-8")
key = gen_bytes(rng, rng.randint(24, 48))
encrypted = bytes(byte ^ key[index % len(key)] for index, byte in enumerate(document))
return EncodedAbyssAssets(
payload=base64.b85encode(encrypted).decode("ascii"),
key=base64.b85encode(key).decode("ascii"),
opcodes=opcodes,
)
def build_runtime_stmts(encoded: EncodedAbyssAssets, rng: random.Random) -> list[ast.stmt]:
blocks = _runtime_dispatch_blocks(encoded.opcodes)
rng.shuffle(blocks)
dispatch_blocks = "\n".join(blocks)
source = f'''
{ASSETS_NAME} = ({encoded.payload!r}, {encoded.key!r})
__pw_ab_cache__ = None
def __pw_ab_const__(_x):
_t = _x['t']
if _t == 'none':
return None
if _t == 'ellipsis':
return Ellipsis
if _t == 'bool':
return bool(_x['v'])
if _t == 'int':
return int(_x['v'])
if _t == 'float':
return float(_x['v'])
if _t == 'str':
return _x['v']
if _t == 'bytes':
return __import__('base64').b85decode(_x['v'].encode('ascii'))
raise RuntimeError('invalid abyss constant')
def __pw_ab_load__():
global __pw_ab_cache__
if __pw_ab_cache__ is None:
_base64 = __import__('base64')
_json = __import__('json')
_payload, _key = {ASSETS_NAME}
_enc = _base64.b85decode(_payload.encode('ascii'))
_raw_key = _base64.b85decode(_key.encode('ascii'))
_raw = bytes(_b ^ _raw_key[_i % len(_raw_key)] for _i, _b in enumerate(_enc))
_doc = _json.loads(_raw.decode('utf-8'))
for _fn in _doc['f']:
_fn['c'] = [__pw_ab_const__(_item) for _item in _fn['c']]
__pw_ab_cache__ = _doc
return __pw_ab_cache__
def __pw_ab_get__(_name, _locals, _globals, _builtins, _declared_globals, _declared_locals):
if _name not in _declared_globals and _name in _locals:
return _locals[_name]
if _name not in _declared_globals and _name in _declared_locals:
raise UnboundLocalError("cannot access local variable '" + _name + "' where it is not associated with a value")
if _name in _globals:
return _globals[_name]
if _name in _builtins:
return _builtins[_name]
raise NameError(_name)
def __pw_ab_store__(_name, _value, _locals, _globals, _declared_globals):
if _name in _declared_globals:
_globals[_name] = _value
else:
_locals[_name] = _value
def __pw_ab_bin__(_op, _a, _b):
if _op == 'add':
return _a + _b
if _op == 'sub':
return _a - _b
if _op == 'mul':
return _a * _b
if _op == 'matmul':
return _a @ _b
if _op == 'truediv':
return _a / _b
if _op == 'floordiv':
return _a // _b
if _op == 'mod':
return _a % _b
if _op == 'pow':
return _a ** _b
if _op == 'lshift':
return _a << _b
if _op == 'rshift':
return _a >> _b
if _op == 'or':
return _a | _b
if _op == 'xor':
return _a ^ _b
if _op == 'and':
return _a & _b
raise RuntimeError('invalid abyss binary op')
def __pw_ab_unary__(_op, _a):
if _op == 'invert':
return ~_a
if _op == 'not':
return not _a
if _op == 'pos':
return +_a
if _op == 'neg':
return -_a
raise RuntimeError('invalid abyss unary op')
def __pw_ab_compare_one__(_op, _a, _b):
if _op == 'eq':
return _a == _b
if _op == 'ne':
return _a != _b
if _op == 'lt':
return _a < _b
if _op == 'le':
return _a <= _b
if _op == 'gt':
return _a > _b
if _op == 'ge':
return _a >= _b
if _op == 'is':
return _a is _b
if _op == 'is_not':
return _a is not _b
if _op == 'in':
return _a in _b
if _op == 'not_in':
return _a not in _b
raise RuntimeError('invalid abyss compare op')
def __pw_ab_compare__(_ops, _values):
for _idx, _op in enumerate(_ops):
if not __pw_ab_compare_one__(_op, _values[_idx], _values[_idx + 1]):
return False
return True
def __pw_ab_format__(_value, _conversion, _has_spec, _stack):
_spec = _stack.pop() if _has_spec else ''
if _conversion == 115:
_value = str(_value)
elif _conversion == 114:
_value = repr(_value)
elif _conversion == 97:
_value = ascii(_value)
return format(_value, _spec)
def __pw_ab_exec__(_fn, _initial_locals, _globals):
_builtins = _globals.get('__builtins__', __builtins__)
if not isinstance(_builtins, dict):
_builtins = _builtins.__dict__
_declared_globals = set(_fn.get('g', ()))
_declared_locals = set(_fn.get('l', ()))
_locals = dict(_initial_locals)
_consts = _fn['c']
_code = _fn['b']
_stack = []
_ip = 0
while True:
_inst = _code[_ip]
_op = _inst[0]
{dispatch_blocks}
raise RuntimeError('invalid abyss opcode')
def {DISPATCH_NAME}(_fid, _env):
_doc = __pw_ab_load__()
return __pw_ab_exec__(_doc['f'][_fid], _env, globals())
'''
return ast.parse(source).body
def _runtime_dispatch_blocks(opcodes: dict[str, int]) -> list[str]:
return [
f""" if _op == {opcodes['CONST']}:
_stack.append(_consts[_inst[1]])
_ip += 1
continue""",
f""" if _op == {opcodes['LOAD']}:
_stack.append(__pw_ab_get__(_inst[1], _locals, _globals, _builtins, _declared_globals, _declared_locals))
_ip += 1
continue""",
f""" if _op == {opcodes['STORE']}:
__pw_ab_store__(_inst[1], _stack.pop(), _locals, _globals, _declared_globals)
_ip += 1
continue""",
f""" if _op == {opcodes['POP']}:
_stack.pop()
_ip += 1
continue""",
f""" if _op == {opcodes['DUP']}:
_stack.append(_stack[-1])
_ip += 1
continue""",
f""" if _op == {opcodes['BIN']}:
_b = _stack.pop()
_a = _stack.pop()
_stack.append(__pw_ab_bin__(_inst[1], _a, _b))
_ip += 1
continue""",
f""" if _op == {opcodes['UNARY']}:
_stack.append(__pw_ab_unary__(_inst[1], _stack.pop()))
_ip += 1
continue""",
f""" if _op == {opcodes['COMPARE_CHAIN']}:
_ops = _inst[1]
_values = [_stack.pop() for _ in range(len(_ops) + 1)]
_values.reverse()
_stack.append(__pw_ab_compare__(_ops, _values))
_ip += 1
continue""",
f""" if _op == {opcodes['JUMP']}:
_ip = _inst[1]
continue""",
f""" if _op == {opcodes['JUMP_IF_FALSE']}:
_ip = _inst[1] if not _stack.pop() else _ip + 1
continue""",
f""" if _op == {opcodes['JUMP_IF_TRUE_KEEP']}:
if _stack[-1]:
_ip = _inst[1]
else:
_stack.pop()
_ip += 1
continue""",
f""" if _op == {opcodes['JUMP_IF_FALSE_KEEP']}:
if not _stack[-1]:
_ip = _inst[1]
else:
_stack.pop()
_ip += 1
continue""",
f""" if _op == {opcodes['CALL']}:
_argc = _inst[1]
_kw_names = _inst[2]
_kw = {{}}
for _name in reversed(_kw_names):
_kw[_name] = _stack.pop()
_args = [_stack.pop() for _ in range(_argc)]
_args.reverse()
_func = _stack.pop()
_stack.append(_func(*_args, **_kw))
_ip += 1
continue""",
f""" if _op == {opcodes['GET_ATTR']}:
_stack.append(getattr(_stack.pop(), _inst[1]))
_ip += 1
continue""",
f""" if _op == {opcodes['SUBSCR']}:
_key = _stack.pop()
_obj = _stack.pop()
_stack.append(_obj[_key])
_ip += 1
continue""",
f""" if _op == {opcodes['BUILD_SLICE']}:
_step = _stack.pop()
_upper = _stack.pop()
_lower = _stack.pop()
_stack.append(slice(_lower, _upper, _step))
_ip += 1
continue""",
f""" if _op == {opcodes['BUILD_LIST']}:
_items = [_stack.pop() for _ in range(_inst[1])]
_items.reverse()
_stack.append(_items)
_ip += 1
continue""",
f""" if _op == {opcodes['BUILD_TUPLE']}:
_items = [_stack.pop() for _ in range(_inst[1])]
_items.reverse()
_stack.append(tuple(_items))
_ip += 1
continue""",
f""" if _op == {opcodes['BUILD_SET']}:
_items = [_stack.pop() for _ in range(_inst[1])]
_items.reverse()
_stack.append(set(_items))
_ip += 1
continue""",
f""" if _op == {opcodes['BUILD_DICT']}:
_items = []
for _ in range(_inst[1]):
_value = _stack.pop()
_key = _stack.pop()
_items.append((_key, _value))
_items.reverse()
_stack.append(dict(_items))
_ip += 1
continue""",
f""" if _op == {opcodes['RETURN']}:
return _stack.pop() if _stack else None""",
f""" if _op == {opcodes['GET_ITER']}:
_stack.append(iter(_stack.pop()))
_ip += 1
continue""",
f""" if _op == {opcodes['FOR_ITER']}:
try:
_stack.append(next(_stack[-1]))
_ip += 1
except StopIteration:
_stack.pop()
_ip = _inst[1]
continue""",
f""" if _op == {opcodes['UNPACK']}:
_items = list(_stack.pop())
if len(_items) != _inst[1]:
raise ValueError('not enough values to unpack' if len(_items) < _inst[1] else 'too many values to unpack')
for _item in reversed(_items):
_stack.append(_item)
_ip += 1
continue""",
f""" if _op == {opcodes['BUILD_STRING']}:
_items = [_stack.pop() for _ in range(_inst[1])]
_items.reverse()
_stack.append(''.join(str(_item) for _item in _items))
_ip += 1
continue""",
f""" if _op == {opcodes['FORMAT_VALUE']}:
_stack.append(__pw_ab_format__(_stack.pop() if not _inst[2] else _stack.pop(-2), _inst[1], _inst[2], _stack))
_ip += 1
continue""",
]