Reference Guide  2.5.0
expression.py
1 # -----------------------------------------------------------------------------
2 # (c) The copyright relating to this work is owned jointly by the Crown,
3 # Met Office and NERC 2014.
4 # However, it has been created with the help of the GungHo Consortium,
5 # whose members are identified at https://puma.nerc.ac.uk/trac/GungHo/wiki
6 # -----------------------------------------------------------------------------
7 # Author D. Ham Imperial College
8 # Modified by R. Ford and A. R. Porter, STFC Daresbury Laboratory
9 # Modified by J. Henrichs, Bureau of Meteorology
10 
11 
12 ''' A simple Fortran expression parser. Note that this does not parse Fortran,
13 only legal Fortran expressions. '''
14 
15 import pyparsing as pparse
16 # Enable the packrat optimisation. This seems to be performance-critical.
17 pparse.ParserElement.enablePackrat()
18 
19 
21  '''Base class for all expression tree nodes'''
22 
23  # pylint: disable=too-few-public-methods
24  def __init__(self, toks):
25  ''' The recursive collection of names enables the dependencies of
26  expressions to be analysed. '''
27  self.namesnames = set()
28  for tok in toks:
29  if isinstance(tok, ExpressionNode):
30  self.namesnames.update(tok.names)
31 
32  # Keep the list of toks for future reference.
33  self.tokstoks = toks
34 
35 
37  '''Expression node for a parenthesised expression.'''
38  def __init__(self, toks):
39  ExpressionNode.__init__(self, toks)
40 
41  self.exprexpr = toks[1]
42 
43  def __repr__(self):
44  return "Grouping(['('," + repr(self.exprexpr) + ",')'])"
45 
46  def __str__(self):
47  return "(" + str(self.exprexpr) + ")"
48 
49 
51  '''Expression node for one or more binary operators with the same
52  precedence.
53 
54  For some reason, operator tokens come in a list of lists.'''
55  def __init__(self, toks):
56  ExpressionNode.__init__(self, toks[0])
57 
58  self.operandsoperands = [toks[0][0]]
59  self.symbolssymbols = []
60  i = iter(toks[0][1:])
61  try:
62  while True:
63  tok = next(i)
64  self.symbolssymbols.append(tok)
65  tok = next(i)
66  self.operandsoperands.append(tok)
67  except StopIteration:
68  pass
69 
70  def __repr__(self):
71  _str = "BinaryOperator([["+repr(self.operandsoperands[0])
72  for sym, opd in zip(self.symbolssymbols, self.operandsoperands[1:]):
73  _str += ", " + repr(sym) + ", " + repr(opd)
74  _str += "]])"
75  return _str
76 
77  def __str__(self):
78  return str(self.operandsoperands[0]) + \
79  "".join([" "+str(sym)+" "+str(opd) for sym, opd in
80  zip(self.symbolssymbols, self.operandsoperands[1:])])
81 
82 
84  """Expression node for Fortran colon array slicings."""
85  def __init__(self, toks):
86  ExpressionNode.__init__(self, toks)
87 
88  self.startstart = ""
89  self.stopstop = ""
90  self.stridestride = ""
91 
92  tokiter = iter(toks)
93  tok = next(tokiter)
94  if tok != ":":
95  self.startstart = tok
96  tok = next(tokiter)
97 
98  try:
99  tok = next(tokiter)
100  if tok != ":":
101  self.stopstop = tok
102  tok = next(tokiter)
103 
104  tok = next(tokiter)
105  self.stridestride = tok
106  except StopIteration:
107  pass
108 
109  def __repr__(self):
110  _str = "Slicing(["
111  if self.startstart:
112  _str += repr(self.startstart)+", "
113  _str += "':'"
114  if self.stopstop:
115  _str += ", "+repr(self.stopstop)
116  if self.stridestride:
117  _str += ", ':', "+repr(self.stridestride)
118  _str += "])"
119  return _str
120 
121  def __str__(self):
122  _str = str(self.startstart)+":"+str(self.stopstop)
123  if self.stridestride:
124  _str += ":"+str(self.stridestride)
125  return _str
126 
127 
129  '''Expression node for a Fortran variable or function call.'''
130  def __init__(self, toks):
131  ExpressionNode.__init__(self, toks)
132 
133  self.namename = toks[0]
134  self.namesnames.update([self.namename])
135 
136  if len(toks) > 1:
137  # No. of args is not sufficient to determine whether this
138  # is a function call since a function may have zero
139  # args.
140  self._is_fn_is_fn = True
141  self.argsargs = toks[2:-1]
142  else:
143  self._is_fn_is_fn = False
144  self.argsargs = None
145 
146  def __repr__(self):
147  _str = "FunctionVar(['" + self.namename + "'"
148  if self._is_fn_is_fn:
149  _str += ",'(',"
150  if self.argsargs:
151  _str += ", ".join([repr(a) for a in self.argsargs]) + ","
152  _str += "')'"
153  _str += "])"
154  return _str
155 
156  def __str__(self):
157  _str = str(self.namename)
158  if self._is_fn_is_fn:
159  _str += '('
160  if self.argsargs is not None:
161  _str += ", ".join([str(a) for a in self.argsargs])
162  _str += ')'
163  return _str
164 
165 
167  '''Expression node for a Fortran literal array.'''
168  def __init__(self, toks):
169  ExpressionNode.__init__(self, toks)
170  self.exprexpr = toks[1:-1] # first and last are delimiters
171 
172  def __getitem__(self, idx):
173  return self.exprexpr[idx]
174 
175  def __len__(self):
176  return len(self.exprexpr)
177 
178  def __repr__(self):
179  _str = "LiteralArray(['[',"
180  if self.exprexpr:
181  _str += ", ".join([repr(a) for a in self.exprexpr])
182  _str += ", ']'])"
183  return _str
184 
185  def __str__(self):
186  _str = "[" + str(self.exprexpr[0])
187  for tok in self.exprexpr[1:]:
188  _str += ", "+tok
189  _str += "]"
190  return _str
191 
192 
194  ''' Expression node for a Fortran named argument. '''
195  def __init__(self, toks):
196  ExpressionNode.__init__(self, toks)
197 
198  # First token is the name of the argument
199  self._name_name = toks[0]
200  self.namesnames.update([self._name_name])
201 
202  # The second token is the '=' so we ignore that and skip to
203  # the third token which contains the value assigned to the
204  # argument...
205  # The named variable can be assigned a character string. We've
206  # told the parser not to remove the delimiters so that we can
207  # see whether they are single or double quotes.
208  first_char = toks[2][0]
209  if first_char in ["'", '"']:
210  # Store the quotation character and the content of the string
211  # excluding the quotation marks
212  self._quote_quote = toks[2][0]
213  self._value_value = toks[2][1:-1]
214  else:
215  # The quantity being assigned is not a string
216  self._quote_quote = None
217  self._value_value = toks[2]
218 
219  def __repr__(self):
220  if self._quote_quote:
221  if self._quote_quote == "'":
222  _str = f"NamedArg(['{self._name}', '=', \"'{self._value}'\"])"
223  else:
224  _str = f'NamedArg(["{self._name}", "=", \'"{self._value}"\'])'
225  else:
226  _str = f"NamedArg(['{self._name}', '=', '{self._value}'])"
227  return _str
228 
229  def __str__(self):
230  _str = str(self._name_name) + "="
231  if self._quote_quote:
232  _str += self._quote_quote + str(self._value_value) + self._quote_quote
233  else:
234  _str += str(self._value_value)
235  return _str
236 
237  @property
238  def name(self):
239  ''' Returns the name of the variable (LHS) involved in a
240  named argument. '''
241  return self._name_name
242 
243  @property
244  def value(self):
245  ''' Returns the value (RHS) of the named argument '''
246  return self._value_value
247 
248  @property
249  def is_string(self):
250  ''' Returns True if the RHS of the named argument is a string '''
251  return self._quote_quote is not None
252 
253 
254 # Construct a grammar using PyParsing
255 
256 # A Fortran variable name starts with a letter and continues with
257 # letters, numbers and _. Can you start a name with _?
258 VAR_NAME = pparse.Word(pparse.alphas, pparse.alphanums+"_")
259 NAME = VAR_NAME | pparse.Literal(".false.") | pparse.Literal(".true.")
260 
261 # Reference to a component of a derived type
262 DERIVED_TYPE_COMPONENT = pparse.Combine(VAR_NAME + "%" + VAR_NAME)
263 
264 # An unsigned integer
265 UNSIGNED = pparse.Word(pparse.nums)
266 
267 # In Fortran, a numerical constant can have its kind appended after an
268 # underscore. The kind can be a 'name' or just digits.
269 KIND = pparse.Word("_", exact=1) + (VAR_NAME | UNSIGNED)
270 
271 # First arg to Word gives allowed initial chars, 2nd arg gives allowed
272 # body characters
273 SIGNED = pparse.Word("+-"+pparse.nums, pparse.nums)
274 INTEGER = pparse.Combine(SIGNED + pparse.Optional(KIND))
275 
276 POINT = pparse.Literal(".")
277 
278 # A floating point number
279 REAL = pparse.Combine(
280  (SIGNED + POINT + pparse.Optional(UNSIGNED) | POINT + UNSIGNED) +
281  pparse.Optional(pparse.Word("dDeE", exact=1) + SIGNED) +
282  pparse.Optional(KIND))
283 
284 # Literal brackets.
285 LPAR = pparse.Literal("(")
286 RPAR = pparse.Literal(")")
287 
288 LIT_ARRAY_START = pparse.Literal("[") | pparse.Literal("(/")
289 LIT_ARRAY_END = pparse.Literal("]") | pparse.Literal("/)")
290 
291 EXPR = pparse.Forward()
292 
293 # Array slicing
294 COLON = pparse.Literal(":")
295 SLICING = pparse.Optional(EXPR) + COLON + pparse.Optional(EXPR) + \
296  pparse.Optional(COLON+pparse.Optional(EXPR))
297 SLICING.setParseAction(lambda strg, loc, toks: [Slicing(toks)])
298 
299 VAR_OR_FUNCTION = (DERIVED_TYPE_COMPONENT | NAME) + pparse.Optional(
300  LPAR + pparse.Optional(pparse.delimitedList(SLICING | EXPR)) + RPAR)
301 VAR_OR_FUNCTION.setParseAction(lambda strg, loc, toks: [FunctionVar(toks)])
302 
303 LITERAL_ARRAY = LIT_ARRAY_START + pparse.delimitedList(EXPR) + LIT_ARRAY_END
304 LITERAL_ARRAY.setParseAction(lambda strg, loc, toks: [LiteralArray(toks)])
305 
306 # An optional/named argument. We use QuotedString here to avoid versioning
307 # problems with the interface to {sgl,dbl}QuotedString in pyparsing.
308 OPTIONAL_VAR = VAR_NAME + "=" + ((NAME | REAL | INTEGER) |
309  pparse.QuotedString("'",
310  unquoteResults=False) |
311  pparse.QuotedString('"',
312  unquoteResults=False))
313 # lambda creates a temporary function which, in this case, takes three
314 # arguments and creates a NamedArg object.
315 OPTIONAL_VAR.setParseAction(lambda strg, loc, toks: [NamedArg(toks)])
316 
317 GROUP = LPAR + EXPR + RPAR
318 GROUP.setParseAction(lambda strg, loc, toks: [Grouping(toks)])
319 
320 # Parser will attempt to match with the expressions in the order they
321 # are specified here. Therefore must list them in order of decreasing
322 # generality
323 OPERAND = (GROUP | OPTIONAL_VAR | VAR_OR_FUNCTION | REAL | INTEGER |
324  LITERAL_ARRAY)
325 
326 # Cause the binary operators to work.
327 OPERATOR = pparse.infixNotation(
328  OPERAND,
329  ((pparse.Literal("**"), 2, pparse.opAssoc.RIGHT,
330  lambda strg, loc, toks: [BinaryOperator(toks)]),
331  (pparse.Literal("*") | pparse.Literal("/"), 2, pparse.opAssoc.LEFT,
332  lambda strg, loc, toks: [BinaryOperator(toks)]),
333  (pparse.Literal("+") | pparse.Literal("-"), 2, pparse.opAssoc.LEFT,
334  lambda strg, loc, toks: [BinaryOperator(toks)]),))
335 
336 # pylint: disable=pointless-statement
337 EXPR << (OPERATOR | OPERAND)
338 # pylint: enable=pointless-statement
339 
340 FORT_EXPRESSION = pparse.StringStart() + EXPR + pparse.StringEnd()
def __init__(self, toks)
Definition: expression.py:38
def __init__(self, toks)
Definition: expression.py:195
def __init__(self, toks)
Definition: expression.py:85