Expression.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. # ***** BEGIN LICENSE BLOCK *****
  2. # Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3. #
  4. # The contents of this file are subject to the Mozilla Public License Version
  5. # 1.1 (the "License"); you may not use this file except in compliance with
  6. # the License. You may obtain a copy of the License at
  7. # http://www.mozilla.org/MPL/
  8. #
  9. # Software distributed under the License is distributed on an "AS IS" basis,
  10. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11. # for the specific language governing rights and limitations under the
  12. # License.
  13. #
  14. # The Original Code is Mozilla build system.
  15. #
  16. # The Initial Developer of the Original Code is
  17. # Mozilla Foundation.
  18. # Portions created by the Initial Developer are Copyright (C) 2007
  19. # the Initial Developer. All Rights Reserved.
  20. #
  21. # Contributor(s):
  22. # Axel Hecht <axel@pike.org>
  23. #
  24. # Alternatively, the contents of this file may be used under the terms of
  25. # either the GNU General Public License Version 2 or later (the "GPL"), or
  26. # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27. # in which case the provisions of the GPL or the LGPL are applicable instead
  28. # of those above. If you wish to allow use of your version of this file only
  29. # under the terms of either the GPL or the LGPL, and not to allow others to
  30. # use your version of this file under the terms of the MPL, indicate your
  31. # decision by deleting the provisions above and replace them with the notice
  32. # and other provisions required by the GPL or the LGPL. If you do not delete
  33. # the provisions above, a recipient may use your version of this file under
  34. # the terms of any one of the MPL, the GPL or the LGPL.
  35. #
  36. # ***** END LICENSE BLOCK *****
  37. """
  38. Parses and evaluates simple statements for Preprocessor:
  39. Expression currently supports the following grammar, whitespace is ignored:
  40. expression :
  41. unary ( ( '==' | '!=' ) unary ) ? ;
  42. unary :
  43. '!'? value ;
  44. value :
  45. [0-9]+ # integer
  46. | \w+ # string identifier or value;
  47. """
  48. import re
  49. class Expression:
  50. def __init__(self, expression_string):
  51. """
  52. Create a new expression with this string.
  53. The expression will already be parsed into an Abstract Syntax Tree.
  54. """
  55. self.content = expression_string
  56. self.offset = 0
  57. self.__ignore_whitespace()
  58. self.e = self.__get_equality()
  59. if self.content:
  60. raise Expression.ParseError, self
  61. def __get_equality(self):
  62. """
  63. Production: unary ( ( '==' | '!=' ) unary ) ?
  64. """
  65. if not len(self.content):
  66. return None
  67. rv = Expression.__AST("equality")
  68. # unary
  69. rv.append(self.__get_unary())
  70. self.__ignore_whitespace()
  71. if not re.match('[=!]=', self.content):
  72. # no equality needed, short cut to our prime unary
  73. return rv[0]
  74. # append operator
  75. rv.append(Expression.__ASTLeaf('op', self.content[:2]))
  76. self.__strip(2)
  77. self.__ignore_whitespace()
  78. rv.append(self.__get_unary())
  79. self.__ignore_whitespace()
  80. return rv
  81. def __get_unary(self):
  82. """
  83. Production: '!'? value
  84. """
  85. # eat whitespace right away, too
  86. not_ws = re.match('!\s*', self.content)
  87. if not not_ws:
  88. return self.__get_value()
  89. rv = Expression.__AST('not')
  90. self.__strip(not_ws.end())
  91. rv.append(self.__get_value())
  92. self.__ignore_whitespace()
  93. return rv
  94. def __get_value(self):
  95. """
  96. Production: ( [0-9]+ | \w+)
  97. Note that the order is important, and the expression is kind-of
  98. ambiguous as \w includes 0-9. One could make it unambiguous by
  99. removing 0-9 from the first char of a string literal.
  100. """
  101. rv = None
  102. word_len = re.match('[0-9]*', self.content).end()
  103. if word_len:
  104. rv = Expression.__ASTLeaf('int', int(self.content[:word_len]))
  105. else:
  106. word_len = re.match('\w*', self.content).end()
  107. if word_len:
  108. rv = Expression.__ASTLeaf('string', self.content[:word_len])
  109. else:
  110. raise Expression.ParseError, self
  111. self.__strip(word_len)
  112. self.__ignore_whitespace()
  113. return rv
  114. def __ignore_whitespace(self):
  115. ws_len = re.match('\s*', self.content).end()
  116. self.__strip(ws_len)
  117. return
  118. def __strip(self, length):
  119. """
  120. Remove a given amount of chars from the input and update
  121. the offset.
  122. """
  123. self.content = self.content[length:]
  124. self.offset += length
  125. def evaluate(self, context):
  126. """
  127. Evaluate the expression with the given context
  128. """
  129. # Helper function to evaluate __get_equality results
  130. def eval_equality(tok):
  131. left = opmap[tok[0].type](tok[0])
  132. right = opmap[tok[2].type](tok[2])
  133. rv = left == right
  134. if tok[1].value == '!=':
  135. rv = not rv
  136. return rv
  137. # Mapping from token types to evaluator functions
  138. # Apart from (non-)equality, all these can be simple lambda forms.
  139. opmap = {
  140. 'equality': eval_equality,
  141. 'not': lambda tok: not opmap[tok[0].type](tok[0]),
  142. 'string': lambda tok: context[tok.value],
  143. 'int': lambda tok: tok.value}
  144. return opmap[self.e.type](self.e);
  145. class __AST(list):
  146. """
  147. Internal class implementing Abstract Syntax Tree nodes
  148. """
  149. def __init__(self, type):
  150. self.type = type
  151. super(self.__class__, self).__init__(self)
  152. class __ASTLeaf:
  153. """
  154. Internal class implementing Abstract Syntax Tree leafs
  155. """
  156. def __init__(self, type, value):
  157. self.value = value
  158. self.type = type
  159. def __str__(self):
  160. return self.value.__str__()
  161. def __repr__(self):
  162. return self.value.__repr__()
  163. class ParseError(StandardError):
  164. """
  165. Error raised when parsing fails.
  166. It has two members, offset and content, which give the offset of the
  167. error and the offending content.
  168. """
  169. def __init__(self, expression):
  170. self.offset = expression.offset
  171. self.content = expression.content[:3]
  172. def __str__(self):
  173. return 'Unexpected content at offset %i, "%s"'%(self.offset, self.content)
  174. class Context(dict):
  175. """
  176. This class holds variable values by subclassing dict, and while it
  177. truthfully reports True and False on
  178. name in context
  179. it returns the variable name itself on
  180. context["name"]
  181. to reflect the ambiguity between string literals and preprocessor
  182. variables.
  183. """
  184. def __getitem__(self, key):
  185. if key in self:
  186. return super(self.__class__, self).__getitem__(key)
  187. return key