Contact: fumanchu@aminus.org

Log in as guest/geniusql to create tickets

Changeset 105

Show
Ignore:
Timestamp:
07/29/07 02:46:39
Author:
fumanchu
Message:

Hackish first attempt at a GenExpDecompiler?.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/geniusql/codewalk.py

    r104 r105  
    44and related data, is placed into the public domain. 
    55 
    6 The orginal author is Robert Brewer, Amor Ministries
     6The orginal author is Robert Brewer
    77 
    88THIS SOFTWARE IS PROVIDED AS-IS, WITHOUT WARRANTY 
     
    2020import operator 
    2121import sets 
    22 from types import CodeType, FunctionType, MethodType 
     22import types 
    2323 
    2424from compiler.consts import * 
     
    5353#          names, varnames, filename, name, firstlineno, 
    5454#          lnotab[, freevars[, cellvars]]) 
    55 _derefblock = CodeType(0, 0, 1, 3, ''.join(map(chr, _deref_bytecode)), 
     55_derefblock = types.CodeType(0, 0, 1, 3, ''.join(map(chr, _deref_bytecode)), 
    5656                       (None,), ('cell',), (), '', '', 2, '', ('cell',)) 
    5757def deref_cell(cell): 
    5858    """Return the value of 'cell' (an object from a func_closure).""" 
    5959    # FunctionType(code, globals[, name[, argdefs[, closure]]]) 
    60     return FunctionType(_derefblock, {}, "", (), (cell,))() 
     60    return types.FunctionType(_derefblock, {}, "", (), (cell,))() 
    6161 
    6262def make_closure(*args): 
     
    133133         
    134134        # Distill supplied 'obj' arg to a code block string. 
    135         if isinstance(obj, MethodType): 
     135        if isinstance(obj, types.MethodType): 
    136136            obj = obj.im_func 
    137         if isinstance(obj, FunctionType): 
     137        if isinstance(obj, types.FunctionType): 
    138138            self._func = obj 
    139139            obj = obj.func_code 
     
    297297        self.walk() 
    298298        codestr = ''.join(map(chr, self.newcode)) 
    299         return CodeType(self.co_argcount, self.co_nlocals, self.co_stacksize, 
     299        return types.CodeType(self.co_argcount, self.co_nlocals, self.co_stacksize, 
    300300                        # Notice co_consts should *not* be safe_tupled. 
    301301                        self.co_flags, codestr, tuple(self.co_consts), 
     
    313313                newname = '' 
    314314            co = self.code_object() 
    315             return FunctionType(co, {}, newname) 
     315            return types.FunctionType(co, {}, newname) 
    316316        else: 
    317317            if newname is None: 
    318318                newname = f.func_name 
    319319            co = self.code_object() 
    320             return FunctionType(co, f.func_globals, newname, 
     320            return types.FunctionType(co, f.func_globals, newname, 
    321321                                f.func_defaults, f.func_closure) 
    322322     
     
    509509        # Assert CO_NOFREE, since all free vars should have been made constant. 
    510510        self.co_flags |= CO_NOFREE 
    511         co = CodeType(self.co_argcount, self.co_nlocals, self.stack.maxsize, 
     511        co = types.CodeType(self.co_argcount, self.co_nlocals, self.stack.maxsize, 
    512512                      self.co_flags, codestr, tuple(self.co_consts), 
    513513                      safe_tuple(self.co_names), safe_tuple(self.co_varnames), 
     
    524524                newname = '' 
    525525            co = self.code_object() 
    526             return FunctionType(co, {}, newname) 
     526            return types.FunctionType(co, {}, newname) 
    527527        else: 
    528528            if newname is None: 
     
    530530            co = self.code_object() 
    531531            # All cells should be dereferenced, so force func_closure to None. 
    532             return FunctionType(co, f.func_globals, newname, f.func_defaults) 
     532            return types.FunctionType(co, f.func_globals, newname, f.func_defaults) 
    533533     
    534534    def reduce(self, number_of_terms, transform=None, overwrite_length=None): 
     
    865865        val = self.co_consts[lo + (hi << 8)] 
    866866        mod = getattr(val, "__module__", None) 
    867         if isinstance(val, (FunctionType, type)): 
     867        if isinstance(val, (types.FunctionType, type)): 
    868868            # The const in question is a factory function, like int or date. 
    869869            name = val.__name__ 
     
    963963 
    964964 
     965class GenexpDecompiler(Visitor): 
     966    """GenexpDecompiler(obj=generator expression). 
     967     
     968    Produce decompiled Python code (a string) from a generator expression.""" 
     969     
     970    def __init__(self, obj, env=None): 
     971        if isinstance(obj, types.GeneratorType): 
     972            frame = obj.gi_frame 
     973            fcode = frame.f_code 
     974        elif isinstance(obj, types.FrameType): 
     975            frame = obj 
     976            fcode = frame.f_code 
     977         
     978        Visitor.__init__(self, fcode) 
     979         
     980        if env is None: 
     981            self.env = {} 
     982        else: 
     983            self.env = env.copy() 
     984        import __builtin__ 
     985        self.env.update(vars(__builtin__)) 
     986        self.env.update(frame.f_globals) 
     987        self.source = frame.f_locals['[outmost-iterable]'] 
     988     
     989    def code(self): 
     990        self.stage = 0 
     991        self.ifexpr = "" 
     992        self.attrs = "" 
     993         
     994        self.walk() 
     995         
     996        names = list(self.co_varnames)[1:] 
     997        names.reverse() 
     998        names = ', '.join(names) 
     999         
     1000        if isinstance(self.source, type(iter([]))): 
     1001            self.source = "[%s]" % ", ".join([repr(x) for x in self.source]) 
     1002        return ("(%s for %s in %s if %s)" % 
     1003                (self.attrs, names, self.source, self.ifexpr)) 
     1004     
     1005    def walk(self): 
     1006        self.stack = [] 
     1007        self.newcode = [] 
     1008        self.targets = {} 
     1009         
     1010        Visitor.walk(self) 
     1011         
     1012        if self.verbose: 
     1013            self.debug("stack:", self.stack) 
     1014     
     1015    def visit_instruction(self, op, lo=None, hi=None): 
     1016        # Get the instruction pointer for the current instruction. 
     1017        ip = self.cursor - 3 
     1018        if hi is None: 
     1019            ip += 1 
     1020            if lo is None: 
     1021                ip += 1 
     1022         
     1023        # This is where we do folding of logical AND and OR operators. 
     1024        # The Python code just writes "a AND b", but the VM (bytecode) 
     1025        # acts more like assembly, using conditional JUMP instructions to 
     1026        # implement logical operators. The map stored in self.targets is 
     1027        # of the form: 
     1028        #     {JUMP target: [(self.stack[-1], 'and'), ...]} 
     1029        # where "JUMP target" is the instruction number of the bytecode 
     1030        # which is the target of the JUMP, and each item in the value list 
     1031        # is a tuple of (top of the calling stack, operation). 
     1032        # It's a list because a single bytecode may be the target of 
     1033        # multiple JUMP instructions. 
     1034        # See visit_JUMP_IF_FALSE / TRUE. 
     1035        terms = self.targets.get(ip) 
     1036        if terms: 
     1037            if self.stage == 3: 
     1038                # 'terms' is storing the complete 'if' portion of the genexp. 
     1039                clause, unnecessary_oper = terms.pop() 
     1040                while terms: 
     1041                    term, oper = terms.pop() 
     1042                    clause = "(%s) %s (%s)" % (term, oper, clause) 
     1043                self.ifexpr = clause 
     1044            else: 
     1045                clause = self.stack[-1] 
     1046                while terms: 
     1047                    term, oper = terms.pop() 
     1048                    clause = "(%s) %s (%s)" % (term, oper, clause) 
     1049                 
     1050                # Replace TOS with the new clause, so that further 
     1051                # combinations have access to it. 
     1052                self.stack[-1] = clause 
     1053                if self.verbose: 
     1054                    self.debug("clause:", clause, "\n") 
     1055                 
     1056                if op == 1: 
     1057                    # Py2.4: The current instruction is POP_TOP, which means 
     1058                    # the previous is probably JUMP_*. If so, we don't want to 
     1059                    # pop the value we just placed on the stack and lose it. 
     1060                    # We need to replace the entry that the JUMP_* made in 
     1061                    # self.targets with our new TOS. 
     1062                    target = self.targets[self.last_target_ip] 
     1063                    target[-1] = ((clause, target[-1][1])) 
     1064                    if self.verbose: 
     1065                        self.debug("newtarget:", self.last_target_ip, target) 
     1066     
     1067    def visit_BUILD_LIST(self, lo, hi): 
     1068        terms = [str(self.stack.pop()) for i in range(lo + (hi << 8))] 
     1069        terms.reverse() 
     1070        self.stack.append("[%s]" % ", ".join(terms)) 
     1071     
     1072    def visit_BUILD_MAP(self, lo, hi): 
     1073        # We're actually going to put a non-string object on the stack here, 
     1074        # with the expectation that the next bytecodes will populate it. 
     1075        self.stack.append(MapStackObject()) 
     1076     
     1077    def visit_BUILD_TUPLE(self, lo, hi): 
     1078        terms = [str(self.stack.pop()) for i in range(lo + (hi << 8))] 
     1079        terms.reverse() 
     1080        self.stack.append("(%s)" % ", ".join(terms)) 
     1081     
     1082    def visit_CALL_FUNCTION(self, lo, hi): 
     1083        kwargs = {} 
     1084        for i in range(hi): 
     1085            val = self.stack.pop() 
     1086            key = self.stack.pop() 
     1087            kwargs[key] = val 
     1088        kwargs = ", ".join(["%s=%s" % (k, v) for k, v in kwargs.iteritems()]) 
     1089         
     1090        args = [] 
     1091        for i in xrange(lo): 
     1092            arg = self.stack.pop() 
     1093            args.append(arg) 
     1094        args.reverse() 
     1095        args = ", ".join([str(x) for x in args]) 
     1096         
     1097        if kwargs: 
     1098            args += ", " + kwargs 
     1099         
     1100        func = self.stack.pop() 
     1101        self.stack.append("%s(%s)" % (func, args)) 
     1102     
     1103    def visit_COMPARE_OP(self, lo, hi): 
     1104        term2, term1 = self.stack.pop(), self.stack.pop() 
     1105        op = cmp_op[lo + (hi << 8)] 
     1106        self.stack.append(term1 + " " + op + " " + term2) 
     1107        if self.verbose: 
     1108            self.debug(op) 
     1109     
     1110    def visit_DUP_TOP(self): 
     1111        self.stack.append(self.stack[-1]) 
     1112     
     1113    def visit_JUMP_IF_FALSE(self, lo, hi): 
     1114        # Note that self.cursor has already advanced to the next instruction. 
     1115        target = self.cursor + (lo + (hi << 8)) 
     1116        bucket = self.targets.setdefault(target, []) 
     1117        bucket.append((self.stack[-1], 'and')) 
     1118        if self.verbose: 
     1119            self.debug("target:", target, bucket) 
     1120        # Store target ip for the special code in visit_instruction 
     1121        self.last_target_ip = target 
     1122     
     1123    def visit_JUMP_IF_TRUE(self, lo, hi): 
     1124        # Note that self.cursor has already advanced to the next instruction. 
     1125        target = self.cursor + (lo + (hi << 8)) 
     1126        bucket = self.targets.setdefault(target, []) 
     1127        bucket.append((self.stack[-1], 'or')) 
     1128        if self.verbose: 
     1129            self.debug("target:", target, bucket) 
     1130        # Store target ip for the special code in visit_instruction 
     1131        self.last_target_ip = target 
     1132     
     1133    def visit_LOAD_ATTR(self, lo, hi): 
     1134        term = self.co_names[lo + (hi << 8)] 
     1135        self.stack[-1] += ("." + term) 
     1136        if self.verbose: 
     1137            self.debug(term) 
     1138     
     1139    def visit_LOAD_CONST(self, lo, hi): 
     1140        val = self.co_consts[lo + (hi << 8)] 
     1141        mod = getattr(val, "__module__", None) 
     1142        if isinstance(val, (types.FunctionType, type)): 
     1143            # The const in question is a factory function, like int or date. 
     1144            name = val.__name__ 
     1145            if name in self.env: 
     1146                term = name 
     1147            else: 
     1148                term = mod + "." + name 
     1149        else: 
     1150            term = repr(val) 
     1151            if mod and not mod.startswith("__"): 
     1152                if not term.startswith(mod + "."): 
     1153                    term = mod + "." + term 
     1154        self.stack.append(term) 
     1155        if self.verbose: 
     1156            self.debug(term) 
     1157     
     1158    def visit_LOAD_FAST(self, lo, hi): 
     1159        if self.stage == 1: 
     1160            return 
     1161         
     1162        term = self.co_varnames[lo + (hi << 8)] 
     1163        self.stack.append(term) 
     1164        if self.verbose: 
     1165            self.debug(term) 
     1166     
     1167    def visit_LOAD_GLOBAL(self, lo, hi): 
     1168        self.stack.append(self.co_names[lo + (hi << 8)]) 
     1169     
     1170    def visit_POP_TOP(self): 
     1171        if self.stage < 3: 
     1172            self.stack.pop() 
     1173     
     1174    def visit_ROT_TWO(self): 
     1175        v = self.stack.pop() 
     1176        k = self.stack.pop() 
     1177        self.stack.extend([v, k]) 
     1178     
     1179    def visit_ROT_THREE(self): 
     1180        v = self.stack.pop() 
     1181        k = self.stack.pop() 
     1182        x = self.stack.pop() 
     1183        self.stack.extend([v, x, k]) 
     1184     
     1185    def visit_SLICE_PLUS_0(self): 
     1186        arg = self.stack.pop() 
     1187        self.stack.append("%s[:]" % arg) 
     1188     
     1189    def visit_SLICE_PLUS_1(self): 
     1190        args = tuple(self.stack[-2:]) 
     1191        del self.stack[-2:] 
     1192        self.stack.append("%s[%s:]" % args) 
     1193     
     1194    def visit_SLICE_PLUS_2(self): 
     1195        args = tuple(self.stack[-2:]) 
     1196        del self.stack[-2:] 
     1197        self.stack.append("%s[:%s]" % args) 
     1198     
     1199    def visit_SLICE_PLUS_3(self): 
     1200        args = tuple(self.stack[-3:]) 
     1201        del self.stack[-3:] 
     1202        self.stack.append("%s[%s:%s]" % args) 
     1203     
     1204    def visit_STORE_SUBSCR(self): 
     1205        k = self.stack.pop() 
     1206        x = self.stack.pop() 
     1207        v = self.stack.pop() 
     1208        x[k] = v 
     1209     
     1210    def visit_UNARY_CONVERT(self): 
     1211        term = self.stack.pop() 
     1212        self.stack.append("`(" + term + ")`") 
     1213     
     1214    def visit_UNARY_INVERT(self): 
     1215        term = self.stack.pop() 
     1216        self.stack.append("~(" + term + ")") 
     1217     
     1218    def visit_UNARY_NEGATIVE(self): 
     1219        term = self.stack.pop() 
     1220        self.stack.append("-(" + term + ")") 
     1221     
     1222    def visit_UNARY_NOT(self): 
     1223        term = self.stack.pop() 
     1224        self.stack.append("not (" + term + ")") 
     1225     
     1226    def visit_UNARY_POSITIVE(self): 
     1227        term = self.stack.pop() 
     1228        self.stack.append("+(" + term + ")") 
     1229     
     1230    def binary_op(self, op): 
     1231        op2, op1 = self.stack.pop(), self.stack.pop() 
     1232        self.stack.append(op1 + " " + op + " " + op2) 
     1233     
     1234    def visit_BINARY_SUBSCR(self): 
     1235        op2, op1 = self.stack.pop(), self.stack.pop() 
     1236        self.stack.append(op1 + "[" + op2 + "]") 
     1237     
     1238    def visit_SETUP_LOOP(self, lo, hi): 
     1239        self.stage = 1 
     1240     
     1241    def visit_FOR_ITER(self, lo, hi): 
     1242        self.stage = 2 
     1243        self.for_loop_address = self.cursor - 3 
     1244     
     1245    def visit_UNPACK_SEQUENCE(self, lo, hi): 
     1246        # Skip all the STORE_FAST opcodes that follow. 
     1247        numvars = lo + (hi << 8) 
     1248        self.cursor += (3 * numvars) 
     1249     
     1250    def visit_YIELD_VALUE(self): 
     1251        self.stage = 3 
     1252     
     1253    def visit_POP_BLOCK(self): 
     1254        self.attrs = self.stack.pop() 
     1255        self.stage = 4 
     1256 
     1257 
     1258# Add visit_BINARY methods to GenexpDecompiler. 
     1259for k, v in binary_repr.iteritems(): 
     1260    setattr(GenexpDecompiler, "visit_" + k, 
     1261            lambda self, op=v: self.binary_op(op)) 
     1262 
     1263 
     1264 
    9651265class BranchTracker(Visitor): 
    9661266    """BranchTracker(obj=[func|co|str|list]). 
  • trunk/geniusql/test/test_codewalk.py

    r104 r105  
    253253        e = lambda x, **kw: x.Date > x.newdate(kw['Year'], 1, 1) 
    254254        self.assertEqual(codewalk.KeywordInspector(e).kwargs(), ["Year"]) 
     255     
     256    def test_GenexpDecompiler(self): 
     257        t = ([([t1.a, t2.b + 3] for t1, t2 in [1, 2, 3] if t1.x == 0), 
     258              '([t1.a, t2.b + 3] for t1, t2 in [1, 2, 3] if t1.x == 0)'], 
     259             ) 
     260        for g, s in t: 
     261            g = codewalk.GenexpDecompiler(g) 
     262##            g.verbose = True 
     263            r = g.code() 
     264            self.assertEqual(r, s) 
    255265 
    256266