70 Defines LineSplitter and helper functions. 72 Original Author: Pearu Peterson <pearu@cens.ioc.ee> 73 First version created: May 2006 83 """Dummy string class.""" 87 """Class representing a parenthesis string.""" 90 __all__ = [
"String",
"string_replace_map",
"splitquote",
"splitparen"]
92 _f2py_str_findall = re.compile(
r"_F2PY_STRING_CONSTANT_\d+_").findall
93 _is_name = re.compile(
r"\w*\Z", re.I).match
94 _is_simple_str = re.compile(
r"\w*\Z", re.I).match
95 _f2py_findall = re.compile(
96 r"(_F2PY_STRING_CONSTANT_\d+_|F2PY_REAL_CONSTANT_\d+_|" r"F2PY_EXPR_TUPLE_\d+)" 105 exponential_constant = re.compile(
106 r"(?:[^\w.]|^)((\d+[.]\d*|\d*[.]\d+|\d+)[edED][+-]?\d+(_\w+)?)" 112 Dictionary object that is callable for applying map returned 113 by string_replace_map() function. 116 def __call__(self, line):
117 for key
in _f2py_findall(line):
124 line = line.replace(key, self[key], 1)
128 def memoize(function):
129 """Simple memoization decorator. 131 :param function: The function to memoize. 132 :type function: Callable 134 Note: Python 3.9 comes with a thread-safe and more efficient cache as it 135 can be bounded and we are interested in lines that have temporal locality. 136 It's the: @functools.lru_cache(maxsize=8) 141 def wrapper(*args, **kwargs):
144 for item
in kwargs.items():
146 result = memo.get(key,
None)
147 if result
is not None:
149 result = function(*args, **kwargs)
157 def string_replace_map(line, lower=False):
159 #. Replaces string constants with symbol `'_F2PY_STRING_CONSTANT_<index>_'` 160 #. Replaces (`expression`) with symbol `(F2PY_EXPR_TUPLE_<index>)` 161 #. Replaces real numerical constants containing an exponent with symbol 162 `F2PY_REAL_CONSTANT_<index>_` 164 :param str line: the line of text in which to perform substitutions. 165 :param bool lower: whether or not the call to splitquote() should return \ 166 items as lowercase (default is to leave the case unchanged). 168 :returns: a new line and the replacement map. 169 :rtype: 2-tuple of str and \ 170 :py:class:`fparser.common.splitline.StringReplaceDict` 181 for item
in splitquote(line, lower=lower)[0]:
182 if isinstance(item, String)
and not _is_simple_str(item[1:-1]):
183 key = rev_string_map.get(item)
186 key =
"_F2PY_STRING_CONSTANT_{0}_".format(str_idx)
188 string_map[key] = trimmed
189 rev_string_map[trimmed] = key
190 items.append(item[0] + key + item[-1])
193 newline =
"".join(items)
196 for item
in exponential_constant.finditer(newline):
199 found = item.group(1)
201 key = rev_string_map.get(found)
204 key =
"F2PY_REAL_CONSTANT_{0}_".format(const_idx)
205 string_map[key] = found
206 rev_string_map[found] = key
207 const_keys.append(key)
208 newline = newline.replace(found, key)
212 for item
in splitparen(newline):
213 if isinstance(item, ParenString)
and not _is_name(item[1:-1].strip()):
214 key = rev_string_map.get(item)
217 key =
"F2PY_EXPR_TUPLE_{0}".format(parens_idx)
218 trimmed = item[1:-1].strip()
219 string_map[key] = trimmed
220 rev_string_map[trimmed] = key
221 expr_keys.append(key)
222 items.append(item[0] + key + item[-1])
229 for key
in expr_keys + const_keys:
230 entry = string_map[key]
232 included_keys = _f2py_findall(entry)
234 found_keys = found_keys.union(included_keys)
235 for inc_key
in included_keys:
236 entry = entry.replace(inc_key, string_map[inc_key], 1)
237 string_map[key] = entry
239 return "".join(items), string_map
242 def splitquote(line, stopchar=None, lower=False, quotechars="\"'"):
260 if char
in quotechars
and not nofslashes % 2:
294 if char == stopchar
and not nofslashes % 2:
311 return items, stopchar
314 def splitparen(line, paren_open="([
", paren_close=")]
"): 316 Splits a line into top-level parenthesis and not-parenthesised 317 parts. E.g.: "a( (1+2)*3) = b(x)" becomes: 318 ["a", "( (1+2)*3)", " = b", "(x)"] 319 :param str line: the string to split. 320 :param str paren_open: The characters that define an open parentheses. 321 :param str paren_close: The characters that define a closing parentheses. 322 :return: List of parenthesised and not-parenthesised parts 324 The paren_open and paren_close strings must be matched in order: 325 paren_open[x] is closed by paren_close[x]. 328 assert len(paren_open) == len(paren_close)
334 inside_quotes_char =
"" 338 for idx, char
in enumerate(line):
340 num_backslashes = (num_backslashes + 1) % 2
345 if num_backslashes == 1:
351 if inside_quotes_char !=
"":
353 if char == inside_quotes_char:
354 inside_quotes_char =
"" 357 if char ==
"'" or char ==
'"':
358 inside_quotes_char = char
361 pos = paren_open.find(char)
365 items.append(line[start:idx])
367 stack.append(paren_close[pos])
371 if len(stack) > 0
and char == stack[-1]:
379 if start != len(line):
380 items.append(line[start:])