Back to index

enigmail  1.4.3
Expression.py
Go to the documentation of this file.
00001 # ***** BEGIN LICENSE BLOCK *****
00002 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
00003 #
00004 # The contents of this file are subject to the Mozilla Public License Version
00005 # 1.1 (the "License"); you may not use this file except in compliance with
00006 # the License. You may obtain a copy of the License at
00007 # http://www.mozilla.org/MPL/
00008 #
00009 # Software distributed under the License is distributed on an "AS IS" basis,
00010 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00011 # for the specific language governing rights and limitations under the
00012 # License.
00013 #
00014 # The Original Code is Mozilla build system.
00015 #
00016 # The Initial Developer of the Original Code is
00017 # Mozilla Foundation.
00018 # Portions created by the Initial Developer are Copyright (C) 2007
00019 # the Initial Developer. All Rights Reserved.
00020 #
00021 # Contributor(s):
00022 #  Axel Hecht <axel@pike.org>
00023 #
00024 # Alternatively, the contents of this file may be used under the terms of
00025 # either the GNU General Public License Version 2 or later (the "GPL"), or
00026 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027 # in which case the provisions of the GPL or the LGPL are applicable instead
00028 # of those above. If you wish to allow use of your version of this file only
00029 # under the terms of either the GPL or the LGPL, and not to allow others to
00030 # use your version of this file under the terms of the MPL, indicate your
00031 # decision by deleting the provisions above and replace them with the notice
00032 # and other provisions required by the GPL or the LGPL. If you do not delete
00033 # the provisions above, a recipient may use your version of this file under
00034 # the terms of any one of the MPL, the GPL or the LGPL.
00035 #
00036 # ***** END LICENSE BLOCK *****
00037 
00038 """
00039 Parses and evaluates simple statements for Preprocessor:
00040 
00041 Expression currently supports the following grammar, whitespace is ignored:
00042 
00043 expression :
00044   unary ( ( '==' | '!=' ) unary ) ? ;
00045 unary :
00046   '!'? value ;
00047 value :
00048   [0-9]+ # integer
00049   | \w+  # string identifier or value;
00050 """
00051 
00052 import re
00053 
00054 class Expression:
00055   def __init__(self, expression_string):
00056     """
00057     Create a new expression with this string.
00058     The expression will already be parsed into an Abstract Syntax Tree.
00059     """
00060     self.content = expression_string
00061     self.offset = 0
00062     self.__ignore_whitespace()
00063     self.e = self.__get_equality()
00064     if self.content:
00065       raise Expression.ParseError, self
00066 
00067   def __get_equality(self):
00068     """
00069     Production: unary ( ( '==' | '!=' ) unary ) ?
00070     """
00071     if not len(self.content):
00072       return None
00073     rv = Expression.__AST("equality")
00074     # unary 
00075     rv.append(self.__get_unary())
00076     self.__ignore_whitespace()
00077     if not re.match('[=!]=', self.content):
00078       # no equality needed, short cut to our prime unary
00079       return rv[0]
00080     # append operator
00081     rv.append(Expression.__ASTLeaf('op', self.content[:2]))
00082     self.__strip(2)
00083     self.__ignore_whitespace()
00084     rv.append(self.__get_unary())
00085     self.__ignore_whitespace()
00086     return rv
00087 
00088   def __get_unary(self):
00089     """
00090     Production: '!'? value
00091     """
00092     # eat whitespace right away, too
00093     not_ws = re.match('!\s*', self.content)
00094     if not not_ws:
00095       return self.__get_value()
00096     rv = Expression.__AST('not')
00097     self.__strip(not_ws.end())
00098     rv.append(self.__get_value())
00099     self.__ignore_whitespace()
00100     return rv
00101 
00102   def __get_value(self):
00103     """
00104     Production: ( [0-9]+ | \w+)
00105     Note that the order is important, and the expression is kind-of
00106     ambiguous as \w includes 0-9. One could make it unambiguous by
00107     removing 0-9 from the first char of a string literal.
00108     """
00109     rv = None
00110     word_len = re.match('[0-9]*', self.content).end()
00111     if word_len:
00112       value = int(self.content[:word_len])
00113       rv = Expression.__ASTLeaf('int', value)
00114     else:
00115       word_len = re.match('\w*', self.content).end()
00116       if word_len:
00117         rv = Expression.__ASTLeaf('string', self.content[:word_len])
00118       else:
00119         raise Expression.ParseError, self
00120     self.__strip(word_len)
00121     self.__ignore_whitespace()
00122     return rv
00123 
00124   def __ignore_whitespace(self):
00125     ws_len = re.match('\s*', self.content).end()
00126     self.__strip(ws_len)
00127     return
00128 
00129   def __strip(self, length):
00130     """
00131     Remove a given amount of chars from the input and update
00132     the offset.
00133     """
00134     self.content = self.content[length:]
00135     self.offset += length
00136   
00137   def evaluate(self, context):
00138     """
00139     Evaluate the expression with the given context
00140     """
00141     
00142     # Helper function to evaluate __get_equality results
00143     def eval_equality(tok):
00144       left = opmap[tok[0].type](tok[0])
00145       right = opmap[tok[2].type](tok[2])
00146       rv = left == right
00147       if tok[1].value == '!=':
00148         rv = not rv
00149       return rv
00150     # Mapping from token types to evaluator functions
00151     # Apart from (non-)equality, all these can be simple lambda forms.
00152     opmap = {
00153       'equality': eval_equality,
00154       'not': lambda tok: not opmap[tok[0].type](tok[0]),
00155       'string': lambda tok: context[tok.value],
00156       'int': lambda tok: tok.value}
00157 
00158     return opmap[self.e.type](self.e);
00159   
00160   class __AST(list):
00161     """
00162     Internal class implementing Abstract Syntax Tree nodes
00163     """
00164     def __init__(self, type):
00165       self.type = type
00166       super(self.__class__, self).__init__(self)
00167   
00168   class __ASTLeaf:
00169     """
00170     Internal class implementing Abstract Syntax Tree leafs
00171     """
00172     def __init__(self, type, value):
00173       self.value = value
00174       self.type = type
00175     def __str__(self):
00176       return self.value.__str__()
00177     def __repr__(self):
00178       return self.value.__repr__()
00179   
00180   class ParseError(StandardError):
00181     """
00182     Error raised when parsing fails.
00183     It has two members, offset and content, which give the offset of the
00184     error and the offending content.
00185     """
00186     def __init__(self, expression):
00187       self.offset = expression.offset
00188       self.content = expression.content[:3]
00189     def __str__(self):
00190       return 'Unexpected content at offset %i, "%s"'%(self.offset, self.content)
00191 
00192 class Context(dict):
00193   """
00194   This class holds variable values by subclassing dict, and while it
00195   truthfully reports True and False on
00196   
00197   name in context
00198   
00199   it returns the variable name itself on
00200   
00201   context["name"]
00202 
00203   to reflect the ambiguity between string literals and preprocessor
00204   variables.
00205   """
00206   def __getitem__(self, key):
00207     if key in self:
00208       return super(self.__class__, self).__getitem__(key)
00209     return key