/js/src/config/Expression.py
Python | 176 lines | 166 code | 1 blank | 9 comment | 0 complexity | 1fad7460f099978191961296f7664ae6 MD5 | raw file
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
- """
- Parses and evaluates simple statements for Preprocessor:
- Expression currently supports the following grammar, whitespace is ignored:
- expression :
- unary ( ( '==' | '!=' ) unary ) ? ;
- unary :
- '!'? value ;
- value :
- [0-9]+ # integer
- | \w+ # string identifier or value;
- """
- import re
- class Expression:
- def __init__(self, expression_string):
- """
- Create a new expression with this string.
- The expression will already be parsed into an Abstract Syntax Tree.
- """
- self.content = expression_string
- self.offset = 0
- self.__ignore_whitespace()
- self.e = self.__get_equality()
- if self.content:
- raise Expression.ParseError, self
- def __get_equality(self):
- """
- Production: unary ( ( '==' | '!=' ) unary ) ?
- """
- if not len(self.content):
- return None
- rv = Expression.__AST("equality")
- # unary
- rv.append(self.__get_unary())
- self.__ignore_whitespace()
- if not re.match('[=!]=', self.content):
- # no equality needed, short cut to our prime unary
- return rv[0]
- # append operator
- rv.append(Expression.__ASTLeaf('op', self.content[:2]))
- self.__strip(2)
- self.__ignore_whitespace()
- rv.append(self.__get_unary())
- self.__ignore_whitespace()
- return rv
- def __get_unary(self):
- """
- Production: '!'? value
- """
- # eat whitespace right away, too
- not_ws = re.match('!\s*', self.content)
- if not not_ws:
- return self.__get_value()
- rv = Expression.__AST('not')
- self.__strip(not_ws.end())
- rv.append(self.__get_value())
- self.__ignore_whitespace()
- return rv
- def __get_value(self):
- """
- Production: ( [0-9]+ | \w+)
- Note that the order is important, and the expression is kind-of
- ambiguous as \w includes 0-9. One could make it unambiguous by
- removing 0-9 from the first char of a string literal.
- """
- rv = None
- word_len = re.match('[0-9]*', self.content).end()
- if word_len:
- value = int(self.content[:word_len])
- rv = Expression.__ASTLeaf('int', value)
- else:
- word_len = re.match('\w*', self.content).end()
- if word_len:
- rv = Expression.__ASTLeaf('string', self.content[:word_len])
- else:
- raise Expression.ParseError, self
- self.__strip(word_len)
- self.__ignore_whitespace()
- return rv
- def __ignore_whitespace(self):
- ws_len = re.match('\s*', self.content).end()
- self.__strip(ws_len)
- return
- def __strip(self, length):
- """
- Remove a given amount of chars from the input and update
- the offset.
- """
- self.content = self.content[length:]
- self.offset += length
-
- def evaluate(self, context):
- """
- Evaluate the expression with the given context
- """
-
- # Helper function to evaluate __get_equality results
- def eval_equality(tok):
- left = opmap[tok[0].type](tok[0])
- right = opmap[tok[2].type](tok[2])
- rv = left == right
- if tok[1].value == '!=':
- rv = not rv
- return rv
- # Mapping from token types to evaluator functions
- # Apart from (non-)equality, all these can be simple lambda forms.
- opmap = {
- 'equality': eval_equality,
- 'not': lambda tok: not opmap[tok[0].type](tok[0]),
- 'string': lambda tok: context[tok.value],
- 'int': lambda tok: tok.value}
- return opmap[self.e.type](self.e);
-
- class __AST(list):
- """
- Internal class implementing Abstract Syntax Tree nodes
- """
- def __init__(self, type):
- self.type = type
- super(self.__class__, self).__init__(self)
-
- class __ASTLeaf:
- """
- Internal class implementing Abstract Syntax Tree leafs
- """
- def __init__(self, type, value):
- self.value = value
- self.type = type
- def __str__(self):
- return self.value.__str__()
- def __repr__(self):
- return self.value.__repr__()
-
- class ParseError(StandardError):
- """
- Error raised when parsing fails.
- It has two members, offset and content, which give the offset of the
- error and the offending content.
- """
- def __init__(self, expression):
- self.offset = expression.offset
- self.content = expression.content[:3]
- def __str__(self):
- return 'Unexpected content at offset %i, "%s"'%(self.offset, self.content)
- class Context(dict):
- """
- This class holds variable values by subclassing dict, and while it
- truthfully reports True and False on
-
- name in context
-
- it returns the variable name itself on
-
- context["name"]
- to reflect the ambiguity between string literals and preprocessor
- variables.
- """
- def __getitem__(self, key):
- if key in self:
- return super(self.__class__, self).__getitem__(key)
- return key