Bug 865244 - Implement AudioContext.destination.maxChannelCount. r=ehsan
[gecko.git] / config / Expression.py
blob3e91ae46858cf1d228d282f4b7f736553cc9181f
1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 """
6 Parses and evaluates simple statements for Preprocessor:
8 Expression currently supports the following grammar, whitespace is ignored:
10 expression :
11 and_cond ( '||' expression ) ? ;
12 and_cond:
13 test ( '&&' and_cond ) ? ;
14 test:
15 unary ( ( '==' | '!=' ) unary ) ? ;
16 unary :
17 '!'? value ;
18 value :
19 [0-9]+ # integer
20 | 'defined(' \w+ ')'
21 | \w+ # string identifier or value;
22 """
24 import re
26 class Expression:
27 def __init__(self, expression_string):
28 """
29 Create a new expression with this string.
30 The expression will already be parsed into an Abstract Syntax Tree.
31 """
32 self.content = expression_string
33 self.offset = 0
34 self.__ignore_whitespace()
35 self.e = self.__get_logical_or()
36 if self.content:
37 raise Expression.ParseError, self
39 def __get_logical_or(self):
40 """
41 Production: and_cond ( '||' expression ) ?
42 """
43 if not len(self.content):
44 return None
45 rv = Expression.__AST("logical_op")
46 # test
47 rv.append(self.__get_logical_and())
48 self.__ignore_whitespace()
49 if self.content[:2] != '||':
50 # no logical op needed, short cut to our prime element
51 return rv[0]
52 # append operator
53 rv.append(Expression.__ASTLeaf('op', self.content[:2]))
54 self.__strip(2)
55 self.__ignore_whitespace()
56 rv.append(self.__get_logical_or())
57 self.__ignore_whitespace()
58 return rv
60 def __get_logical_and(self):
61 """
62 Production: test ( '&&' and_cond ) ?
63 """
64 if not len(self.content):
65 return None
66 rv = Expression.__AST("logical_op")
67 # test
68 rv.append(self.__get_equality())
69 self.__ignore_whitespace()
70 if self.content[:2] != '&&':
71 # no logical op needed, short cut to our prime element
72 return rv[0]
73 # append operator
74 rv.append(Expression.__ASTLeaf('op', self.content[:2]))
75 self.__strip(2)
76 self.__ignore_whitespace()
77 rv.append(self.__get_logical_and())
78 self.__ignore_whitespace()
79 return rv
81 def __get_equality(self):
82 """
83 Production: unary ( ( '==' | '!=' ) unary ) ?
84 """
85 if not len(self.content):
86 return None
87 rv = Expression.__AST("equality")
88 # unary
89 rv.append(self.__get_unary())
90 self.__ignore_whitespace()
91 if not re.match('[=!]=', self.content):
92 # no equality needed, short cut to our prime unary
93 return rv[0]
94 # append operator
95 rv.append(Expression.__ASTLeaf('op', self.content[:2]))
96 self.__strip(2)
97 self.__ignore_whitespace()
98 rv.append(self.__get_unary())
99 self.__ignore_whitespace()
100 return rv
102 def __get_unary(self):
104 Production: '!'? value
106 # eat whitespace right away, too
107 not_ws = re.match('!\s*', self.content)
108 if not not_ws:
109 return self.__get_value()
110 rv = Expression.__AST('not')
111 self.__strip(not_ws.end())
112 rv.append(self.__get_value())
113 self.__ignore_whitespace()
114 return rv
116 def __get_value(self):
118 Production: ( [0-9]+ | 'defined(' \w+ ')' | \w+ )
119 Note that the order is important, and the expression is kind-of
120 ambiguous as \w includes 0-9. One could make it unambiguous by
121 removing 0-9 from the first char of a string literal.
123 rv = None
124 m = re.match('defined\s*\(\s*(\w+)\s*\)', self.content)
125 if m:
126 word_len = m.end()
127 rv = Expression.__ASTLeaf('defined', m.group(1))
128 else:
129 word_len = re.match('[0-9]*', self.content).end()
130 if word_len:
131 value = int(self.content[:word_len])
132 rv = Expression.__ASTLeaf('int', value)
133 else:
134 word_len = re.match('\w*', self.content).end()
135 if word_len:
136 rv = Expression.__ASTLeaf('string', self.content[:word_len])
137 else:
138 raise Expression.ParseError, self
139 self.__strip(word_len)
140 self.__ignore_whitespace()
141 return rv
143 def __ignore_whitespace(self):
144 ws_len = re.match('\s*', self.content).end()
145 self.__strip(ws_len)
146 return
148 def __strip(self, length):
150 Remove a given amount of chars from the input and update
151 the offset.
153 self.content = self.content[length:]
154 self.offset += length
156 def evaluate(self, context):
158 Evaluate the expression with the given context
161 # Helper function to evaluate __get_equality results
162 def eval_equality(tok):
163 left = opmap[tok[0].type](tok[0])
164 right = opmap[tok[2].type](tok[2])
165 rv = left == right
166 if tok[1].value == '!=':
167 rv = not rv
168 return rv
169 # Helper function to evaluate __get_logical_and and __get_logical_or results
170 def eval_logical_op(tok):
171 left = opmap[tok[0].type](tok[0])
172 right = opmap[tok[2].type](tok[2])
173 if tok[1].value == '&&':
174 return left and right
175 elif tok[1].value == '||':
176 return left or right
177 raise Expression.ParseError, self
179 # Mapping from token types to evaluator functions
180 # Apart from (non-)equality, all these can be simple lambda forms.
181 opmap = {
182 'logical_op': eval_logical_op,
183 'equality': eval_equality,
184 'not': lambda tok: not opmap[tok[0].type](tok[0]),
185 'string': lambda tok: context[tok.value],
186 'defined': lambda tok: tok.value in context,
187 'int': lambda tok: tok.value}
189 return opmap[self.e.type](self.e);
191 class __AST(list):
193 Internal class implementing Abstract Syntax Tree nodes
195 def __init__(self, type):
196 self.type = type
197 super(self.__class__, self).__init__(self)
199 class __ASTLeaf:
201 Internal class implementing Abstract Syntax Tree leafs
203 def __init__(self, type, value):
204 self.value = value
205 self.type = type
206 def __str__(self):
207 return self.value.__str__()
208 def __repr__(self):
209 return self.value.__repr__()
211 class ParseError(StandardError):
213 Error raised when parsing fails.
214 It has two members, offset and content, which give the offset of the
215 error and the offending content.
217 def __init__(self, expression):
218 self.offset = expression.offset
219 self.content = expression.content[:3]
220 def __str__(self):
221 return 'Unexpected content at offset {0}, "{1}"'.format(self.offset,
222 self.content)
224 class Context(dict):
226 This class holds variable values by subclassing dict, and while it
227 truthfully reports True and False on
229 name in context
231 it returns the variable name itself on
233 context["name"]
235 to reflect the ambiguity between string literals and preprocessor
236 variables.
238 def __getitem__(self, key):
239 if key in self:
240 return super(self.__class__, self).__getitem__(key)
241 return key