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/.
6 Parses and evaluates simple statements for Preprocessor:
8 Expression currently supports the following grammar, whitespace is ignored:
11 and_cond ( '||' expression ) ? ;
13 test ( '&&' and_cond ) ? ;
15 unary ( ( '==' | '!=' ) unary ) ? ;
21 | \w+ # string identifier or value;
27 def __init__(self
, expression_string
):
29 Create a new expression with this string.
30 The expression will already be parsed into an Abstract Syntax Tree.
32 self
.content
= expression_string
34 self
.__ignore
_whitespace
()
35 self
.e
= self
.__get
_logical
_or
()
37 raise Expression
.ParseError
, self
39 def __get_logical_or(self
):
41 Production: and_cond ( '||' expression ) ?
43 if not len(self
.content
):
45 rv
= Expression
.__AST
("logical_op")
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
53 rv
.append(Expression
.__ASTLeaf
('op', self
.content
[:2]))
55 self
.__ignore
_whitespace
()
56 rv
.append(self
.__get
_logical
_or
())
57 self
.__ignore
_whitespace
()
60 def __get_logical_and(self
):
62 Production: test ( '&&' and_cond ) ?
64 if not len(self
.content
):
66 rv
= Expression
.__AST
("logical_op")
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
74 rv
.append(Expression
.__ASTLeaf
('op', self
.content
[:2]))
76 self
.__ignore
_whitespace
()
77 rv
.append(self
.__get
_logical
_and
())
78 self
.__ignore
_whitespace
()
81 def __get_equality(self
):
83 Production: unary ( ( '==' | '!=' ) unary ) ?
85 if not len(self
.content
):
87 rv
= Expression
.__AST
("equality")
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
95 rv
.append(Expression
.__ASTLeaf
('op', self
.content
[:2]))
97 self
.__ignore
_whitespace
()
98 rv
.append(self
.__get
_unary
())
99 self
.__ignore
_whitespace
()
102 def __get_unary(self
):
104 Production: '!'? value
106 # eat whitespace right away, too
107 not_ws
= re
.match('!\s*', self
.content
)
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
()
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.
124 m
= re
.match('defined\s*\(\s*(\w+)\s*\)', self
.content
)
127 rv
= Expression
.__ASTLeaf
('defined', m
.group(1))
129 word_len
= re
.match('[0-9]*', self
.content
).end()
131 value
= int(self
.content
[:word_len
])
132 rv
= Expression
.__ASTLeaf
('int', value
)
134 word_len
= re
.match('\w*', self
.content
).end()
136 rv
= Expression
.__ASTLeaf
('string', self
.content
[:word_len
])
138 raise Expression
.ParseError
, self
139 self
.__strip
(word_len
)
140 self
.__ignore
_whitespace
()
143 def __ignore_whitespace(self
):
144 ws_len
= re
.match('\s*', self
.content
).end()
148 def __strip(self
, length
):
150 Remove a given amount of chars from the input and update
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])
166 if tok
[1].value
== '!=':
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
== '||':
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.
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
);
193 Internal class implementing Abstract Syntax Tree nodes
195 def __init__(self
, type):
197 super(self
.__class
__, self
).__init
__(self
)
201 Internal class implementing Abstract Syntax Tree leafs
203 def __init__(self
, type, value
):
207 return self
.value
.__str
__()
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]
221 return 'Unexpected content at offset {0}, "{1}"'.format(self
.offset
,
226 This class holds variable values by subclassing dict, and while it
227 truthfully reports True and False on
231 it returns the variable name itself on
235 to reflect the ambiguity between string literals and preprocessor
238 def __getitem__(self
, key
):
240 return super(self
.__class
__, self
).__getitem
__(key
)