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):
def wrap(fn):
try:
fn.prefix_kinds.extend(kinds)
except AttributeError:
fn.prefix_kinds = list(kinds)
return fn
return wrap
def infix(*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):
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)
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) + ')')
cls.precedence_map = {
kind: prec + 1
for (prec, row) in enumerate(cls.precedence)
for kind in row
}
class PrattParser(with_metaclass(PrattParserMeta, object)):
ignore = None
patterns = {}
tokens = []
precedence = []
def parse(self, source):
pc = ParseContext(self, source, self._generate_tokens(source))
result = pc.parse()
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()
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), 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):
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):
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