fix_division_safe.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. """
  2. For the ``future`` package.
  3. Adds this import line:
  4. from __future__ import division
  5. at the top and changes any old-style divisions to be calls to
  6. past.utils.old_div so the code runs as before on Py2.6/2.7 and has the same
  7. behaviour on Py3.
  8. If "from __future__ import division" is already in effect, this fixer does
  9. nothing.
  10. """
  11. import re
  12. from lib2to3.fixer_util import Leaf, Node, Comma
  13. from lib2to3 import fixer_base
  14. from libfuturize.fixer_util import (token, future_import, touch_import_top,
  15. wrap_in_fn_call)
  16. def match_division(node):
  17. u"""
  18. __future__.division redefines the meaning of a single slash for division,
  19. so we match that and only that.
  20. """
  21. slash = token.SLASH
  22. return node.type == slash and not node.next_sibling.type == slash and \
  23. not node.prev_sibling.type == slash
  24. const_re = re.compile('^[0-9]*[.][0-9]*$')
  25. def is_floaty(node):
  26. return _is_floaty(node.prev_sibling) or _is_floaty(node.next_sibling)
  27. def _is_floaty(expr):
  28. if isinstance(expr, list):
  29. expr = expr[0]
  30. if isinstance(expr, Leaf):
  31. # If it's a leaf, let's see if it's a numeric constant containing a '.'
  32. return const_re.match(expr.value)
  33. elif isinstance(expr, Node):
  34. # If the expression is a node, let's see if it's a direct cast to float
  35. if isinstance(expr.children[0], Leaf):
  36. return expr.children[0].value == u'float'
  37. return False
  38. class FixDivisionSafe(fixer_base.BaseFix):
  39. # BM_compatible = True
  40. run_order = 4 # this seems to be ignored?
  41. _accept_type = token.SLASH
  42. PATTERN = """
  43. term<(not('/') any)+ '/' ((not('/') any))>
  44. """
  45. def start_tree(self, tree, name):
  46. """
  47. Skip this fixer if "__future__.division" is already imported.
  48. """
  49. super(FixDivisionSafe, self).start_tree(tree, name)
  50. self.skip = "division" in tree.future_features
  51. def match(self, node):
  52. u"""
  53. Since the tree needs to be fixed once and only once if and only if it
  54. matches, we can start discarding matches after the first.
  55. """
  56. if node.type == self.syms.term:
  57. matched = False
  58. skip = False
  59. children = []
  60. for child in node.children:
  61. if skip:
  62. skip = False
  63. continue
  64. if match_division(child) and not is_floaty(child):
  65. matched = True
  66. # Strip any leading space for the first number:
  67. children[0].prefix = u''
  68. children = [wrap_in_fn_call("old_div",
  69. children + [Comma(), child.next_sibling.clone()],
  70. prefix=node.prefix)]
  71. skip = True
  72. else:
  73. children.append(child.clone())
  74. if matched:
  75. return Node(node.type, children, fixers_applied=node.fixers_applied)
  76. return False
  77. def transform(self, node, results):
  78. if self.skip:
  79. return
  80. future_import(u"division", node)
  81. touch_import_top(u'past.utils', u'old_div', node)
  82. return results