Bug 820724 - Add support for more elaborate expressions for Preprocessor.py. r=ted
authorMike Hommey <mh+mozilla@glandium.org>
Wed, 12 Dec 2012 16:00:40 +0100
changeset 115793 213b4178df8c757ad11c5ed35b98cb95aa383a1e
parent 115792 0f6f0585d019d54fb9d0c15709cb4dbc6c3f65bb
child 115794 53c35671c6c2f1c9c85aec6448fa43f815c27e8b
push idunknown
push userunknown
push dateunknown
reviewersted
bugs820724
milestone20.0a1
Bug 820724 - Add support for more elaborate expressions for Preprocessor.py. r=ted
config/Expression.py
config/tests/unit-Expression.py
js/src/config/Expression.py
--- a/config/Expression.py
+++ b/config/Expression.py
@@ -3,39 +3,86 @@
 # 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 :
+  and_cond ( '||' expression ) ? ;
+and_cond:
+  test ( '&&' and_cond ) ? ;
+test:
   unary ( ( '==' | '!=' ) unary ) ? ;
 unary :
   '!'? value ;
 value :
   [0-9]+ # integer
+  | 'defined(' \w+ ')'
   | \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()
+    self.e = self.__get_logical_or()
     if self.content:
       raise Expression.ParseError, self
 
+  def __get_logical_or(self):
+    """
+    Production: and_cond ( '||' expression ) ?
+    """
+    if not len(self.content):
+      return None
+    rv = Expression.__AST("logical_op")
+    # test
+    rv.append(self.__get_logical_and())
+    self.__ignore_whitespace()
+    if self.content[:2] != '||':
+      # no logical op needed, short cut to our prime element
+      return rv[0]
+    # append operator
+    rv.append(Expression.__ASTLeaf('op', self.content[:2]))
+    self.__strip(2)
+    self.__ignore_whitespace()
+    rv.append(self.__get_logical_or())
+    self.__ignore_whitespace()
+    return rv
+
+  def __get_logical_and(self):
+    """
+    Production: test ( '&&' and_cond ) ?
+    """
+    if not len(self.content):
+      return None
+    rv = Expression.__AST("logical_op")
+    # test
+    rv.append(self.__get_equality())
+    self.__ignore_whitespace()
+    if self.content[:2] != '&&':
+      # no logical op needed, short cut to our prime element
+      return rv[0]
+    # append operator
+    rv.append(Expression.__ASTLeaf('op', self.content[:2]))
+    self.__strip(2)
+    self.__ignore_whitespace()
+    rv.append(self.__get_logical_and())
+    self.__ignore_whitespace()
+    return rv
+
   def __get_equality(self):
     """
     Production: unary ( ( '==' | '!=' ) unary ) ?
     """
     if not len(self.content):
       return None
     rv = Expression.__AST("equality")
     # unary 
@@ -63,32 +110,37 @@ class Expression:
     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+)
+    Production: ( [0-9]+ | 'defined(' \w+ ')' | \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)
+    m = re.match('defined\s*\(\s*(\w+)\s*\)', self.content)
+    if m:
+      word_len = m.end()
+      rv = Expression.__ASTLeaf('defined', m.group(1))
     else:
-      word_len = re.match('\w*', self.content).end()
+      word_len = re.match('[0-9]*', self.content).end()
       if word_len:
-        rv = Expression.__ASTLeaf('string', self.content[:word_len])
+        value = int(self.content[:word_len])
+        rv = Expression.__ASTLeaf('int', value)
       else:
-        raise Expression.ParseError, self
+        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
@@ -109,22 +161,34 @@ class Expression:
     # 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
+    # Helper function to evaluate __get_logical_and and __get_logical_or results
+    def eval_logical_op(tok):
+      left = opmap[tok[0].type](tok[0])
+      right = opmap[tok[2].type](tok[2])
+      if tok[1].value == '&&':
+        return left and right
+      elif tok[1].value == '||':
+        return left or right
+      raise Expression.ParseError, self
+
     # Mapping from token types to evaluator functions
     # Apart from (non-)equality, all these can be simple lambda forms.
     opmap = {
+      'logical_op': eval_logical_op,
       'equality': eval_equality,
       'not': lambda tok: not opmap[tok[0].type](tok[0]),
       'string': lambda tok: context[tok.value],
+      'defined': lambda tok: tok.value in context,
       'int': lambda tok: tok.value}
 
     return opmap[self.e.type](self.e);
   
   class __AST(list):
     """
     Internal class implementing Abstract Syntax Tree nodes
     """
--- a/config/tests/unit-Expression.py
+++ b/config/tests/unit-Expression.py
@@ -54,10 +54,29 @@ class TestExpression(unittest.TestCase):
   def test_equals(self):
     """ Test for the == operator"""
     self.assert_(Expression('FAIL == PASS').evaluate(self.c))
 
   def test_notequals(self):
     """ Test for the != operator"""
     self.assert_(Expression('FAIL != 1').evaluate(self.c))
 
+  def test_logical_and(self):
+    """ Test for the && operator"""
+    self.assertTrue(Expression('PASS == PASS && PASS != NOTPASS').evaluate(self.c))
+
+  def test_logical_or(self):
+    """ Test for the || operator"""
+    self.assertTrue(Expression('PASS == NOTPASS || PASS != NOTPASS').evaluate(self.c))
+
+  def test_logical_ops(self):
+    """ Test for the && and || operators precedence"""
+    # Would evaluate to false if precedence was wrong
+    self.assertTrue(Expression('PASS == PASS || PASS != NOTPASS && PASS == NOTPASS').evaluate(self.c))
+
+  def test_defined(self):
+    """ Test for the defined() value"""
+    self.assertTrue(Expression('defined(FAIL)').evaluate(self.c))
+    self.assertTrue(Expression('!defined(PASS)').evaluate(self.c))
+
+
 if __name__ == '__main__':
   mozunit.main()
--- a/js/src/config/Expression.py
+++ b/js/src/config/Expression.py
@@ -3,39 +3,86 @@
 # 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 :
+  and_cond ( '||' expression ) ? ;
+and_cond:
+  test ( '&&' and_cond ) ? ;
+test:
   unary ( ( '==' | '!=' ) unary ) ? ;
 unary :
   '!'? value ;
 value :
   [0-9]+ # integer
+  | 'defined(' \w+ ')'
   | \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()
+    self.e = self.__get_logical_or()
     if self.content:
       raise Expression.ParseError, self
 
+  def __get_logical_or(self):
+    """
+    Production: and_cond ( '||' expression ) ?
+    """
+    if not len(self.content):
+      return None
+    rv = Expression.__AST("logical_op")
+    # test
+    rv.append(self.__get_logical_and())
+    self.__ignore_whitespace()
+    if self.content[:2] != '||':
+      # no logical op needed, short cut to our prime element
+      return rv[0]
+    # append operator
+    rv.append(Expression.__ASTLeaf('op', self.content[:2]))
+    self.__strip(2)
+    self.__ignore_whitespace()
+    rv.append(self.__get_logical_or())
+    self.__ignore_whitespace()
+    return rv
+
+  def __get_logical_and(self):
+    """
+    Production: test ( '&&' and_cond ) ?
+    """
+    if not len(self.content):
+      return None
+    rv = Expression.__AST("logical_op")
+    # test
+    rv.append(self.__get_equality())
+    self.__ignore_whitespace()
+    if self.content[:2] != '&&':
+      # no logical op needed, short cut to our prime element
+      return rv[0]
+    # append operator
+    rv.append(Expression.__ASTLeaf('op', self.content[:2]))
+    self.__strip(2)
+    self.__ignore_whitespace()
+    rv.append(self.__get_logical_and())
+    self.__ignore_whitespace()
+    return rv
+
   def __get_equality(self):
     """
     Production: unary ( ( '==' | '!=' ) unary ) ?
     """
     if not len(self.content):
       return None
     rv = Expression.__AST("equality")
     # unary 
@@ -63,32 +110,37 @@ class Expression:
     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+)
+    Production: ( [0-9]+ | 'defined(' \w+ ')' | \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)
+    m = re.match('defined\s*\(\s*(\w+)\s*\)', self.content)
+    if m:
+      word_len = m.end()
+      rv = Expression.__ASTLeaf('defined', m.group(1))
     else:
-      word_len = re.match('\w*', self.content).end()
+      word_len = re.match('[0-9]*', self.content).end()
       if word_len:
-        rv = Expression.__ASTLeaf('string', self.content[:word_len])
+        value = int(self.content[:word_len])
+        rv = Expression.__ASTLeaf('int', value)
       else:
-        raise Expression.ParseError, self
+        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
@@ -109,22 +161,34 @@ class Expression:
     # 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
+    # Helper function to evaluate __get_logical_and and __get_logical_or results
+    def eval_logical_op(tok):
+      left = opmap[tok[0].type](tok[0])
+      right = opmap[tok[2].type](tok[2])
+      if tok[1].value == '&&':
+        return left and right
+      elif tok[1].value == '||':
+        return left or right
+      raise Expression.ParseError, self
+
     # Mapping from token types to evaluator functions
     # Apart from (non-)equality, all these can be simple lambda forms.
     opmap = {
+      'logical_op': eval_logical_op,
       'equality': eval_equality,
       'not': lambda tok: not opmap[tok[0].type](tok[0]),
       'string': lambda tok: context[tok.value],
+      'defined': lambda tok: tok.value in context,
       'int': lambda tok: tok.value}
 
     return opmap[self.e.type](self.e);
   
   class __AST(list):
     """
     Internal class implementing Abstract Syntax Tree nodes
     """