Contact: fumanchu@aminus.org

Log in as guest/geniusql to create tickets

Changeset 159

Show
Ignore:
Timestamp:
09/01/07 19:26:18
Author:
fumanchu
Message:

Work on generator expressions, including a new JoinIterator? class (since the outmost-iterator in f_locals must be an iterator).

Files:

Legend:

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

    r158 r159  
    148148            self._func = obj 
    149149            obj = obj.func_code 
     150        if hasattr(types, 'GeneratorType') and isinstance(obj, types.GeneratorType): 
     151            obj = obj.gi_frame.f_code 
    150152         
    151153        # Copy code object attributes (if present). 
     
    972974 
    973975 
    974 class GenexpDecompiler(Visitor): 
    975     """GenexpDecompiler(obj=generator expression). 
    976      
    977     Produce decompiled Python code (a string) from a generator expression.""" 
    978      
    979     def __init__(self, obj, env=None): 
    980         if isinstance(obj, types.GeneratorType): 
    981             frame = obj.gi_frame 
    982             fcode = frame.f_code 
    983         elif isinstance(obj, types.FrameType): 
    984             frame = obj 
    985             fcode = frame.f_code 
    986          
    987         Visitor.__init__(self, fcode) 
    988          
    989         if env is None: 
    990             self.env = {} 
    991         else: 
    992             self.env = env.copy() 
    993         import __builtin__ 
    994         self.env.update(vars(__builtin__)) 
    995         self.env.update(frame.f_globals) 
    996         self.source = frame.f_locals['[outmost-iterable]'] 
    997      
    998     def code(self): 
    999         self.stage = 0 
    1000         self.ifexpr = "" 
    1001         self.attrs = "" 
    1002          
    1003         self.walk() 
    1004          
    1005         names = list(self.co_varnames)[1:] 
    1006         names.reverse() 
    1007         names = ', '.join(names) 
    1008          
    1009         if isinstance(self.source, type(iter([]))): 
    1010             self.source = "[%s]" % ", ".join([repr(x) for x in self.source]) 
    1011         return ("(%s for %s in %s if %s)" % 
    1012                 (self.attrs, names, self.source, self.ifexpr)) 
    1013      
    1014     def walk(self): 
    1015         self.stack = [] 
    1016         self.newcode = [] 
    1017         self.targets = {} 
    1018          
    1019         Visitor.walk(self) 
    1020          
    1021         if self.verbose: 
    1022             self.debug("stack:", self.stack) 
    1023      
    1024     def visit_instruction(self, op, lo=None, hi=None): 
    1025         # Get the instruction pointer for the current instruction. 
    1026         ip = self.cursor - 3 
    1027         if hi is None: 
    1028             ip += 1 
    1029             if lo is None: 
    1030                 ip += 1 
    1031          
    1032         # This is where we do folding of logical AND and OR operators. 
    1033         # The Python code just writes "a AND b", but the VM (bytecode) 
    1034         # acts more like assembly, using conditional JUMP instructions to 
    1035         # implement logical operators. The map stored in self.targets is 
    1036         # of the form: 
    1037         #     {JUMP target: [(self.stack[-1], 'and'), ...]} 
    1038         # where "JUMP target" is the instruction number of the bytecode 
    1039         # which is the target of the JUMP, and each item in the value list 
    1040         # is a tuple of (top of the calling stack, operation). 
    1041         # It's a list because a single bytecode may be the target of 
    1042         # multiple JUMP instructions. 
    1043         # See visit_JUMP_IF_FALSE / TRUE. 
    1044         terms = self.targets.get(ip) 
    1045         if terms: 
    1046             if self.stage == 3: 
    1047                 # 'terms' is storing the complete 'if' portion of the genexp. 
    1048                 clause, unnecessary_oper = terms.pop() 
    1049                 while terms: 
    1050                     term, oper = terms.pop() 
    1051                     clause = "(%s) %s (%s)" % (term, oper, clause) 
    1052                 self.ifexpr = clause 
    1053             else: 
    1054                 clause = self.stack[-1] 
    1055                 while terms: 
    1056                     term, oper = terms.pop() 
    1057                     clause = "(%s) %s (%s)" % (term, oper, clause) 
    1058                  
    1059                 # Replace TOS with the new clause, so that further 
    1060                 # combinations have access to it. 
    1061                 self.stack[-1] = clause 
    1062                 if self.verbose: 
    1063                     self.debug("clause:", clause, "\n") 
    1064                  
    1065                 if op == 1: 
    1066                     # Py2.4: The current instruction is POP_TOP, which means 
    1067                     # the previous is probably JUMP_*. If so, we don't want to 
    1068                     # pop the value we just placed on the stack and lose it. 
    1069                     # We need to replace the entry that the JUMP_* made in 
    1070                     # self.targets with our new TOS. 
    1071                     target = self.targets[self.last_target_ip] 
    1072                     target[-1] = ((clause, target[-1][1])) 
    1073                     if self.verbose: 
    1074                         self.debug("newtarget:", self.last_target_ip, target) 
    1075      
    1076     def visit_BUILD_LIST(self, lo, hi): 
    1077         terms = [str(self.stack.pop()) for i in range(lo + (hi << 8))] 
    1078         terms.reverse() 
    1079         self.stack.append("[%s]" % ", ".join(terms)) 
    1080      
    1081     def visit_BUILD_MAP(self, lo, hi): 
    1082         # We're actually going to put a non-string object on the stack here, 
    1083         # with the expectation that the next bytecodes will populate it. 
    1084         self.stack.append(MapStackObject()) 
    1085      
    1086     def visit_BUILD_TUPLE(self, lo, hi): 
    1087         terms = [str(self.stack.pop()) for i in range(lo + (hi << 8))] 
    1088         terms.reverse() 
    1089         self.stack.append("(%s)" % ", ".join(terms)) 
    1090      
    1091     def visit_CALL_FUNCTION(self, lo, hi): 
    1092         kwargs = {} 
    1093         for i in range(hi): 
    1094             val = self.stack.pop() 
    1095             key = self.stack.pop() 
    1096             kwargs[key] = val 
    1097         kwargs = ", ".join(["%s=%s" % (k, v) for k, v in kwargs.iteritems()]) 
    1098          
    1099         args = [] 
    1100         for i in xrange(lo): 
    1101             arg = self.stack.pop() 
    1102             args.append(arg) 
    1103         args.reverse() 
    1104         args = ", ".join([str(x) for x in args]) 
    1105          
    1106         if kwargs: 
    1107             args += ", " + kwargs 
    1108          
    1109         func = self.stack.pop() 
    1110         self.stack.append("%s(%s)" % (func, args)) 
    1111      
    1112     def visit_COMPARE_OP(self, lo, hi): 
    1113         term2, term1 = self.stack.pop(), self.stack.pop() 
    1114         op = cmp_op[lo + (hi << 8)] 
    1115         self.stack.append(term1 + " " + op + " " + term2) 
    1116         if self.verbose: 
    1117             self.debug(op) 
    1118      
    1119     def visit_DUP_TOP(self): 
    1120         self.stack.append(self.stack[-1]) 
    1121      
    1122     def visit_JUMP_IF_FALSE(self, lo, hi): 
    1123         # Note that self.cursor has already advanced to the next instruction. 
    1124         target = self.cursor + (lo + (hi << 8)) 
    1125         bucket = self.targets.setdefault(target, []) 
    1126         bucket.append((self.stack[-1], 'and')) 
    1127         if self.verbose: 
    1128             self.debug("target:", target, bucket) 
    1129         # Store target ip for the special code in visit_instruction 
    1130         self.last_target_ip = target 
    1131      
    1132     def visit_JUMP_IF_TRUE(self, lo, hi): 
    1133         # Note that self.cursor has already advanced to the next instruction. 
    1134         target = self.cursor + (lo + (hi << 8)) 
    1135         bucket = self.targets.setdefault(target, []) 
    1136         bucket.append((self.stack[-1], 'or')) 
    1137         if self.verbose: 
    1138             self.debug("target:", target, bucket) 
    1139         # Store target ip for the special code in visit_instruction 
    1140         self.last_target_ip = target 
    1141      
    1142     def visit_LOAD_ATTR(self, lo, hi): 
    1143         term = self.co_names[lo + (hi << 8)] 
    1144         self.stack[-1] += ("." + term) 
    1145         if self.verbose: 
    1146             self.debug(term) 
    1147      
    1148     def visit_LOAD_CONST(self, lo, hi): 
    1149         val = self.co_consts[lo + (hi << 8)] 
    1150         mod = getattr(val, "__module__", None) 
    1151         if isinstance(val, (types.FunctionType, type)): 
    1152             # The const in question is a factory function, like int or date. 
    1153             name = val.__name__ 
    1154             if name in self.env: 
    1155                 term = name 
    1156             else: 
    1157                 term = mod + "." + name 
    1158         else: 
    1159             term = repr(val) 
    1160             if mod and not mod.startswith("__"): 
    1161                 if not term.startswith(mod + "."): 
    1162                     term = mod + "." + term 
    1163         self.stack.append(term) 
    1164         if self.verbose: 
    1165             self.debug(term) 
    1166      
    1167     def visit_LOAD_FAST(self, lo, hi): 
    1168         if self.stage == 1: 
    1169             return 
    1170          
    1171         term = self.co_varnames[lo + (hi << 8)] 
    1172         self.stack.append(term) 
    1173         if self.verbose: 
    1174             self.debug(term) 
    1175      
    1176     def visit_LOAD_GLOBAL(self, lo, hi): 
    1177         self.stack.append(self.co_names[lo + (hi << 8)]) 
    1178      
    1179     def visit_POP_TOP(self): 
    1180         if self.stage < 3: 
    1181             self.stack.pop() 
    1182      
    1183     def visit_ROT_TWO(self): 
    1184         v = self.stack.pop() 
    1185         k = self.stack.pop() 
    1186         self.stack.extend([v, k]) 
    1187      
    1188     def visit_ROT_THREE(self): 
    1189         v = self.stack.pop() 
    1190         k = self.stack.pop() 
    1191         x = self.stack.pop() 
    1192         self.stack.extend([v, x, k]) 
    1193      
    1194     def visit_SLICE_PLUS_0(self): 
    1195         arg = self.stack.pop() 
    1196         self.stack.append("%s[:]" % arg) 
    1197      
    1198     def visit_SLICE_PLUS_1(self): 
    1199         args = tuple(self.stack[-2:]) 
    1200         del self.stack[-2:] 
    1201         self.stack.append("%s[%s:]" % args) 
    1202      
    1203     def visit_SLICE_PLUS_2(self): 
    1204         args = tuple(self.stack[-2:]) 
    1205         del self.stack[-2:] 
    1206         self.stack.append("%s[:%s]" % args) 
    1207      
    1208     def visit_SLICE_PLUS_3(self): 
    1209         args = tuple(self.stack[-3:]) 
    1210         del self.stack[-3:] 
    1211         self.stack.append("%s[%s:%s]" % args) 
    1212      
    1213     def visit_STORE_SUBSCR(self): 
    1214         k = self.stack.pop() 
    1215         x = self.stack.pop() 
    1216         v = self.stack.pop() 
    1217         x[k] = v 
    1218      
    1219     def visit_UNARY_CONVERT(self): 
    1220         term = self.stack.pop() 
    1221         self.stack.append("`(" + term + ")`") 
    1222      
    1223     def visit_UNARY_INVERT(self): 
    1224         term = self.stack.pop() 
    1225         self.stack.append("~(" + term + ")") 
    1226      
    1227     def visit_UNARY_NEGATIVE(self): 
    1228         term = self.stack.pop() 
    1229         self.stack.append("-(" + term + ")") 
    1230      
    1231     def visit_UNARY_NOT(self): 
    1232         term = self.stack.pop() 
    1233         self.stack.append("not (" + term + ")") 
    1234      
    1235     def visit_UNARY_POSITIVE(self): 
    1236         term = self.stack.pop() 
    1237         self.stack.append("+(" + term + ")") 
    1238      
    1239     def binary_op(self, op): 
    1240         op2, op1 = self.stack.pop(), self.stack.pop() 
    1241         self.stack.append(op1 + " " + op + " " + op2) 
    1242      
    1243     def visit_BINARY_SUBSCR(self): 
    1244         op2, op1 = self.stack.pop(), self.stack.pop() 
    1245         self.stack.append(op1 + "[" + op2 + "]") 
    1246      
    1247     def visit_SETUP_LOOP(self, lo, hi): 
    1248         self.stage = 1 
    1249      
    1250     def visit_FOR_ITER(self, lo, hi): 
    1251         self.stage = 2 
    1252         self.for_loop_address = self.cursor - 3 
    1253      
    1254     def visit_UNPACK_SEQUENCE(self, lo, hi): 
    1255         # Skip all the STORE_FAST opcodes that follow. 
    1256         numvars = lo + (hi << 8) 
    1257         self.cursor += (3 * numvars) 
    1258      
    1259     def visit_YIELD_VALUE(self): 
    1260         self.stage = 3 
    1261      
    1262     def visit_POP_BLOCK(self): 
    1263         self.attrs = self.stack.pop() 
    1264         self.stage = 4 
    1265  
    1266  
    1267 # Add visit_BINARY methods to GenexpDecompiler. 
    1268 for k, v in binary_repr.iteritems(): 
    1269     setattr(GenexpDecompiler, "visit_" + k, 
    1270             lambda self, op=v: self.binary_op(op)) 
    1271  
    1272  
    1273976 
    1274977class BranchTracker(Visitor): 
  • trunk/geniusql/sqlwriters.py

    r158 r159  
    6262        self.restriction = restriction 
    6363     
    64     def from_genexp(cls, genexp): 
     64    def from_genexp(cls, expr): 
    6565        # this would allow consumers to write: 
    6666        #     select(([t1.a, t1.b + t2.a] for t1, t2 in relation if t1.a > 13)) 
    67         code = genexp.gi_frame.f_code.co_code 
    68          
    69         # Strip leading and trailing cruft 
    70         # ['SETUP_LOOP', 62, 0, 'LOAD_FAST', 0, 0, 'FOR_ITER', 55, 0, 
    71         #  ... 
    72         #  'YIELD_VALUE', 'JUMP_ABSOLUTE', 6, 0, 
    73         #  'POP_TOP', 'JUMP_ABSOLUTE', 6, 0, 'POP_BLOCK', 
    74         #  'LOAD_CONST', 1, 0, 'RETURN_VALUE'] 
    75         code = code[9:-13] 
    76          
    77         # Here, we unpack "for t1, t2 in relation" 
    78         ##              9 UNPACK_SEQUENCE          2 
    79         ##             12 STORE_FAST               2 (t1) 
    80         ##             15 STORE_FAST               1 (t2) 
    81         numtables = code[1] + (code[2] << 8) 
    82         code = code[3 + (3 * numtables):] 
    83          
    84         # Now chop the tail, which builds the yielded list (or tuple) 
    85         ##             33 POP_TOP              
    86         ##             34 LOAD_FAST                2 (t1) 
    87         ##             37 LOAD_ATTR                3 (a) 
    88         ##             40 LOAD_FAST                2 (t1) 
    89         ##             43 LOAD_ATTR                4 (b) 
    90         ##             46 LOAD_FAST                1 (t2) 
    91         ##             49 LOAD_ATTR                3 (a) 
    92         ##             52 BINARY_ADD           
    93         ##             53 BUILD_LIST               2 
    94         lo, hi = code[-2:] 
    95         numattrs = code[1] + (code[2] << 8) 
    96         code = code[:-2] 
    97          
    98         # Now parse the restriction (e.g. "if t1.a > 13") 
    99         ##             18 LOAD_FAST                2 (t1) 
    100         ##             21 LOAD_ATTR                3 (a) 
    101         ##             24 LOAD_CONST               0 (13) 
    102         ##             27 COMPARE_OP               4 (>) 
    103         ##             30 JUMP_IF_FALSE           27 (to 60) 
    104         choke 
     67        from geniusql import genexp 
     68        dep = genexp.GenexpParser(expr) 
     69        dep.verbose = True 
     70        dep.walk() 
     71         
     72        newq = cls(dep.relation, dep.attributes, dep.restriction) 
     73        return newq 
    10574    from_genexp = classmethod(from_genexp) 
    10675 
     
    154123     
    155124    def __iter__(self): 
    156         def gentables(): 
    157             if isinstance(self.table1, Join): 
    158                 for t in iter(self.table1): 
    159                     yield t 
    160             else: 
    161                 yield self.table1 
    162             if isinstance(self.table2, Join): 
    163                 for t in iter(self.table2): 
    164                     yield t 
    165             else: 
    166                 yield self.table2 
    167         return gentables() 
     125        return JoinIterator(self) 
    168126     
    169127    def __lshift__(self, other): 
     
    188146                self.leftbiased == other.leftbiased and 
    189147                self.path == other.path) 
     148 
     149 
     150class JoinIterator(object): 
     151     
     152    def __init__(self, join): 
     153        if isinstance(join.table1, Join): 
     154            t1 = list(join.table1) 
     155        else: 
     156            t1 = [join.table1] 
     157         
     158        if isinstance(join.table2, Join): 
     159            t2 = list(join.table2) 
     160        else: 
     161            t2 = [join.table2] 
     162         
     163        self.tableiter = iter(t1 + t2) 
     164     
     165    def __iter__(self): 
     166        return self 
     167     
     168    def next(self): 
     169        return self.tableiter.next() 
    190170 
    191171