adhoc_expression.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. from .expressions import Expression
  2. def expression(callable, rule_name, grammar):
  3. """Turn a plain callable into an Expression.
  4. The callable can be of this simple form::
  5. def foo(text, pos):
  6. '''If this custom expression matches starting at text[pos], return
  7. the index where it stops matching. Otherwise, return None.'''
  8. if the expression matched:
  9. return end_pos
  10. If there child nodes to return, return a tuple::
  11. return end_pos, children
  12. If the expression doesn't match at the given ``pos`` at all... ::
  13. return None
  14. If your callable needs to make sub-calls to other rules in the grammar or
  15. do error reporting, it can take this form, gaining additional arguments::
  16. def foo(text, pos, cache, error, grammar):
  17. # Call out to other rules:
  18. node = grammar['another_rule'].match_core(text, pos, cache, error)
  19. ...
  20. # Return values as above.
  21. The return value of the callable, if an int or a tuple, will be
  22. automatically transmuted into a :class:`~parsimonious.Node`. If it returns
  23. a Node-like class directly, it will be passed through unchanged.
  24. :arg rule_name: The rule name to attach to the resulting
  25. :class:`~parsimonious.Expression`
  26. :arg grammar: The :class:`~parsimonious.Grammar` this expression will be a
  27. part of, to make delegating to other rules possible
  28. """
  29. # Resolve unbound methods; allows grammars to use @staticmethod custom rules
  30. # https://stackoverflow.com/questions/41921255/staticmethod-object-is-not-callable
  31. if ismethoddescriptor(callable) and hasattr(callable, '__func__'):
  32. callable = callable.__func__
  33. num_args = len(getfullargspec(callable).args)
  34. if ismethod(callable):
  35. # do not count the first argument (typically 'self') for methods
  36. num_args -= 1
  37. if num_args == 2:
  38. is_simple = True
  39. elif num_args == 5:
  40. is_simple = False
  41. else:
  42. raise RuntimeError("Custom rule functions must take either 2 or 5 "
  43. "arguments, not %s." % num_args)
  44. class AdHocExpression(Expression):
  45. def _uncached_match(self, text, pos, cache, error):
  46. result = (callable(text, pos) if is_simple else
  47. callable(text, pos, cache, error, grammar))
  48. if isinstance(result, int):
  49. end, children = result, None
  50. elif isinstance(result, tuple):
  51. end, children = result
  52. else:
  53. # Node or None
  54. return result
  55. return Node(self, text, pos, end, children=children)
  56. def _as_rhs(self):
  57. return '{custom function "%s"}' % callable.__name__
  58. return AdHocExpression(name=rule_name)