tco.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. from macropy.core.macros import *
  2. from macropy.experimental.pattern import macros, switch, _matching, ClassMatcher, NameMatcher
  3. from macropy.core.hquotes import macros, hq
  4. __all__ = ['tco']
  5. macros = Macros()
  6. in_tc_stack = [False]
  7. @singleton
  8. class TcoIgnore:
  9. pass
  10. @singleton
  11. class TcoCall:
  12. pass
  13. def trampoline(func, args, kwargs):
  14. """
  15. Repeatedly apply a function until it returns a value.
  16. The function may return (tco.CALL, func, args, kwargs) or (tco.IGNORE,
  17. func, args, kwargs) or just a value.
  18. """
  19. ignoring = False
  20. while True:
  21. # We can only set this if we know it will be immediately unset by func
  22. if hasattr(func, 'tco'):
  23. in_tc_stack[0] = True
  24. result = func(*args, **kwargs)
  25. # for performance reasons, do not use pattern matching here
  26. if isinstance(result, tuple):
  27. if result[0] is TcoCall:
  28. func = result[1]
  29. args = result[2]
  30. kwargs = result[3]
  31. continue
  32. elif result[0] is TcoIgnore:
  33. ignoring = True
  34. func = result[1]
  35. args = result[2]
  36. kwargs = result[3]
  37. continue
  38. if ignoring:
  39. return None
  40. else:
  41. return result
  42. def trampoline_decorator(func):
  43. import functools
  44. @functools.wraps(func)
  45. def trampolined(*args, **kwargs):
  46. if in_tc_stack[0]:
  47. in_tc_stack[0] = False
  48. return func(*args, **kwargs)
  49. in_tc_stack.append(False)
  50. return trampoline(func, args, kwargs)
  51. trampolined.tco = True
  52. return trampolined
  53. @macros.decorator
  54. def tco(tree, **kw):
  55. @Walker
  56. # Replace returns of calls
  57. def return_replacer(tree, **kw):
  58. with switch(tree):
  59. if Return(value=Call(
  60. func=func,
  61. args=args,
  62. starargs=starargs,
  63. kwargs=kwargs)):
  64. if starargs:
  65. with hq as code:
  66. # get rid of starargs
  67. return (TcoCall,
  68. ast[func],
  69. ast[List(args, Load())] + list(ast[starargs]),
  70. ast[kwargs or Dict([],[])])
  71. else:
  72. with hq as code:
  73. return (TcoCall,
  74. ast[func],
  75. ast[List(args, Load())],
  76. ast[kwargs or Dict([], [])])
  77. return code
  78. else:
  79. return tree
  80. # Replace calls (that aren't returned) which happen to be in a tail-call
  81. # position
  82. def replace_tc_pos(node):
  83. with switch(node):
  84. if Expr(value=Call(
  85. func=func,
  86. args=args,
  87. starargs=starargs,
  88. kwargs=kwargs)):
  89. if starargs:
  90. with hq as code:
  91. # get rid of starargs
  92. return (TcoIgnore,
  93. ast[func],
  94. ast[List(args, Load())] + list(ast[starargs]),
  95. ast[kwargs or Dict([],[])])
  96. else:
  97. with hq as code:
  98. return (TcoIgnore,
  99. ast[func],
  100. ast[List(args, Load())],
  101. ast[kwargs or Dict([], [])])
  102. return code
  103. elif If(test=test, body=body, orelse=orelse):
  104. body[-1] = replace_tc_pos(body[-1])
  105. if orelse:
  106. orelse[-1] = replace_tc_pos(orelse[-1])
  107. return If(test, body, orelse)
  108. else:
  109. return node
  110. tree = return_replacer.recurse(tree)
  111. tree.decorator_list = ([hq[trampoline_decorator]] +
  112. tree.decorator_list)
  113. tree.body[-1] = replace_tc_pos(tree.body[-1])
  114. return tree