Files
2026-04-29 00:17:18 -05:00

94 lines
4.5 KiB
Python

from __future__ import annotations
import ast
import copy
import random
def _is_pure(node: ast.AST) -> bool:
if isinstance(node, ast.Name):
return True
if isinstance(node, ast.Constant):
return isinstance(node.value, (int, bool))
if isinstance(node, ast.Attribute):
return _is_pure(node.value)
if isinstance(node, ast.Subscript):
return _is_pure(node.value) and _is_pure(node.slice)
if isinstance(node, ast.UnaryOp) and isinstance(node.op, (ast.USub, ast.UAdd, ast.Invert)):
return _is_pure(node.operand)
return False
def _dup(node: ast.AST) -> ast.AST:
return copy.deepcopy(node)
class MBATransformer(ast.NodeTransformer):
def __init__(self, rng: random.Random, prob: float=0.7):
self.rng = rng
self.prob = prob
def visit_BinOp(self, node: ast.BinOp) -> ast.AST:
self.generic_visit(node)
if not (_is_pure(node.left) and _is_pure(node.right)):
return node
if self.rng.random() > self.prob:
return node
a, b = (node.left, node.right)
op = node.op
if isinstance(op, ast.BitXor):
return self._xor(a, b)
if isinstance(op, ast.BitOr):
return self._or(a, b)
if isinstance(op, ast.BitAnd):
return self._and(a, b)
if isinstance(op, ast.LShift) and isinstance(b, ast.Constant) and isinstance(b.value, int):
return self._lshift(a, b.value)
if isinstance(op, ast.RShift) and isinstance(b, ast.Constant) and isinstance(b.value, int):
return self._rshift(a, b.value)
return node
def visit_UnaryOp(self, node: ast.UnaryOp) -> ast.AST:
self.generic_visit(node)
if not _is_pure(node.operand):
return node
if self.rng.random() > self.prob:
return node
if isinstance(node.op, ast.Invert):
return self._invert(node.operand)
return node
def _xor(self, a: ast.AST, b: ast.AST) -> ast.AST:
choice = self.rng.choice(('subor', 'ornotand', 'addsub'))
if choice == 'subor':
return ast.BinOp(left=ast.BinOp(left=_dup(a), op=ast.BitOr(), right=_dup(b)), op=ast.Sub(), right=ast.BinOp(left=_dup(a), op=ast.BitAnd(), right=_dup(b)))
if choice == 'ornotand':
return ast.BinOp(left=ast.BinOp(left=_dup(a), op=ast.BitOr(), right=_dup(b)), op=ast.BitAnd(), right=ast.UnaryOp(op=ast.Invert(), operand=ast.BinOp(left=_dup(a), op=ast.BitAnd(), right=_dup(b))))
return ast.BinOp(left=ast.BinOp(left=_dup(a), op=ast.Add(), right=_dup(b)), op=ast.Sub(), right=ast.BinOp(left=ast.Constant(value=2), op=ast.Mult(), right=ast.BinOp(left=_dup(a), op=ast.BitAnd(), right=_dup(b))))
def _or(self, a: ast.AST, b: ast.AST) -> ast.AST:
if self.rng.random() < 0.5:
return ast.BinOp(left=ast.BinOp(left=_dup(a), op=ast.Add(), right=_dup(b)), op=ast.Sub(), right=ast.BinOp(left=_dup(a), op=ast.BitAnd(), right=_dup(b)))
return ast.BinOp(left=ast.BinOp(left=_dup(a), op=ast.BitXor(), right=_dup(b)), op=ast.BitXor(), right=ast.BinOp(left=_dup(a), op=ast.BitAnd(), right=_dup(b)))
def _and(self, a: ast.AST, b: ast.AST) -> ast.AST:
if self.rng.random() < 0.5:
return ast.BinOp(left=ast.BinOp(left=_dup(a), op=ast.Add(), right=_dup(b)), op=ast.Sub(), right=ast.BinOp(left=_dup(a), op=ast.BitOr(), right=_dup(b)))
return ast.UnaryOp(op=ast.Invert(), operand=ast.BinOp(left=ast.UnaryOp(op=ast.Invert(), operand=_dup(a)), op=ast.BitOr(), right=ast.UnaryOp(op=ast.Invert(), operand=_dup(b))))
def _invert(self, a: ast.AST) -> ast.AST:
return ast.BinOp(left=ast.UnaryOp(op=ast.USub(), operand=_dup(a)), op=ast.Sub(), right=ast.Constant(value=1))
def _lshift(self, a: ast.AST, n: int) -> ast.AST:
if n < 0 or n > 60:
return ast.BinOp(left=_dup(a), op=ast.LShift(), right=ast.Constant(value=n))
return ast.BinOp(left=_dup(a), op=ast.Mult(), right=ast.Constant(value=1 << n))
def _rshift(self, a: ast.AST, n: int) -> ast.AST:
if n < 0 or n > 60:
return ast.BinOp(left=_dup(a), op=ast.RShift(), right=ast.Constant(value=n))
return ast.BinOp(left=_dup(a), op=ast.FloorDiv(), right=ast.Constant(value=1 << n))
def visit_match_case(self, node: ast.match_case) -> ast.AST:
if node.guard is not None:
node.guard = self.visit(node.guard)
node.body = [self.visit(stmt) for stmt in node.body]
return node