forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			254 lines
		
	
	
	
		
			9.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			254 lines
		
	
	
	
		
			9.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
from .AST import Primitive, UnaryOp, ContextValue, BinOp, FunctionCall, ValueAccess, Object, List
 | 
						|
from collections import namedtuple
 | 
						|
import re
 | 
						|
from .shared import TemplateError
 | 
						|
 | 
						|
Token = namedtuple('Token', ['kind', 'value', 'start', 'end'])
 | 
						|
 | 
						|
expectedTokens = ["!", "(", "+", "-", "[", "false", "identifier", "null", "number", "string", "true", "{"]
 | 
						|
 | 
						|
 | 
						|
class SyntaxError(TemplateError):
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def unexpected(cls, got, exp):
 | 
						|
        exp = ', '.join(sorted(exp))
 | 
						|
        return cls('Found: {} token, expected one of: {}'.format(got.value, exp))
 | 
						|
 | 
						|
 | 
						|
class Parser(object):
 | 
						|
    def __init__(self, source, tokenizer):
 | 
						|
        self.tokens = tokenizer.generate_tokens(source)
 | 
						|
        self.source = source
 | 
						|
        self.current_token = next(self.tokens)
 | 
						|
        self.unaryOpTokens = ["-", "+", "!"]
 | 
						|
        self.primitivesTokens = ["number", "null", "true", "false", "string"]
 | 
						|
        self.operatorsByPriority = [["||"], ["&&"], ["in"], ["==", "!="], ["<", ">", "<=", ">="], ["+", "-"],
 | 
						|
                                    ["*", "/"], ["**"]]
 | 
						|
 | 
						|
    def take_token(self, *kinds):
 | 
						|
        if not self.current_token:
 | 
						|
            raise SyntaxError('Unexpected end of input')
 | 
						|
        if kinds and self.current_token.kind not in kinds:
 | 
						|
            raise SyntaxError.unexpected(self.current_token, kinds)
 | 
						|
        try:
 | 
						|
            self.current_token = next(self.tokens)
 | 
						|
        except StopIteration:
 | 
						|
            self.current_token = None
 | 
						|
        except SyntaxError as exc:
 | 
						|
            raise exc
 | 
						|
 | 
						|
    def parse(self, level=0):
 | 
						|
        """  expr : logicalAnd (OR logicalAnd)* """
 | 
						|
        """  logicalAnd : inStatement (AND inStatement)* """
 | 
						|
        """  inStatement : equality (IN equality)*  """
 | 
						|
        """  equality : comparison (EQUALITY | INEQUALITY  comparison)* """
 | 
						|
        """  comparison : addition (LESS | GREATER | LESSEQUAL | GREATEREQUAL addition)* """
 | 
						|
        """  addition : multiplication (PLUS | MINUS multiplication)* """
 | 
						|
        """  multiplication : exponentiation (MUL | DIV exponentiation)* """
 | 
						|
        """  exponentiation : propertyAccessOrFunc (EXP exponentiation)* """
 | 
						|
        if level == len(self.operatorsByPriority) - 1:
 | 
						|
            node = self.parse_property_access_or_func()
 | 
						|
            token = self.current_token
 | 
						|
 | 
						|
            while token is not None and token.kind in self.operatorsByPriority[level]:
 | 
						|
                self.take_token(token.kind)
 | 
						|
                node = BinOp(token, self.parse(level), node)
 | 
						|
                token = self.current_token
 | 
						|
        else:
 | 
						|
            node = self.parse(level + 1)
 | 
						|
            token = self.current_token
 | 
						|
 | 
						|
            while token is not None and token.kind in self.operatorsByPriority[level]:
 | 
						|
                self.take_token(token.kind)
 | 
						|
                node = BinOp(token, node, self.parse(level + 1))
 | 
						|
                token = self.current_token
 | 
						|
 | 
						|
        return node
 | 
						|
 | 
						|
    def parse_property_access_or_func(self):
 | 
						|
        """  propertyAccessOrFunc : unit (accessWithBrackets | DOT id | functionCall)* """
 | 
						|
        node = self.parse_unit()
 | 
						|
        token = self.current_token
 | 
						|
        operators = ["[", "(", "."]
 | 
						|
        while token is not None and token.kind in operators:
 | 
						|
            if token.kind == "[":
 | 
						|
                node = self.parse_access_with_brackets(node)
 | 
						|
            elif token.kind == ".":
 | 
						|
                token = self.current_token
 | 
						|
                self.take_token(".")
 | 
						|
                right_part = Primitive(self.current_token)
 | 
						|
                self.take_token("identifier")
 | 
						|
                node = BinOp(token, node, right_part)
 | 
						|
            elif token.kind == "(":
 | 
						|
                node = self.parse_function_call(node)
 | 
						|
            token = self.current_token
 | 
						|
        return node
 | 
						|
 | 
						|
    def parse_unit(self):
 | 
						|
        # unit : unaryOp unit | primitives | contextValue | LPAREN expr RPAREN | list | object
 | 
						|
        token = self.current_token
 | 
						|
        if self.current_token is None:
 | 
						|
            raise SyntaxError('Unexpected end of input')
 | 
						|
        node = None
 | 
						|
 | 
						|
        if token.kind in self.unaryOpTokens:
 | 
						|
            self.take_token(token.kind)
 | 
						|
            node = UnaryOp(token, self.parse_unit())
 | 
						|
        elif token.kind in self.primitivesTokens:
 | 
						|
            self.take_token(token.kind)
 | 
						|
            node = Primitive(token)
 | 
						|
        elif token.kind == "identifier":
 | 
						|
            self.take_token(token.kind)
 | 
						|
            node = ContextValue(token)
 | 
						|
        elif token.kind == "(":
 | 
						|
            self.take_token("(")
 | 
						|
            node = self.parse()
 | 
						|
            if node is None:
 | 
						|
                raise SyntaxError.unexpected(self.current_token, expectedTokens)
 | 
						|
            self.take_token(")")
 | 
						|
        elif token.kind == "[":
 | 
						|
            node = self.parse_list()
 | 
						|
        elif token.kind == "{":
 | 
						|
            node = self.parse_object()
 | 
						|
 | 
						|
        return node
 | 
						|
 | 
						|
    def parse_function_call(self, name):
 | 
						|
        """functionCall: LPAREN (expr ( COMMA expr)*)? RPAREN"""
 | 
						|
        args = []
 | 
						|
        token = self.current_token
 | 
						|
        self.take_token("(")
 | 
						|
 | 
						|
        if self.current_token.kind != ")":
 | 
						|
            node = self.parse()
 | 
						|
            args.append(node)
 | 
						|
 | 
						|
            while self.current_token is not None and self.current_token.kind == ",":
 | 
						|
                if args[-1] is None:
 | 
						|
                    raise SyntaxError.unexpected(self.current_token, expectedTokens)
 | 
						|
                self.take_token(",")
 | 
						|
                node = self.parse()
 | 
						|
                args.append(node)
 | 
						|
 | 
						|
        self.take_token(")")
 | 
						|
        node = FunctionCall(token, name, args)
 | 
						|
 | 
						|
        return node
 | 
						|
 | 
						|
    def parse_list(self):
 | 
						|
        """  list: LSQAREBRAKET (expr (COMMA expr)*)? RSQAREBRAKET """
 | 
						|
        arr = []
 | 
						|
        token = self.current_token
 | 
						|
        self.take_token("[")
 | 
						|
 | 
						|
        if self.current_token != "]":
 | 
						|
            node = self.parse()
 | 
						|
            arr.append(node)
 | 
						|
 | 
						|
            while self.current_token and self.current_token.kind == ",":
 | 
						|
                if arr[-1] is None:
 | 
						|
                    raise SyntaxError.unexpected(self.current_token, expectedTokens)
 | 
						|
                self.take_token(",")
 | 
						|
                node = self.parse()
 | 
						|
                arr.append(node)
 | 
						|
 | 
						|
        self.take_token("]")
 | 
						|
        node = List(token, arr)
 | 
						|
 | 
						|
        return node
 | 
						|
 | 
						|
    def parse_access_with_brackets(self, node):
 | 
						|
        """  valueAccess : LSQAREBRAKET expr |(expr? COLON expr?)  RSQAREBRAKET)"""
 | 
						|
        left = None
 | 
						|
        right = None
 | 
						|
        is_interval = False
 | 
						|
        token = self.current_token
 | 
						|
        self.take_token("[")
 | 
						|
        if self.current_token.kind == "]":
 | 
						|
            raise SyntaxError.unexpected(self.current_token, expectedTokens)
 | 
						|
        if self.current_token.kind != ":":
 | 
						|
            left = self.parse()
 | 
						|
        if self.current_token.kind == ":":
 | 
						|
            is_interval = True
 | 
						|
            self.take_token(":")
 | 
						|
        if self.current_token.kind != "]":
 | 
						|
            right = self.parse()
 | 
						|
 | 
						|
        if is_interval and right is None and self.current_token.kind != "]":
 | 
						|
            raise SyntaxError.unexpected(self.current_token, expectedTokens)
 | 
						|
 | 
						|
        self.take_token("]")
 | 
						|
        node = ValueAccess(token, node, is_interval, left, right)
 | 
						|
 | 
						|
        return node
 | 
						|
 | 
						|
    def parse_object(self):
 | 
						|
        # """   object : LCURLYBRACE ( STR | ID COLON expr (COMMA STR | ID COLON expr)*)?
 | 
						|
        # RCURLYBRACE """
 | 
						|
        obj = {}
 | 
						|
        objToken = self.current_token
 | 
						|
        self.take_token("{")
 | 
						|
        token = self.current_token
 | 
						|
 | 
						|
        while token is not None and (token.kind == "string" or token.kind == "identifier"):
 | 
						|
            key = token.value
 | 
						|
            if token.kind == "string":
 | 
						|
                key = parse_string(key)
 | 
						|
            self.take_token(token.kind)
 | 
						|
            self.take_token(":")
 | 
						|
            value = self.parse()
 | 
						|
            if value is None:
 | 
						|
                raise SyntaxError.unexpected(self.current_token, expectedTokens)
 | 
						|
            obj[key] = value
 | 
						|
            if self.current_token and self.current_token.kind == "}":
 | 
						|
                break
 | 
						|
            else:
 | 
						|
                self.take_token(",")
 | 
						|
            token = self.current_token
 | 
						|
 | 
						|
        self.take_token("}")
 | 
						|
        node = Object(objToken, obj)
 | 
						|
 | 
						|
        return node
 | 
						|
 | 
						|
 | 
						|
def parse_string(string):
 | 
						|
    return string[1:-1]
 | 
						|
 | 
						|
 | 
						|
class Tokenizer(object):
 | 
						|
    def __init__(self, ignore, patterns, tokens):
 | 
						|
        self.ignore = ignore
 | 
						|
        self.patterns = patterns
 | 
						|
        self.tokens = tokens
 | 
						|
        # build a regular expression to generate a sequence of tokens
 | 
						|
        token_patterns = [
 | 
						|
            '({})'.format(self.patterns.get(t, re.escape(t)))
 | 
						|
            for t in self.tokens]
 | 
						|
        if self.ignore:
 | 
						|
            token_patterns.append('(?:{})'.format(self.ignore))
 | 
						|
        self.token_re = re.compile('^(?:' + '|'.join(token_patterns) + ')')
 | 
						|
 | 
						|
    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 for '{}' at '{}'".format(source, remainder))
 | 
						|
                break
 | 
						|
            offset += mo.end()
 | 
						|
 | 
						|
            # figure out which token matched (note that idx is 0-based)
 | 
						|
            indexes = [idx for idx, grp in enumerate(mo.groups()) if grp is not None]
 | 
						|
            if indexes:
 | 
						|
                idx = indexes[0]
 | 
						|
                yield Token(
 | 
						|
                    kind=self.tokens[idx],
 | 
						|
                    value=mo.group(idx + 1),  # (mo.group is 1-based)
 | 
						|
                    start=start,
 | 
						|
                    end=offset)
 |