def replace_ors(node): """Replace all occurances of "or" with calls to "__logor__".""" if node.__class__ == Or: # Or nodes can contain 2 or more nodes, we have to handle them all. We # also have to respect the premature optimisation that if any is True # then the rest aren't evaluated. Thus the expression a or b or c or d # cannot be turned into simply a.__logor__(b).__logor__(c).__logor__(d) # because this will evaluate them all. Instead we need to pass in the # expressions as strings, and eval them if needed, so our call becomes # a.__logor__('''b.__logor__("""c.__logor__('d')""")''') # We replace Ors recursively. If there's only 2 nodes to compare then # we convert the first to a boolean, then recurse our tree # transformations through the second node, then pretty print the result # into a string which we wrap with a Const node and pass into a method # call of __logor__ on the boolean of the first node from python_rewriter.base import grammar if len(node.nodes) == 2: matcher = grammar([apply(node.nodes[-1])]) return CallFunc(Getattr(CallFunc(Name('bool'),[node.nodes[0]]), \ Name('__logor__')), [Const(matcher.apply('thing',0))]) # Otherwise we build a nested series of ors (ie. "a or b or c or d" # becomes "a or (b or (c or d))") which we do recursively, and then we # apply ourselves to it recursively to complete the translation else: matcher = grammar([apply(Or(node.nodes[1:]))]) return CallFunc(Getattr(CallFunc(Name('bool'),[node.nodes[0]]), \ Name('__logor__')), [Const(matcher.apply('thing',0))]) else: # If we've not got an Or node then simply recurse try: # map our transformation to members of any lists or tuples if type(node) in [type([]), type((0, 1))]: return map(replace_ors, node) # Stmt nodes are weird: their contents is [nodes] but asList only # gives nodes, breaking the *varargs technique used below if node.__class__ == Stmt: return Stmt(map(replace_ors, node.asList())) # If we're not in a list or Stmt, return a new instance of our # class, with transformed children return node.__class__(*map(replace_ors, node.asList())) except: # If an error occurs it's because strings, numbers, None, etc. don't # have an asList method. Since they're leaves, just return them. return node
def replace_ors(node): """Replace all occurances of "or" with calls to "__logor__".""" if node.__class__ == Or: # Or nodes can contain 2 or more nodes, we have to handle them all. We # also have to respect the premature optimisation that if any is True # then the rest aren't evaluated. Thus the expression a or b or c or d # cannot be turned into simply a.__logor__(b).__logor__(c).__logor__(d) # because this will evaluate them all. Instead we need to pass in the # expressions as strings, and eval them if needed, so our call becomes # a.__logor__('''b.__logor__("""c.__logor__('d')""")''') # We replace Ors recursively. If there's only 2 nodes to compare then # we convert the first to a boolean, then recurse our tree # transformations through the second node, then pretty print the result # into a string which we wrap with a Const node and pass into a method # call of __logor__ on the boolean of the first node from python_rewriter.base import grammar if len(node.nodes) == 2: matcher = grammar([apply(node.nodes[-1])]) return CallFunc(Getattr(CallFunc(Name('bool'),[node.nodes[0]]), \ Name('__logor__')), [Const(matcher.apply('thing',0))]) # Otherwise we build a nested series of ors (ie. "a or b or c or d" # becomes "a or (b or (c or d))") which we do recursively, and then we # apply ourselves to it recursively to complete the translation else: matcher = grammar([apply(Or(node.nodes[1:]))]) return CallFunc(Getattr(CallFunc(Name('bool'),[node.nodes[0]]), \ Name('__logor__')), [Const(matcher.apply('thing',0))]) else: # If we've not got an Or node then simply recurse try: # map our transformation to members of any lists or tuples if type(node) in [type([]), type((0,1))]: return map(replace_ors, node) # Stmt nodes are weird: their contents is [nodes] but asList only # gives nodes, breaking the *varargs technique used below if node.__class__ == Stmt: return Stmt(map(replace_ors, node.asList())) # If we're not in a list or Stmt, return a new instance of our # class, with transformed children return node.__class__(*map(replace_ors, node.asList())) except: # If an error occurs it's because strings, numbers, None, etc. don't # have an asList method. Since they're leaves, just return them. return node
def replace_ands(node): """Replace all occurances of "and" with calls to "__logand__".""" if node.__class__ == And: # We've got to be careful that we don't evaluate any expressions after # one which returns False, since this would break existing code that # depends on the assumption that such expressions will not be evaluated # If we have exactly 2 expressions then we simply build a string out of # the second and send it to the boolean value of the first, which will # presumably eval it if needed from python_rewriter.base import grammar if len(node.nodes) == 2: matcher = grammar([apply(node.nodes[-1])]) return CallFunc( Getattr(CallFunc(Name('bool'), node.nodes[0]), Name('__logand__')), [Const(matcher.apply('thing', 0))]) else: matcher = grammar([apply(And(node.nodes[1:])).rec(0)]) return CallFunc( Getattr(CallFunc(Name('bool'), node.nodes[0]), Name('__logand__')), [Const(matcher.apply('thing', 0))]) else: # If we've not got an And node then simply recurse try: # map our transformation to members of any lists or tuples if type(node) in [type([]), type((0, 1))]: return map(replace_ands, node) # Stmt nodes are weird: their contents is [nodes] but asList only # gives nodes, breaking the *varargs technique used below if node.__class__ == Stmt: return Stmt(map(replace_ands, node.asList())) # If we're not in a list or Stmt, return a new instance of our # class, with transformed children return node.__class__(*map(replace_ands, node.asList())) except: # If an error occurs it's because strings, numbers, None, etc. don't # have an asList method. Since they're leaves, just return them. return node
def replace_ands(node): """Replace all occurances of "and" with calls to "__logand__".""" if node.__class__ == And: # We've got to be careful that we don't evaluate any expressions after # one which returns False, since this would break existing code that # depends on the assumption that such expressions will not be evaluated # If we have exactly 2 expressions then we simply build a string out of # the second and send it to the boolean value of the first, which will # presumably eval it if needed from python_rewriter.base import grammar if len(node.nodes) == 2: matcher = grammar([apply(node.nodes[-1])]) return CallFunc(Getattr(CallFunc(Name('bool'),node.nodes[0]), Name('__logand__')), [Const(matcher.apply('thing',0))]) else: matcher = grammar([apply(And(node.nodes[1:])).rec(0)]) return CallFunc(Getattr(CallFunc(Name('bool'),node.nodes[0]), Name('__logand__')), [Const(matcher.apply('thing',0))]) else: # If we've not got an And node then simply recurse try: # map our transformation to members of any lists or tuples if type(node) in [type([]), type((0,1))]: return map(replace_ands, node) # Stmt nodes are weird: their contents is [nodes] but asList only # gives nodes, breaking the *varargs technique used below if node.__class__ == Stmt: return Stmt(map(replace_ands, node.asList())) # If we're not in a list or Stmt, return a new instance of our # class, with transformed children return node.__class__(*map(replace_ands, node.asList())) except: # If an error occurs it's because strings, numbers, None, etc. don't # have an asList method. Since they're leaves, just return them. return node
def translate(path_or_text, initial_indent=0): """This performs the translation from Python to Diet Python. It takes in Python code (assuming the string to be a file path, falling back to treating it as Python code if it is not a valid path) and emits Diet Python code.""" # See if the given string is a valid path if os.path.exists(path_or_text): # If so then open it and read the file contents into in_text infile = open(path_or_text, "r") in_text = "\n".join([line for line in infile.readlines()]) infile.close() # Otherwise take the string contents to be in_text else: in_text = path_or_text # Wrap in try/except to give understandable error messages (PyMeta's # are full of obscure implementation details) try: # Get an Abstract Syntax Tree for the contents of in_text tree = parse(in_text) # Transform the Python AST into a Diet Python AST diet_tree = apply(tree) # print str(tree) # print str(diet_tree) # Generate (Diet) Python code to match the transformed tree from python_rewriter.base import grammar matcher = grammar([diet_tree]) diet_code, err = matcher.apply("thing", initial_indent) # print str(tree) # print # print str(diet_tree) # print return diet_code except Exception, e: sys.stderr.write(str(e) + "\n") sys.stderr.write("Unable to translate.\n") sys.exit(1)
def translate(path_or_text, initial_indent=0): """This performs the translation from Python to Diet Python. It takes in Python code (assuming the string to be a file path, falling back to treating it as Python code if it is not a valid path) and emits Diet Python code.""" # See if the given string is a valid path if os.path.exists(path_or_text): # If so then open it and read the file contents into in_text infile = open(path_or_text, 'r') in_text = '\n'.join([line for line in infile.readlines()]) infile.close() # Otherwise take the string contents to be in_text else: in_text = path_or_text # Wrap in try/except to give understandable error messages (PyMeta's # are full of obscure implementation details) try: # Get an Abstract Syntax Tree for the contents of in_text tree = parse(in_text) # Transform the Python AST into a Diet Python AST diet_tree = apply(tree) #print str(tree) #print str(diet_tree) # Generate (Diet) Python code to match the transformed tree from python_rewriter.base import grammar matcher = grammar([diet_tree]) diet_code, err = matcher.apply('thing', initial_indent) #print str(tree) #print #print str(diet_tree) #print return diet_code except Exception, e: sys.stderr.write(str(e) + '\n') sys.stderr.write('Unable to translate.\n') sys.exit(1)
def comparison_to_and(node): """Turns a series of comparisons into a nested series of independent comparisons. The behaviour is similar to logical and, including the restriction that the expression on the right should not be evaluated unless the expression on the left is true. The difficulty is that each expression should only be evaluated once, if at all. Thus if we had a comparison like: a < b < c == d >= e < f We cannot rewrite this as a < b and b < c and c == d and d >= e and e < f since in the case that they are all true, the expressions b, c, d and e will all be evaluated twice. Thus we must rely on the comparison methods to handle these correctly, and pass the right-hand expressions as strings to be evaled as needed (otherwise they would be evaluated as they are passed to the function, which would break the restriction that they should only be evaluated when the expressions to their left are true). Thus we extend the comparison method signature with a list of expressions to evaluate in the case that their first one succeeds. This should be implemented recursively, popping the head off the list, evaluating it and storing the result in a temporary variable. We can then do 2 comparisons against the temporary variable, which eliminates the need to evaluate the expressions twice. The tail of the list should be passed to this comparison so that it can recurse until it's empty. a.__lt__(b, [('__lt__','c'),('__eq__','d'),('__ge__','e'),('__lt__','f')])""" left = node.expr op = {'==':'__eq__', '!=':'__ne__', '>':'__gt__', '<':'__lt__', \ '>=':'__ge__', '<=':'__le__'}[node.ops[0][0]] ops = [(op[a[0]], a[1]) for a in node.ops] if len(ops) == 1: return CallFunc(Getattr(apply(node.expr), Name(ops[0][0])), [apply(ops[0][1])]) else: first_op = ops.pop(0) from python_rewriter.base import grammar matcher = grammar([apply(a[1])]) val, err = matcher.apply('thing', 0) return CallFunc(Getattr(apply(node.expr), Name(first_op[0])), \ [apply(first_op[1]),List([ \ Tuple([Const(a[0]),Const(val)]) for a in ops \ ])] \ )
def comparison_to_and(node): """Turns a series of comparisons into a nested series of independent comparisons. The behaviour is similar to logical and, including the restriction that the expression on the right should not be evaluated unless the expression on the left is true. The difficulty is that each expression should only be evaluated once, if at all. Thus if we had a comparison like: a < b < c == d >= e < f We cannot rewrite this as a < b and b < c and c == d and d >= e and e < f since in the case that they are all true, the expressions b, c, d and e will all be evaluated twice. Thus we must rely on the comparison methods to handle these correctly, and pass the right-hand expressions as strings to be evaled as needed (otherwise they would be evaluated as they are passed to the function, which would break the restriction that they should only be evaluated when the expressions to their left are true). Thus we extend the comparison method signature with a list of expressions to evaluate in the case that their first one succeeds. This should be implemented recursively, popping the head off the list, evaluating it and storing the result in a temporary variable. We can then do 2 comparisons against the temporary variable, which eliminates the need to evaluate the expressions twice. The tail of the list should be passed to this comparison so that it can recurse until it's empty. a.__lt__(b, [('__lt__','c'),('__eq__','d'),('__ge__','e'),('__lt__','f')])""" left = node.expr op = {"==": "__eq__", "!=": "__ne__", ">": "__gt__", "<": "__lt__", ">=": "__ge__", "<=": "__le__"}[node.ops[0][0]] ops = [(op[a[0]], a[1]) for a in node.ops] if len(ops) == 1: return CallFunc(Getattr(apply(node.expr), Name(ops[0][0])), [apply(ops[0][1])]) else: first_op = ops.pop(0) from python_rewriter.base import grammar matcher = grammar([apply(a[1])]) val, err = matcher.apply("thing", 0) return CallFunc( Getattr(apply(node.expr), Name(first_op[0])), [apply(first_op[1]), List([Tuple([Const(a[0]), Const(val)]) for a in ops])], )