summaryrefslogtreecommitdiffstats
path: root/third_party/python/json-e/jsone/prattparser.py
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/python/json-e/jsone/prattparser.py')
-rw-r--r--third_party/python/json-e/jsone/prattparser.py191
1 files changed, 191 insertions, 0 deletions
diff --git a/third_party/python/json-e/jsone/prattparser.py b/third_party/python/json-e/jsone/prattparser.py
new file mode 100644
index 0000000000..5bf250a816
--- /dev/null
+++ b/third_party/python/json-e/jsone/prattparser.py
@@ -0,0 +1,191 @@
+from __future__ import absolute_import, print_function, unicode_literals
+
+import re
+from collections import namedtuple
+from .shared import TemplateError
+from .six import with_metaclass, viewitems
+
+
+class SyntaxError(TemplateError):
+
+ @classmethod
+ def unexpected(cls, got, exp):
+ exp = ', '.join(sorted(exp))
+ return cls('Found {}, expected {}'.format(got.value, exp))
+
+
+Token = namedtuple('Token', ['kind', 'value', 'start', 'end'])
+
+
+def prefix(*kinds):
+ """Decorate a method as handling prefix tokens of the given kinds"""
+ def wrap(fn):
+ try:
+ fn.prefix_kinds.extend(kinds)
+ except AttributeError:
+ fn.prefix_kinds = list(kinds)
+ return fn
+ return wrap
+
+
+def infix(*kinds):
+ """Decorate a method as handling infix tokens of the given kinds"""
+ def wrap(fn):
+ try:
+ fn.infix_kinds.extend(kinds)
+ except AttributeError:
+ fn.infix_kinds = list(kinds)
+ return fn
+ return wrap
+
+
+class PrattParserMeta(type):
+
+ def __init__(cls, name, bases, body):
+ # set up rules based on decorated methods
+ infix_rules = cls.infix_rules = {}
+ prefix_rules = cls.prefix_rules = {}
+ for prop, value in viewitems(body):
+ if hasattr(value, 'prefix_kinds'):
+ for kind in value.prefix_kinds:
+ prefix_rules[kind] = value
+ delattr(cls, prop)
+ if hasattr(value, 'infix_kinds'):
+ for kind in value.infix_kinds:
+ infix_rules[kind] = value
+ delattr(cls, prop)
+
+ # build a regular expression to generate a sequence of tokens
+ token_patterns = [
+ '({})'.format(cls.patterns.get(t, re.escape(t)))
+ for t in cls.tokens]
+ if cls.ignore:
+ token_patterns.append('(?:{})'.format(cls.ignore))
+ cls.token_re = re.compile('^(?:' + '|'.join(token_patterns) + ')')
+
+ # build a map from token kind to precedence level
+ cls.precedence_map = {
+ kind: prec + 1
+ for (prec, row) in enumerate(cls.precedence)
+ for kind in row
+ }
+
+
+class PrattParser(with_metaclass(PrattParserMeta, object)):
+
+ # regular expression for ignored input (e.g., whitespace)
+ ignore = None
+
+ # regular expressions for tokens that do not match themselves
+ patterns = {}
+
+ # all token kinds (note that order matters - the first matching token
+ # will be returned)
+ tokens = []
+
+ # precedence of tokens, as a list of lists, from lowest to highest
+ precedence = []
+
+ def parse(self, source):
+ pc = ParseContext(self, source, self._generate_tokens(source))
+ result = pc.parse()
+ # if there are any tokens remaining, that's an error..
+ token = pc.attempt()
+ if token:
+ raise SyntaxError.unexpected(token, self.infix_rules)
+ return result
+
+ def parseUntilTerminator(self, source, terminator):
+ pc = ParseContext(self, source, self._generate_tokens(source))
+ result = pc.parse()
+ token = pc.attempt()
+ if token.kind != terminator:
+ raise SyntaxError.unexpected(token, [terminator])
+ return (result, token.start)
+
+ def _generate_tokens(self, source):
+ offset = 0
+ while True:
+ start = offset
+ remainder = source[offset:]
+ mo = self.token_re.match(remainder)
+ if not mo:
+ if remainder:
+ raise SyntaxError(
+ "Unexpected input: '{}'".format(remainder))
+ break
+ offset += mo.end()
+
+ # figure out which token matched (note that idx is 0-based)
+ indexes = list(
+ filter(lambda x: x[1] is not None, enumerate(mo.groups())))
+ if indexes:
+ idx = indexes[0][0]
+ yield Token(
+ kind=self.tokens[idx],
+ value=mo.group(idx + 1), # (mo.group is 1-based)
+ start=start,
+ end=offset)
+
+
+class ParseContext(object):
+
+ def __init__(self, parser, source, token_generator):
+ self.parser = parser
+ self.source = source
+
+ self._tokens = token_generator
+ self._error = None
+
+ self._advance()
+
+ def _advance(self):
+ try:
+ self.next_token = next(self._tokens)
+ except StopIteration:
+ self.next_token = None
+ except SyntaxError as exc:
+ self._error = exc
+
+ def attempt(self, *kinds):
+ """Try to get the next token if it matches one of the kinds given,
+ otherwise returning None. If no kinds are given, any kind is
+ accepted."""
+ if self._error:
+ raise self._error
+ token = self.next_token
+ if not token:
+ return None
+ if kinds and token.kind not in kinds:
+ return None
+ self._advance()
+ return token
+
+ def require(self, *kinds):
+ """Get the next token, raising an exception if it doesn't match one of
+ the given kinds, or the input ends. If no kinds are given, returns the
+ next token of any kind."""
+ token = self.attempt()
+ if not token:
+ raise SyntaxError('Unexpected end of input')
+ if kinds and token.kind not in kinds:
+ raise SyntaxError.unexpected(token, kinds)
+ return token
+
+ def parse(self, precedence=None):
+ parser = self.parser
+ precedence = parser.precedence_map[precedence] if precedence else 0
+ token = self.require()
+ prefix_rule = parser.prefix_rules.get(token.kind)
+ if not prefix_rule:
+ raise SyntaxError.unexpected(token, parser.prefix_rules)
+ left = prefix_rule(parser, token, self)
+ while self.next_token:
+ kind = self.next_token.kind
+ if kind not in parser.infix_rules:
+ break
+ if precedence >= parser.precedence_map[kind]:
+ break
+ token = self.require()
+ left = parser.infix_rules[kind](parser, left, token, self)
+ return left