From ec41880b00aacb89324100cbfc79a0170ec19174 Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Wed, 8 Jan 2025 15:51:56 +0000 Subject: [PATCH] Frontend: Final removal of OFP frontend o7 --- loki/frontend/__init__.py | 1 - loki/frontend/ofp.py | 1973 -------------------------------- loki/frontend/preprocessing.py | 14 - loki/frontend/util.py | 11 +- loki/module.py | 27 +- loki/program_unit.py | 7 +- loki/sourcefile.py | 57 +- loki/subroutine.py | 27 +- 8 files changed, 10 insertions(+), 2107 deletions(-) delete mode 100644 loki/frontend/ofp.py diff --git a/loki/frontend/__init__.py b/loki/frontend/__init__.py index d87003217..fd1914abe 100644 --- a/loki/frontend/__init__.py +++ b/loki/frontend/__init__.py @@ -14,7 +14,6 @@ from loki.frontend.preprocessing import * # noqa from loki.frontend.source import * # noqa -from loki.frontend.ofp import * # noqa from loki.frontend.omni import * # noqa from loki.frontend.fparser import * # noqa from loki.frontend.util import * # noqa diff --git a/loki/frontend/ofp.py b/loki/frontend/ofp.py deleted file mode 100644 index ca4577491..000000000 --- a/loki/frontend/ofp.py +++ /dev/null @@ -1,1973 +0,0 @@ -# (C) Copyright 2018- ECMWF. -# This software is licensed under the terms of the Apache Licence Version 2.0 -# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. -# In applying this licence, ECMWF does not waive the privileges and immunities -# granted to it by virtue of its status as an intergovernmental organisation -# nor does it submit to any jurisdiction. - -# pylint: disable=too-many-lines -from collections import deque -from collections.abc import Iterable -from pathlib import Path -import re -from codetiming import Timer - -try: - import open_fortran_parser - - HAVE_OFP = True - """Indicate whether OpenFortranParser frontend is available.""" -except ImportError: - HAVE_OFP = False - -from loki.frontend.source import extract_source, extract_source_from_range -from loki.frontend.preprocessing import sanitize_registry -from loki.frontend.util import sanitize_ir, Frontend - -from loki import ir -from loki.ir import ( - GenericVisitor, attach_pragmas, process_dimension_pragmas, - detach_pragmas, pragmas_attached, FindNodes -) -import loki.expression.symbols as sym -from loki.expression.operations import ( - ParenthesisedAdd, ParenthesisedMul, ParenthesisedPow, ParenthesisedDiv, - StringConcat -) -from loki.expression import ExpressionDimensionsMapper, AttachScopesMapper -from loki.tools import ( - as_tuple, disk_cached, flatten, gettempdir, filehash, CaseInsensitiveDict, - LazyNodeLookup -) -from loki.logging import debug, info, warning, error -from loki.types import BasicType, DerivedType, ProcedureType, SymbolAttributes -from loki.config import config - - -__all__ = ['HAVE_OFP', 'parse_ofp_file', 'parse_ofp_source', 'parse_ofp_ast'] - - -OFP = Frontend.OFP - - -@Timer(logger=debug, text=lambda s: f'[Loki::OFP] Executed parse_ofp_file in {s:.2f}s') -@disk_cached(argname='filename', suffix='ofpast') -def parse_ofp_file(filename): - """ - Read and parse a source file using the Open Fortran Parser (OFP). - - Note: The parsing is cached on disk in ``.cache``. - """ - if not HAVE_OFP: - error('OpenFortranParser is not available.') - - warning('[DEPRECATION]: The OFP frontend is deprecated and will be removed soon!') - - filepath = Path(filename) - info(f'[Loki::OFP] Parsing {filepath}') - return open_fortran_parser.parse(filepath, raise_on_error=True) - - -@Timer(logger=debug, text=lambda s: f'[Loki::OFP] Executed parse_ofp_source in {s:.2f}s') -def parse_ofp_source(source, filepath=None): - """ - Read and parse a source string using the Open Fortran Parser (OFP). - """ - # Use basename of filepath if given - if filepath is None: - filepath = Path(filehash(source, prefix='ofp-', suffix='.f90')) - else: - filepath = filepath.with_suffix(f'.ofp{filepath.suffix}') - - # Always store intermediate flies in tmp dir - filepath = gettempdir()/filepath.name - - debug(f'[Loki::OFP] Writing temporary source {filepath}') - with filepath.open('w') as f: - f.write(source) - - return parse_ofp_file(filename=filepath) - - -@Timer(logger=debug, text=lambda s: f'[Loki::OFP] Executed parse_ofp_ast in {s:.2f}s') -def parse_ofp_ast(ast, pp_info=None, raw_source=None, definitions=None, scope=None): - """ - Generate an internal IR from the raw OFP parser AST. - """ - # Parse the raw OFP language AST - _ir = OFP2IR(definitions=definitions, raw_source=raw_source, pp_info=pp_info, scope=scope).visit(ast) - - # Apply postprocessing rules to re-insert information lost during preprocessing - # and perform some minor sanitation tasks - _ir = sanitize_ir(_ir, OFP, pp_registry=sanitize_registry[OFP], pp_info=pp_info) - - return _ir - - -def match_tag_sequence(sequence, patterns): - """ - Return a list of tuples with all AST nodes (xml objects) that - match a tag pattern in the list of :param patterns:. - """ - new_sequence = [] - tags = [i.tag for i in sequence] - for i, _ in enumerate(zip(sequence, tags)): - for pattern in patterns: - if tuple(tags[i:i+len(pattern)]) == pattern: - new_sequence.append(tuple(sequence[i:i+len(pattern)])) - return new_sequence - - -class OFP2IR(GenericVisitor): - # pylint: disable=unused-argument # Stop warnings about unused arguments - - def __init__(self, raw_source, definitions=None, pp_info=None, scope=None): - super().__init__() - - self._raw_source = raw_source - self.definitions = CaseInsensitiveDict((d.name, d) for d in as_tuple(definitions)) - self.pp_info = pp_info - self.default_scope = scope - - @staticmethod - def warn_or_fail(msg): - if config['frontend-strict-mode']: - error(msg) - raise NotImplementedError - warning(msg) - - def lookup_method(self, instance): - """ - Alternative lookup method for XML element types, identified by ``element.tag`` - """ - if isinstance(instance, Iterable): - return super().lookup_method(instance) - - tag = instance.tag.replace('-', '_') - if tag in self._handlers: - return self._handlers[tag] - return super().lookup_method(instance) - - def get_label(self, o): - """ - Helper routine to extract the label from one of the many places it could be. - """ - if o is None or not hasattr(o, 'attrib'): - return None - if 'lbl' in o.attrib: - return o.attrib['lbl'] - if 'label' in o.attrib: - return o.attrib['label'] - return self.get_label(o.find('label')) - - def get_source(self, o, label=None): - """Helper method that builds the source object for a node""" - try: - source = extract_source(o.attrib, self._raw_source, label=label) - except KeyError: - source = None - return source - - def visit(self, o, **kwargs): # pylint: disable=arguments-differ - """ - Generic dispatch method that tries to generate meta-data from source. - """ - if isinstance(o, Iterable): - return super().visit(o, **kwargs) - - kwargs['label'] = self.get_label(o) - kwargs.setdefault('scope', self.default_scope) - kwargs['source'] = self.get_source(o, kwargs['label']) - return super().visit(o, **kwargs) - - def visit_tuple(self, o, **kwargs): - return as_tuple(flatten(self.visit(c, **kwargs) for c in o)) - - visit_list = visit_tuple - - def visit_Element(self, o, label=None, source=None, **kwargs): - """ - Universal default for XML element types - """ - children = tuple(self.visit(c, **kwargs) for c in o) - children = tuple(c for c in children if c is not None) - if len(children) == 1: - return children[0] # Flatten hierarchy if possible - return children if len(children) > 0 else None - - def visit_file(self, o, **kwargs): - body = [self.visit(c, **kwargs) for c in o] - return ir.Section(body=as_tuple(body)) - - def visit_specification(self, o, **kwargs): - body = tuple(self.visit(c, **kwargs) for c in o) - body = tuple(c for c in body if c is not None) - return ir.Section(body=body, label=kwargs['label'], source=kwargs['source']) - - def visit_body(self, o, **kwargs): - body = tuple(self.visit(c, **kwargs) for c in o) - body = tuple(c for c in body if c is not None) - return body - - def visit_loop(self, o, **kwargs): - body = as_tuple(self.visit(o.find('body'), **kwargs)) - # Store full lines with loop body for easy replacement - source = extract_source(o.attrib, self._raw_source, full_lines=True) - - if o.attrib.get('type') == 'forall': - named_bounds = () - for var in o.findall('header/index-variables/index-variable'): - # forall range - variable = sym.Variable(name=var.attrib['name']) - lower = self.visit(var.find('lower-bound'), **kwargs) - upper = self.visit(var.find('upper-bound'), **kwargs) - bounds = sym.RangeIndex((lower, upper)) - named_bounds += ((variable, bounds),) - - # mask - if (operation := o.find('header/operation')) is not None: - mask = self.visit(operation, **kwargs) - else: - mask = None - forall_stmt = o.find('forall-stmt') - inline = forall_stmt.attrib['line_begin'] == forall_stmt.attrib['line_end'] - return ir.Forall(name=None, named_bounds=named_bounds, body=body, - mask=mask, inline=inline, source=source) - - # Extract loop label if any - loop_label = o.find('do-stmt').attrib['digitString'] or None - construct_name = o.find('do-stmt').attrib['id'] or None - label = self.get_label(o.find('do-stmt')) - has_end_do = o.find('end-do-stmt') is not None - term_stmt = o.find('do-term-action-stmt') - if not has_end_do and term_stmt is not None: - # Yay, a special case for loops with label - has_end_do = term_stmt.get('endKeyword') == 'end' - - if o.find('header/index-variable') is None: - if o.find('do-stmt').attrib['hasLoopControl'] == 'false': - # We are processing an unbounded do loop - condition = None - else: - # We are processing a while loop - condition = self.visit(o.find('header'), **kwargs) - return ir.WhileLoop(condition=condition, body=body, loop_label=loop_label, - name=construct_name, has_end_do=has_end_do, - label=label, source=source) - - # We are processing a regular for/do loop with bounds - vname = o.find('header/index-variable').attrib['name'] - variable = sym.Variable(name=vname) - lower = self.visit(o.find('header/index-variable/lower-bound'), **kwargs) - upper = self.visit(o.find('header/index-variable/upper-bound'), **kwargs) - step = None - if o.find('header/index-variable/step') is not None: - step = self.visit(o.find('header/index-variable/step'), **kwargs) - bounds = sym.LoopRange((lower, upper, step)) - return ir.Loop(variable=variable, body=body, bounds=bounds, loop_label=loop_label, - label=label, name=construct_name, has_end_do=has_end_do, - source=source) - - def visit_if(self, o, **kwargs): - # process all conditions and bodies - conditions = [self.visit(h, **kwargs) for h in o.findall('header')] - bodies = [flatten(as_tuple(self.visit(b, **kwargs))) for b in o.findall('body')] - ncond = len(conditions) - if len(bodies) > ncond: - else_body = bodies[-1] - bodies = bodies[:-1] - else: - else_body = () - assert ncond == len(bodies) - # shortcut for inline conditionals - if o.find('if-then-stmt') is None: - assert ncond == 1 and else_body == () - return ir.Conditional(condition=conditions[0], body=bodies[0], else_body=(), - inline=True, has_elseif=False, label=kwargs['label'], - source=kwargs['source']) - # extract labels, names and source - lend, cend = int(o.attrib['line_end']), int(o.attrib['col_end']) - names, labels, sources = [], [], [] - for stmt in reversed(o.findall('else-if-stmt')): - names += [None] - labels += [self.get_label(stmt)] - lstart, cstart = int(stmt.attrib['line_begin']), int(stmt.attrib['col_begin']) - sources += [extract_source_from_range((lstart, lend), (cstart, cend), self._raw_source, label=labels[-1])] - names += [o.find('if-then-stmt').attrib['id'] or None] - labels += [self.get_label(o.find('if-then-stmt'))] - sources += [kwargs['source']] - # build IR nodes from inside out, using else-if branches as else bodies - conditions.reverse() - bodies.reverse() - node = ir.Conditional(condition=conditions[0], body=bodies[0], else_body=else_body, - inline=False, has_elseif=False, label=kwargs['label'], - name=names[0], source=sources[0]) - for idx in range(1, ncond): - node = ir.Conditional(condition=conditions[idx], body=bodies[idx], else_body=(node,), - inline=False, has_elseif=True, label=labels[idx], name=names[idx], - source=sources[idx]) - return node - - def visit_select(self, o, **kwargs): - expr = self.visit(o.find('header'), **kwargs) - body = o.find('body') - case_stmts, case_stmt_index = zip(*[(c, i) for i, c in enumerate(body) if c.tag == 'case']) - values, bodies = zip(*[self.visit(c, **kwargs) for c in case_stmts]) - if None in values: - else_index = values.index(None) - else_body = as_tuple(bodies[else_index]) - values = values[:else_index] + values[else_index+1:] - bodies = bodies[:else_index] + bodies[else_index+1:] - else: - else_body = () - # Retain comments before the first case statement - pre = as_tuple(self.visit(c, **kwargs) for c in body[:case_stmt_index[0]]) - # Retain any comments in-between cases - bodies = list(bodies) - for case_idx, stmt_idx in enumerate(case_stmt_index[1:]): - start_idx = case_stmt_index[case_idx] + 1 - bodies[case_idx-1] += as_tuple(self.visit(c, **kwargs) for c in body[start_idx:stmt_idx]) - bodies = as_tuple(bodies) - construct_name = o.find('select-case-stmt').attrib['id'] or None - label = self.get_label(o.find('select-case-stmt')) - return ( - *pre, - ir.MultiConditional(expr=expr, values=values, bodies=bodies, else_body=else_body, - label=label, name=construct_name, source=kwargs['source']) - ) - - def visit_case(self, o, **kwargs): - value = self.visit(o.find('header'), **kwargs) - if isinstance(value, tuple) and len(value) > int(o.find('header/value-ranges').attrib['count']): - value = sym.RangeIndex(value) - body = self.visit(o.find('body'), **kwargs) - return as_tuple(value) or None, as_tuple(body) - - # TODO: Deal with line-continuation pragmas! - _re_pragma = re.compile(r'^\s*\!\$(?P\w+)\s*(?P.*)', re.IGNORECASE) - - def visit_comment(self, o, **kwargs): - match_pragma = self._re_pragma.search(kwargs['source'].string) - if match_pragma: - # Found pragma, generate this instead - gd = match_pragma.groupdict() - return ir.Pragma(keyword=gd['keyword'], content=gd['content'], source=kwargs['source']) - return ir.Comment(text=o.attrib['text'], label=kwargs['label'], source=kwargs['source']) - - def visit_statement(self, o, **kwargs): - # TODO: Hacky pre-emption for special-case statements - if o.find('name/nullify-stmt') is not None: - variable = self.visit(o.find('name'), **kwargs) - return ir.Nullify(variables=as_tuple(variable), label=kwargs['label'], source=kwargs['source']) - if o.find('cycle') is not None: - return self.visit(o.find('cycle'), **kwargs) - if o.find('where-construct-stmt') is not None or o.find('where-stmt') is not None: - # Parse WHERE statement(s)... - # They can appear multiple times within a single statement node and - # alongside other, non-WHERE-statement nodes. Conveniently, conditions - # and body are also flat in the node and therefore not marked explicitly. - # We have to step through them and do our best at picking them out... - children = [self.visit(c, **kwargs) for c in o] - children = [c for c in children if c is not None] - - stmts = [] - # Pick out all nodes that belong to this WHERE construct - # TODO: this breaks for nested where constructs... - while True: - # Find position of relevant constructs - if 'ENDWHERE_CONSTRUCT' in children: - construct_start = children.index('WHERE_CONSTRUCT') - else: - construct_start = len(children) - if 'WHERE_STATEMENT' in children: - statement_start = children.index('WHERE_STATEMENT') - else: - statement_start = len(children) - - # Start with where construct if it comes first - if construct_start < statement_start: - if construct_start > 1: - # There's stuff before that we retain flat - stmts += children[:construct_start-1] - - # That's the stuff that belongs to this construct - iend = children.index('ENDWHERE_CONSTRUCT') - w_children = children[construct_start-1:iend] - - # That's the stuff that follows after - children = children[iend+1:] - - # condition of the WHERE statement - conditions = [w_children[0]] - bodies = [] # picking out bodies lags behind - assert w_children[1] == 'WHERE_CONSTRUCT' - w_children = w_children[2:] - - # any ELSEWHERE statements with a condition? - while 'MASKED_ELSEWHERE_CONSTRUCT' in w_children: - iw = w_children.index('MASKED_ELSEWHERE_CONSTRUCT') - bodies += [as_tuple(w_children[:iw-1])] # that's the body from the previous case - conditions += [w_children[iw-1]] # that's the condition for the next case - w_children = w_children[iw+1:] - - # any ELSEWHERE statement without a condition? - if 'ELSEWHERE_CONSTRUCT' in w_children: - iw = w_children.index('ELSEWHERE_CONSTRUCT') - bodies += [as_tuple(w_children[:iw])] # again, body from before - default = as_tuple(w_children[iw+1:]) # The default body - - else: # No ELSEWHERE, so only body for previous conditional - bodies += [as_tuple(w_children)] - default = () - - # Build the masked statement - stmts += [ - ir.MaskedStatement( - conditions=as_tuple(conditions), bodies=as_tuple(bodies), default=default, - label=kwargs['label'], source=kwargs['source'] - ) - ] - - # Where-statement comes first - elif statement_start < len(children): - # This is always the sequence of [condition, assignment, WHERE_STATEMENT] - if statement_start > 2: - # There's stuff before that we retain flat - stmts += children[:statement_start-2] - - # Pick out condition and body, update remaining children list - conditions = [children[statement_start-2]] - bodies = [as_tuple(children[statement_start-1])] - assert children[statement_start] == 'WHERE_STATEMENT' - children = children[statement_start+1:] - - # Build the masked statement - stmts += [ - ir.MaskedStatement( - conditions=as_tuple(conditions), bodies=as_tuple(bodies), default=(), - inline=True, label=kwargs['label'], source=kwargs['source'] - ) - ] - - else: # Found neither: terminate loop - break - - if children: - stmts += children - return as_tuple(stmts) - - if o.find('goto-stmt') is not None: - target_label = o.find('goto-stmt').attrib['target_label'] - return ir.Intrinsic(text=f'go to {target_label}', label=kwargs['label'], source=kwargs['source']) - return self.visit_Element(o, **kwargs) - - def visit_where_construct_stmt(self, o, **kwargs): - # Only used as a marker above - return 'WHERE_CONSTRUCT' - - def visit_masked_elsewhere_stmt(self, o, **kwargs): - # Only used as a marker above - return 'MASKED_ELSEWHERE_CONSTRUCT' - - def visit_elsewhere_stmt(self, o, **kwargs): - # Only used as a marker above - return 'ELSEWHERE_CONSTRUCT' - - def visit_end_where_stmt(self, o, **kwargs): - # Only used as a marker above - return 'ENDWHERE_CONSTRUCT' - - def visit_where_stmt(self, o, **kwargs): - # Only used as a marker above - return 'WHERE_STATEMENT' - - def visit_assignment(self, o, **kwargs): - lhs = self.visit(o.find('target'), **kwargs) - rhs = self.visit(o.find('value'), **kwargs) - - # Special-case: Identify statement functions using our internal symbol table - symbol_attrs = kwargs['scope'].symbol_attrs - if isinstance(lhs, sym.Array) and symbol_attrs.lookup(lhs.name) is not None: - # If this looks like an array but we have an explicit scalar declaration then - # this might in fact be a statement function. - # To avoid the costly lookup for declarations on each array assignment, we run through - # some sanity checks instead that allow us to bail out early in most cases - lhs_type = lhs.type - could_be_a_statement_func = not ( - lhs_type.shape or lhs_type.length # Declaration with length or dimensions - or lhs.parent # Derived type member (we might lack information from enrichment) - or lhs_type.intent or lhs_type.imported # Dummy argument or imported from module - or isinstance(lhs.scope, ir.Associate) # Symbol stems from an associate - ) - - if could_be_a_statement_func: - def _create_stmt_func_type(stmt_func): - name = str(stmt_func.variable) - procedure = LazyNodeLookup( - anchor=kwargs['scope'], - query=lambda x: [ - f for f in FindNodes(ir.StatementFunction).visit(x.spec) if f.variable == name - ][0] - ) - proc_type = ProcedureType(is_function=True, procedure=procedure, name=name) - return SymbolAttributes(dtype=proc_type, is_stmt_func=True) - - f_symbol = sym.ProcedureSymbol(name=lhs.name, scope=kwargs['scope']) - stmt_func = ir.StatementFunction( - variable=f_symbol, arguments=lhs.dimensions, - rhs=rhs, return_type=symbol_attrs[lhs.name], - label=kwargs.get('label'), source=kwargs.get('source') - ) - - # Update the type in the local scope and return stmt func node - symbol_attrs[str(stmt_func.variable)] = _create_stmt_func_type(stmt_func) - return stmt_func - - return ir.Assignment(lhs=lhs, rhs=rhs, label=kwargs['label'], source=kwargs['source']) - - def visit_pointer_assignment(self, o, **kwargs): - lhs = self.visit(o.find('target'), **kwargs) - rhs = self.visit(o.find('value'), **kwargs) - return ir.Assignment(lhs=lhs, rhs=rhs, ptr=True, label=kwargs['label'], source=kwargs['source']) - - def visit_type(self, o, **kwargs): - if o.attrib['type'] == 'intrinsic': - _type = self.visit(o.find('intrinsic-type-spec'), **kwargs) - if o.attrib['hasKind'] == 'true': - kind = self.visit(o.find('kind'), **kwargs) - _type = _type.clone(kind=kind) - if o.find('length') is not None: - _type = _type.clone(length=self.visit(o.find('length'), **kwargs)) - return _type - - if o.attrib['type'] == 'derived': - dtype = self.visit(o.find('derived-type-spec'), **kwargs) - - # Look for a previous definition of this type - _type = kwargs['scope'].symbol_attrs.lookup(dtype.name) - if _type is None or _type.dtype is BasicType.DEFERRED: - _type = SymbolAttributes(dtype) - - decl_spec = o.find('declaration-type-spec') - if decl_spec is not None and decl_spec.get('udtKeyword') == 'class': - _type = _type.clone(polymorphic=True) - - # Strip import annotations - return _type.clone(imported=None, module=None) - - raise ValueError(f'Unknown type {o.attrib("type")}') - - def visit_intrinsic_type_spec(self, o, **kwargs): - dtype = BasicType.from_str(o.attrib['keyword1']) - return SymbolAttributes(dtype) - - def visit_derived_type_spec(self, o, **kwargs): - dtype = DerivedType(o.attrib['typeName']) - return dtype - - def visit_kind(self, o, **kwargs): - return self.visit(o[0], **kwargs) - - def visit_kind_selector(self, o, **kwargs): - assert o.attrib['token1'] == '*' - return sym.IntLiteral(int(o.attrib['token2'])) - - def visit_attribute_parameter(self, o, **kwargs): - return self.visit(o.findall('attr-spec'), **kwargs) - - visit_attribute_pointer = visit_attribute_parameter - - def visit_attr_spec(self, o, **kwargs): - return (o.attrib['attrKeyword'].lower(), True) - - def visit_type_attr_spec(self, o, **kwargs): - if o.attrib['keyword'].lower() == 'extends': - return ('extends', o.attrib['id'].lower()) - return (o.attrib['keyword'].lower(), True) - - def visit_intent(self, o, **kwargs): - return ('intent', o.attrib['type'].lower()) - - def visit_access_spec(self, o, **kwargs): - return (o.attrib['keyword'], True) - - def visit_type_param_value(self, o, **kwargs): - if o.attrib['hasAsterisk'] == 'true': - return '*' - if o.attrib['hasColon'] == 'true': - return ':' - return None - - def visit_entity_decl(self, o, **kwargs): - return sym.Variable(name=o.attrib['id']) - - def visit_variables(self, o, **kwargs): - count = int(o.attrib['count']) - return self.visit(o.findall('variable')[:count], **kwargs) - - def visit_variable(self, o, **kwargs): - var = self.visit(o.find('entity-decl'), **kwargs) - - if o.attrib['hasInitialValue'] == 'true': - init = self.visit(o.find('initial-value'), **kwargs) - var = var.clone(type=var.type.clone(initial=init)) - - dimensions = o.find('dimensions') - if dimensions is not None: - dimensions = self.visit(dimensions, **kwargs) - var = var.clone(dimensions=dimensions, type=var.type.clone(shape=dimensions)) - - length = o.find('length') - if length is not None: - length = self.visit(length, **kwargs) - var = var.clone(type=var.type.clone(length=length)) - - return var - - def visit_procedures(self, o, **kwargs): - count = int(o.attrib['count']) - nodes = [self.visit(c, **kwargs) for c in o] - nodes = [c for c in nodes if c is not None] - symbols = [] - initial = None - for c in nodes: - if not isinstance(c, sym.TypedSymbol): - assert initial is None - initial = c - else: - if initial is not None: - symbols += [c.clone(type=c.type.clone(initial=initial))] - initial = None - else: - symbols += [c] - assert initial is None - assert len(symbols) == count - return symbols - - def visit_procedure(self, o, **kwargs): - var = self.visit(o.find('proc-decl'), **kwargs) - return var - - def visit_null_init(self, o, **kwargs): - name = sym.Variable(name='NULL') - return sym.InlineCall(name, parameters=()) - - visit_proc_decl = visit_entity_decl - visit_proc_interface = visit_entity_decl - - def visit_specific_binding(self, o, **kwargs): - # pylint: disable=no-member # There is no Variable.member but Variable is a factory - scope = kwargs['scope'] - attrs = kwargs.pop('attrs') - - # Name of the binding - var = sym.Variable(name=o.attrib['bindingName']) - - interface = o.attrib['interfaceName'] or None - if interface is not None: - # Interface provided (PROCEDURE()) - assert not o.attrib['procedureName'] - - # Figure out interface type - interface_scope = scope.get_symbol_scope(interface) - if interface_scope is not None: - interface = sym.Variable(name=interface, scope=interface_scope) - else: - interface = sym.Variable( - name=interface, - type=SymbolAttributes(ProcedureType(interface)) - ) - - _type = interface.type.clone(bind_names=(interface,)) - - elif o.attrib['procedureName']: - # Binding provided ( => ) - procedure_scope = scope.get_symbol_scope(o.attrib['procedureName']) - bind_name = sym.Variable(name=o.attrib['procedureName'], scope=procedure_scope) - _type = bind_name.type.clone(bind_names=as_tuple(bind_name)) - - else: - # Binding has the same name as procedure - _type = scope.symbol_attrs.lookup(var.name) - if _type is None: - _type = SymbolAttributes(ProcedureType(var.name)) - - if attrs: - _type = _type.clone(**attrs) - - # Update symbol table and rescope symbols - scope.symbol_attrs[var.name] = _type - var = var.rescope(scope=scope) - return ir.ProcedureDeclaration( - symbols=(var,), interface=interface, source=kwargs['source'], label=kwargs['label'] - ) - - def visit_generic_binding(self, o, **kwargs): - scope = kwargs['scope'] - spec = kwargs.pop('spec') - attrs = kwargs.pop('attrs') - names = kwargs.pop('names') - - var = self.visit(spec, **kwargs) - bind_names = [self.visit(name, **kwargs) for name in names] - bind_names = AttachScopesMapper()(bind_names, scope=scope) - _type = SymbolAttributes(ProcedureType(var.name, is_generic=True), bind_names=as_tuple(bind_names)) - - if attrs: - _type = _type.clone(**attrs) - scope.symbol_attrs[var.name] = _type - var = var.rescope(scope=scope) - return ir.ProcedureDeclaration( - symbols=(var,), generic=True, source=kwargs['source'], label=kwargs['label'] - ) - - def visit_final_binding(self, o, **kwargs): - scope = kwargs['scope'] - spec = kwargs.pop('spec') - attrs = kwargs.pop('attrs') - names = kwargs.pop('names') - - assert spec is None - assert not attrs - - symbols = [self.visit(name, **kwargs) for name in names] - symbols = AttachScopesMapper()(symbols, scope=scope) - - return ir.ProcedureDeclaration( - symbols=symbols, final=True, source=kwargs['source'], label=kwargs['label'] - ) - - def visit_private_components_stmt(self, o, **kwargs): - return ir.Intrinsic(o.attrib['privateKeyword'], source=kwargs['source'], label=kwargs['label']) - - def visit_sequence_stmt(self, o, **kwargs): - return ir.Intrinsic(o.attrib['sequenceKeyword'], source=kwargs['source'], label=kwargs['label']) - - visit_binding_private_stmt = visit_private_components_stmt - - def visit_declaration(self, o, **kwargs): - label = kwargs['label'] - source = kwargs['source'] - if not o.attrib: - return None # Skip empty declarations - - # Dispatch to certain other declarations - if not 'type' in o.attrib: - if o.find('access-spec') is not None: - # access-stmt for module - from loki.module import Module # pylint: disable=import-outside-toplevel,cyclic-import - assert isinstance(kwargs['scope'], Module) - access_spec = o.find('access-spec').attrib['keyword'].lower() - assert access_spec in ('public', 'private') - names = o.findall('name') - if not names: - # default access specification - kwargs['scope'].default_access_spec = access_spec - else: - names = [name.attrib['id'].lower() for name in names] - if access_spec == 'public': - kwargs['scope'].public_access_spec += as_tuple(names) - else: - kwargs['scope'].private_access_spec += as_tuple(names) - return None - - if o.find('save-stmt') is not None: - return ir.Intrinsic(text=source.string.strip(), label=label, source=source) - if o.find('interface') is not None: - return self.visit(o.find('interface'), **kwargs) - if o.find('subroutine') is not None: - return self.visit(o.find('subroutine'), **kwargs) - if o.find('function') is not None: - return self.visit(o.find('function'), **kwargs) - if o.find('module-nature') is not None: - return self.visit(o.find('module-nature'), **kwargs) - if o.find('enum-def-stmt') is not None: - return self.create_enum(o, **kwargs) - if o.find('cray-pointer-stmt') is not None: - return ir.Intrinsic(text=source.string.strip(), label=label, source=source) - raise ValueError('Unsupported declaration') - if o.attrib['type'] in ('implicit', 'intrinsic', 'parameter'): - return ir.Intrinsic(text=source.string.strip(), label=label, source=source) - - if o.attrib['type'] == 'external': - # External stmt (not as attribute in a declaration) - assert o.find('external-stmt') is not None - assert o.find('type') is None - - variables = self.visit(o.findall('names'), **kwargs) - for var in variables: - _type = kwargs['scope'].symbol_attrs.lookup(var.name) or SymbolAttributes(dtype=BasicType.DEFERRED) - if _type.dtype == BasicType.DEFERRED: - dtype = ProcedureType(var.name, is_function=False) - else: - dtype = ProcedureType(var.name, is_function=True, return_type=_type) - kwargs['scope'].symbol_attrs[var.name] = _type.clone(dtype=dtype, external=True) - - variables = tuple(v.clone(scope=kwargs['scope']) for v in variables) - declaration = ir.ProcedureDeclaration(symbols=variables, external=True, source=source, label=label) - return declaration - - if o.attrib['type'] == 'data': - self.warn_or_fail('data declaration not implemented') - return ir.Intrinsic(text=source.string.strip(), label=label, source=source) - - if o.find('derived-type-stmt') is not None: - # Derived type definition - type_stmt = o.find('derived-type-stmt') - - # Derived type attributes - if type_stmt.attrib['hasTypeAttrSpecList'] == 'true': - attrs = dict(self.visit(attr, **kwargs) for attr in o.findall('type-attr-spec')) - abstract = attrs.get('abstract', False) - extends = attrs.get('extends', None) - bind_c = attrs.get('bind', False) - access_spec = o.find('access-spec') - private = access_spec is not None and access_spec.attrib['keyword'].lower() == 'private' - public = access_spec is not None and access_spec.attrib['keyword'].lower() == 'public' - else: - abstract = False - extends = None - bind_c = False - private = False - public = False - - # Instantiate the TypeDef without its body - # Note: This creates the symbol table for the declarations and - # the typedef object registers itself in the parent scope - typedef = ir.TypeDef( - name=type_stmt.attrib['id'], body=(), abstract=abstract, extends=extends, bind_c=bind_c, - private=private, public=public, label=label, source=source, parent=kwargs['scope']) - kwargs['scope'] = typedef - - body = [] - if o.find('sequence-stmt') is not None: - body.append(self.visit(o.find('sequence-stmt'), **kwargs)) - if o.find('private-components-stmt') is not None: - body.append(self.visit(o.find('private-components-stmt'), **kwargs)) - - # Less pretty than before but due to variable components being grouped - # and procedure components not, we have to step through children and - # collect type, attributes, etc. along the way. - - contains_stmt = o.find('contains-stmt') - if contains_stmt is not None: - contains_idx = list(o).index(contains_stmt) - else: - contains_idx = len(list(o)) - - # For variable declarations - t = None - attr = None - - # For procedure declarations - iface = None - proc_attrs = [] - - for c in o[:contains_idx]: - # Variable declarations - if c.tag == 'type': - assert iface is None and not proc_attrs - t = c - elif c.tag == 'attributes': - assert iface is None and not proc_attrs - attr = c - elif c.tag == 'components': - assert iface is None and not proc_attrs - decl = self.create_typedef_variable_declaration( - t=t, comps=c, attr=attr, scope=typedef, source=source - ) - body.append(decl) - t = None - attr = None - - # Procedure declarations - elif c.tag == 'proc-interface': - assert t is None and attr is None - iface = c - elif c.tag == 'proc-component-attr-spec': - assert t is None and attr is None - proc_attrs += [c] - elif c.tag == 'procedures': - assert t is None and attr is None - decl = self.create_typedef_procedure_declaration( - iface=iface, comps=c, attrs=proc_attrs, scope=typedef, source=source - ) - body.append(decl) - iface = None - proc_attrs = [] - - elif c.tag in ('comment',): # Add here other node types. Unfortunately we can't allow all - # because some produce spurious nodes that have been dealt with before - node = self.visit(c, **kwargs) - if node is not None: - body += [node] - - assert t is None and attr is None - assert iface is None and not proc_attrs - - if contains_stmt is not None: - # The derived type contains type-bound procedures - body.append(self.visit(contains_stmt, **kwargs)) - start_idx = contains_idx - - # Once again, this is _hell_ with OFP because binding attributes are not - # grouped (like they are for components) and therefore we have to step through - # the flat list of items, picking up the grouped names for generic/final-bindings - # on the way... - attrs = {} - names = [] - generic_spec = None - for i in o[start_idx:]: - if i.tag in ('comment', 'binding-private-stmt'): - body.append(self.visit(i, **kwargs)) - - if i.tag == 'binding-attr': - if i.attrib['bindingAttr'].upper() == 'PASS': - attrs['pass_attr'] = i.attrib['id'] or True - elif i.attrib['bindingAttr'].upper() == 'NOPASS': - attrs['pass_attr'] = False - elif i.attrib['bindingAttr']: - attrs[i.attrib['bindingAttr'].lower()] = True - - if i.tag == 'access-spec': - attrs[i.attrib['keyword'].lower()] = True - - if i.tag == 'names': - names += i.findall('name') - if i.tag == 'name': - generic_spec = i.find('generic_spec') - - if i.tag == 'specific-binding': - assert not names - body.append(self.visit(i, attrs=attrs, **kwargs)) - attrs = {} - - elif i.tag in ('generic-binding', 'final-binding'): - body.append(self.visit(i, attrs=attrs, names=names, spec=generic_spec, **kwargs)) - names = [] - attrs = {} - generic_spec = None - - assert not attrs - assert not names - assert generic_spec is None - - # Infer any additional shape information from `!$loki dimension` pragmas - body = attach_pragmas(body, ir.VariableDeclaration) - body = process_dimension_pragmas(body) - body = detach_pragmas(body, ir.VariableDeclaration) - - # Finally: update the typedef with its body - typedef._update(body=body) - typedef.rescope_symbols() - return typedef - - # First, declaration attributes - attrs = {} - if o.find('intent') is not None: - intent = self.visit(o.find('intent'), **kwargs) - attrs.update((intent,)) - - if o.find('attribute-parameter') is not None: - parameter = self.visit(o.find('attribute-parameter'), **kwargs) - attrs.update((parameter,)) - - if o.find('attribute-optional') is not None: - optional = self.visit(o.find('attribute-optional'), **kwargs) - attrs.update((optional,)) - - if o.find('attribute-allocatable') is not None: - allocatable = self.visit(o.find('attribute-allocatable'), **kwargs) - attrs.update((allocatable,)) - - if o.find('attribute-pointer') is not None: - pointer = self.visit(o.find('attribute-pointer'), **kwargs) - attrs.update((pointer,)) - - if o.find('attribute-target') is not None: - target = self.visit(o.find('attribute-target'), **kwargs) - attrs.update((target,)) - - if o.find('attribute-save') is not None: - save = self.visit(o.find('attribute-save'), **kwargs) - attrs.update((save,)) - - if o.find('access-spec') is not None: - access_spec = self.visit(o.find('access-spec'), **kwargs) - attrs.update((access_spec,)) - - if o.find('variables') is not None: - # This is probably a variable declaration - _type = self.visit(o.find('type'), **kwargs) - - if _type.dtype == BasicType.CHARACTER: - char_selector = o.find('char-selector') - if _type.length is None and char_selector is not None: - selector_idx = list(o).index(char_selector) - - if selector_idx > 0: - tk1 = char_selector.get('tk1') - tk2 = char_selector.get('tk2') - - length = None - kind = None - if tk1.lower() in ('', 'len'): - # The first child _should_ be the length selector - length = self.visit(o[0], **kwargs) - - if tk2.lower() == 'kind' or selector_idx > 2: - # There is another value, presumably the kind specifier, which - # should be right before the char-selector - kind = self.visit(o[selector_idx-1], **kwargs) - elif tk1.lower() == 'kind': - # The first child _should_ be the kind selector - kind = self.visit(o[0], **kwargs) - - if tk2.lower() == 'len': - # The second child should then be the length selector - assert selector_idx > 2 - length = self.visit(o[1], **kwargs) - - attrs['length'] = length - attrs['kind'] = kind - - # Then, build the common symbol type for all variables - _type = _type.clone(**attrs) - - # Last, instantiate declared variables - variables = as_tuple(self.visit(o.find('variables'), **kwargs)) - - # check if we have a dimensions keyword - if o.find('dimensions') is not None: - dimensions = self.visit(o.find('dimensions'), **kwargs) - _type = _type.clone(shape=dimensions) - # Attach dimension attribute to variable declaration for uniform - # representation of variables in declarations - variables = as_tuple(v.clone(dimensions=dimensions) for v in variables) - - # Make sure KIND (which can be a name) is in the right scope - scope = kwargs['scope'] - if _type.kind is not None: - kind = AttachScopesMapper()(_type.kind, scope=scope) - _type = _type.clone(kind=kind) - - # EXTERNAL attribute means this is actually a function or subroutine - # Since every symbol refers to a different function we have to update the - # type definition for every symbol individually - external = o.find('attribute-external') is not None - if external: - _type = _type.clone(external=True) - for var in variables: - type_kwargs = _type.__dict__.copy() - if _type.dtype is not None: - return_type = SymbolAttributes(_type.dtype) - type_kwargs['dtype'] = ProcedureType(var.name, is_function=True, return_type=return_type) - else: - type_kwargs['dtype'] = ProcedureType(var.name, is_function=False) - scope.symbol_attrs[var.name] = var.type.clone(**type_kwargs) - - variables = tuple(var.rescope(scope=scope) for var in variables) - return ir.ProcedureDeclaration(symbols=variables, external=True, source=source, label=label) - - # Update symbol table entries and rescope - scope.symbol_attrs.update({var.name: var.type.clone(**_type.__dict__) for var in variables}) - variables = tuple(var.rescope(scope=scope) for var in variables) - return ir.VariableDeclaration(symbols=variables, dimensions=_type.shape, source=source, label=label) - - if o.find('procedures') is not None: - # This is probably a procedure declaration - scope = kwargs['scope'] - - interface = None - if o.find('type') is not None: - _type = self.visit(o.find('type'), **kwargs) - elif o.find('proc-interface') is not None: - interface = self.visit(o.find('proc-interface'), **kwargs) - interface = interface.rescope(scope.get_symbol_scope(interface.name)) - _type = interface.type - else: - self.warn_or_fail('No type or proc-interface given') - _type = SymbolAttributes(BasicType.DEFERRED) - - if o.find('proc-attr-spec') is not None: - # Apparently, the POINTER attribute doesn't show up explicitly anywhere, - # but a proc-attr-spec node seems to be always present when a declaration - # carries the POINTER attribute... - _type = _type.clone(pointer=True) - - _type = _type.clone(**attrs) - - # Build the declared symbols - symbols = self.visit(o.find('procedures'), **kwargs) - - # Update symbol table entries - if isinstance(_type.dtype, ProcedureType): - scope.symbol_attrs.update({var.name: var.type.clone(**_type.__dict__) for var in symbols}) - elif o.find('procedure-declaration-stmt').get('hasProcInterface'): - if o.find('proc-interface').get('id'): - # This is (presumably!) an external function with explicit interface that we - # don't know because the type information is not available, e.g., because it's been - # imported from another module or sits in an intfb.h header file. - # So, we create a ProcedureType object with the proc-interface name and use that - dtype = ProcedureType(o.find('proc-interface').get('id')) - _type = _type.clone(dtype=dtype) - scope.symbol_attrs.update({var.name: var.type.clone(**_type.__dict__) for var in symbols}) - else: - # This is (presumably!) an external or dummy function with implicit interface, - # which is declared as `PROCEDURE() [::] `. Easy, isn't it...? - # Great, now we have to update each symbol's type one-by-one... - interface = _type.dtype - for var in symbols: - dtype = ProcedureType(var.name, is_function=True, return_type=_type) - scope.symbol_attrs[var.name] = _type.clone(dtype=dtype) - - # Rescope variables so they know their type - symbols = tuple(var.rescope(scope=scope) for var in symbols) - return ir.ProcedureDeclaration(symbols=symbols, interface=interface, source=source, label=label) - - if o.find('import-stmt') is not None: - # This is an IMPORT statement in a subroutine declaration inside of - # an interface body - symbols = self.visit(o.find('names'), **kwargs) - symbols = AttachScopesMapper()(symbols, scope=kwargs['scope']) - return ir.Import( - module = None, symbols=symbols, f_import=True, source=kwargs['source'] - ) - - if o.find('prefix-spec') is not None: - # This is the prefix specification of a subroutine/function. We can't - # handle this, yet - return None - - # This should never happen: - raise ValueError('Unknown Declaration') - - def visit_interface(self, o, **kwargs): - scope = kwargs['scope'] - if o.get('type') is not None: - abstract = o.get('type').lower() == 'abstract' - else: - abstract = False - - if o.find('header/defined-operator') is not None: - spec = self.visit(o.find('header/defined-operator'), **kwargs) - elif o.find('header/name') is not None: - spec = self.visit(o.find('header/name'), **kwargs) - else: - spec = None - - if spec is not None: - if spec.name not in scope.symbol_attrs: - scope.symbol_attrs[spec.name] = SymbolAttributes(ProcedureType(name=spec.name, is_generic=True)) - spec = spec.rescope(scope=scope) - - body = [] - # Sometimes, OFP gets it wrong and puts the declarations inside , - # on other occasions it puts it inside ... ¯\_(ツ)_/¯ - grouped_elems = match_tag_sequence(o.find('body/specification/declaration'), [ - ('names', 'procedure-stmt'), - ('function', ), - ('subroutine', ), - ('comment', ), - ]) - if not grouped_elems: - grouped_elems = match_tag_sequence(o.find('body/specification'), [ - ('function',), - ('subroutine',), - ('comment',) - ]) - - for group in grouped_elems: - if len(group) == 1: - # Process indidividual comments/pragmas/functions/subroutines - body.append(self.visit(group[0], **kwargs)) - - elif len(group) == 2: - # Process procedure declarations - body.append(self.create_interface_declaration( - names=group[0], proc_stmt=group[1], scope=scope, source=kwargs['source'] - )) - - return ir.Interface(abstract=abstract, body=as_tuple(body), spec=spec, - label=kwargs['label'], source=kwargs['source']) - - def visit_generic_spec(self, o, **kwargs): - name = o.attrib['name'] - if o.get('keyword').upper() == 'ASSIGNMENT': - name = 'ASSIGNMENT(=)' - return sym.Variable(name=name) - - def visit_defined_operator(self, o, **kwargs): - name = f'OPERATOR({o.attrib["definedOp"]})' - return sym.Variable(name=name) - - def _create_Subroutine_object(self, o, scope, prefix=None): - """Helper method to instantiate a Subroutine object""" - from loki.subroutine import Subroutine # pylint: disable=import-outside-toplevel,cyclic-import - assert o.tag in ('subroutine', 'function') - name = o.attrib['name'] - - # Check if the Subroutine node has been created before by looking it up in the scope - routine = None - if scope is not None and name in scope.symbol_attrs: - proc_type = scope.symbol_attrs[name] # Look-up only in current scope! - if proc_type and proc_type.dtype.procedure != BasicType.DEFERRED: - routine = proc_type.dtype.procedure - if not routine._incomplete: - # We return the existing object right away, unless it exists from a - # previous incomplete parse for which we have to make sure we get a - # full parse first - return routine - - # Dummy args and procedure attributes - header_ast = o.find('header') - if o.tag == 'function': - is_function = True - args = [a.attrib['id'].upper() for a in header_ast.findall('names/name')] - else: - is_function = False - args = [a.attrib['name'].upper() for a in header_ast.findall('arguments/argument')] - - # Subroutine/Function statement suffix (such as result name and language binding) - result_name = None - bind = None - suffix = header_ast.find('suffix') - if suffix is None: - # For absolutely no conceivable reason, suffix is sometimes wrapped inside a "declaration" tag??? - suffix = header_ast.find('declaration/suffix') - if suffix is not None: - if suffix.attrib['hasProcLangBindSpec'] == 'true': - bind = self.visit(header_ast.find('literal')) - if suffix.attrib['result'] == 'result': - result_name = header_ast.find('name').attrib['name'] - - if prefix: - prefix = [a.attrib['spec'].upper() for a in prefix if a.tag == 't-prefix-spec'] - else: - prefix = [] - if header_ast: - prefix += [a.attrib['spec'].upper() for a in header_ast.findall('t-prefix-spec')] - - if routine is None: - routine = Subroutine( - name=name, args=args, prefix=prefix, bind=bind, result_name=result_name, - is_function=is_function, parent=scope, - ast=o, source=self.get_source(o) - ) - else: - routine.__initialize__( - name=name, args=args, docstring=routine.docstring, spec=routine.spec, - body=routine.body, contains=routine.contains, prefix=prefix, bind=bind, - result_name=result_name, is_function=is_function, ast=o, - source=self.get_source(o), incomplete=routine._incomplete - ) - - return routine - - def visit_subroutine(self, o, **kwargs): - # Extract known sections - body_ast = list(o.find('body')) - spec_ast = o.find('body/specification') - spec_ast_idx = body_ast.index(spec_ast) - docs_ast, body_ast = body_ast[:spec_ast_idx], body_ast[spec_ast_idx+1:] - members_ast = o.find('members') - - # Instantiate the object - routine = self._create_Subroutine_object(o, kwargs['scope']) - kwargs['scope'] = routine - - # Create IRs for the docstring and the declaration spec - docstring = self.visit(docs_ast, **kwargs) - docstring = sanitize_ir(docstring, OFP, pp_registry=sanitize_registry[OFP], pp_info=self.pp_info) - spec = self.visit(spec_ast, **kwargs) - spec = sanitize_ir(spec, OFP, pp_registry=sanitize_registry[OFP], pp_info=self.pp_info) - - # Parse "member" subroutines and functions recursively - contains = None - if members_ast is not None: - contains = self.visit(members_ast, **kwargs) - contains.prepend(self.visit(o.find('contains-stmt'), **kwargs)) - - # Finally, take care of the body - if not body_ast: - body = ir.Section(body=()) - else: - body = ir.Section(body=self.visit(body_ast, **kwargs)) - body = sanitize_ir(body, OFP, pp_registry=sanitize_registry[OFP], pp_info=self.pp_info) - - # Finally, call the subroutine constructor on the object again to register all - # bits and pieces in place and rescope all symbols - # pylint: disable=unnecessary-dunder-call - routine.__initialize__( - name=routine.name, args=routine._dummies, docstring=docstring, spec=spec, - body=body, contains=contains, ast=o, prefix=routine.prefix, bind=routine.bind, - result_name=routine.result_name, is_function=routine.is_function, - rescope_symbols=True, source=kwargs['source'], incomplete=False - ) - - # For deferred array dimensions on allocatables, we infer the conceptual - # dimension by finding any `allocate(var())` statements. - routine._infer_allocatable_shapes() - - # Update array shapes with Loki dimension pragmas - with pragmas_attached(routine, ir.VariableDeclaration): - routine.spec = process_dimension_pragmas(routine.spec, scope=routine) - - return routine - - visit_function = visit_subroutine - - def visit_members(self, o, **kwargs): - body = [self.visit(c, **kwargs) for c in o] - body = [c for c in body if c is not None] - return ir.Section(body=as_tuple(body), source=kwargs['source']) - - def _create_Module_object(self, o, scope): - """Helper method to instantiate a Module object""" - from loki.module import Module # pylint: disable=import-outside-toplevel,cyclic-import - - name = o.attrib['name'] - - # Check if the Module node has been created before by looking it up in the scope - if scope is not None and name in scope.symbol_attrs: - module_type = scope.symbol_attrs[name] # Look-up only in current scope - if module_type and module_type.dtype.module != BasicType.DEFERRED: - return module_type.dtype.module - - module = Module(name=name, parent=scope) - self.definitions[name] = module - return module - - def visit_module(self, o, **kwargs): - # Extract known sections - body_ast = list(o.find('body')) - spec_ast = o.find('body/specification') - spec_ast_idx = body_ast.index(spec_ast) - docs_ast, body_ast = body_ast[:spec_ast_idx], body_ast[spec_ast_idx+1:] - - # Instantiate the object - module = self._create_Module_object(o, kwargs['scope']) - kwargs['scope'] = module - - # Pre-populate symbol table with procedure types declared in this module - # to correctly classify inline function calls and type-bound procedures - contains_ast = o.find('members') - contains_stmt = o.find('contains-stmt') - if contains_ast is not None and contains_stmt is not None: - # Note that we overwrite this variable subsequently with the fully parsed subroutines - # where the visit-method for the subroutine/function statement will pick out the existing - # subroutine objects using the weakref pointers stored in the symbol table. - # I know, it's not pretty but alternatively we could hand down this array as part of - # kwargs but that feels like carrying around a lot of bulk, too. - contains = [] - prefix = [] - for member_ast in contains_ast: - if member_ast.tag in ('subroutine', 'function'): - contains += [self._create_Subroutine_object(member_ast, kwargs['scope'], prefix)] - prefix = [] - else: - prefix += [member_ast] - - # Parse the spec - spec = self.visit(spec_ast, **kwargs) - spec = sanitize_ir(spec, OFP, pp_registry=sanitize_registry[OFP], pp_info=self.pp_info) - - # Parse the docstring - if not docs_ast and not spec.body: - # If the spec is empty the docstring is conveniently put flat into the module instead... - docs_ast = o.findall('comment') - docstring = self.visit(docs_ast, **kwargs) - docstring = sanitize_ir(docstring, OFP, pp_registry=sanitize_registry[OFP], pp_info=self.pp_info) - - # Parse member subroutines and functions - if contains_ast is not None and contains_stmt is not None: - contains_stmt = self.visit(contains_stmt, **kwargs) - contains = self.visit(contains_ast, **kwargs) - contains.prepend(contains_stmt) - else: - contains = None - - # Finally, call the module constructor on the object again to register all - # bits and pieces in place and rescope all symbols - # pylint: disable=unnecessary-dunder-call - module.__initialize__( - name=module.name, docstring=docstring, spec=spec, contains=contains, - default_access_spec=module.default_access_spec, public_access_spec=module.public_access_spec, - private_access_spec=module.private_access_spec, ast=o, source=kwargs['source'], - rescope_symbols=True, incomplete=False - ) - return module - - def visit_program(self, o, **kwargs): - self.warn_or_fail('No support for PROGRAM') - - def visit_association(self, o, **kwargs): - return sym.Variable(name=o.attrib['associate-name']) - - def visit_associate(self, o, **kwargs): - # Handle the associates - associations = [(self.visit(a[0], **kwargs), self.visit(a.find('association'), **kwargs)) - for a in o.findall('header/keyword-arguments/keyword-argument')] - - # Create a scope for the associate - parent_scope = kwargs['scope'] - associate = ir.Associate(associations=(), body=(), parent=parent_scope, - label=kwargs['label'], source=kwargs['source']) - kwargs['scope'] = associate - - # Put associate expressions into the right scope and determine type of new symbols - rescoped_associations = [] - for expr, name in associations: - # Put symbols in associated expression into the right scope - expr = AttachScopesMapper()(expr, scope=parent_scope) - - # Determine type of new names - if isinstance(expr, (sym.TypedSymbol, sym.MetaSymbol)): - # Use the type of the associated variable - _type = expr.type.clone(parent=None) - if isinstance(expr, sym.Array) and expr.dimensions is not None: - shape = ExpressionDimensionsMapper()(expr) - if shape == (sym.IntLiteral(1),): - # For a scalar expression, we remove the shape - shape = None - _type = _type.clone(shape=shape) - else: - # TODO: Handle data type and shape of complex expressions - shape = ExpressionDimensionsMapper()(expr) - if shape == (sym.IntLiteral(1),): - # For a scalar expression, we remove the shape - shape = None - _type = SymbolAttributes(BasicType.DEFERRED, shape=shape) - name = name.clone(scope=associate, type=_type) - rescoped_associations += [(expr, name)] - associations = as_tuple(rescoped_associations) - - body = as_tuple(self.visit(o.find('body'), **kwargs)) - associate._update(associations=associations, body=body) - return associate - - def visit_allocate(self, o, **kwargs): - variables = as_tuple(self.visit(v, **kwargs) for v in o.findall('expressions/expression/name')) - kw_args = {arg.attrib['name'].lower(): self.visit(arg, **kwargs) - for arg in o.findall('keyword-arguments/keyword-argument')} - return ir.Allocation(variables=variables, label=kwargs['label'], - source=kwargs['source'], data_source=kw_args.get('source'), - status_var=kw_args.get('stat')) - - def visit_deallocate(self, o, **kwargs): - variables = as_tuple(self.visit(v, **kwargs) for v in o.findall('expressions/expression/name')) - kw_args = {arg.attrib['name'].lower(): self.visit(arg, **kwargs) - for arg in o.findall('keyword-arguments/keyword-argument')} - return ir.Deallocation(variables=variables, label=kwargs['label'], - source=kwargs['source'], status_var=kw_args.get('stat')) - - def visit_use(self, o, **kwargs): - name, module, nature = self.visit(o.find('use-stmt'), **kwargs) - scope = kwargs['scope'] - if o.find('only') is not None: - # ONLY list given (import only selected symbols) - symbols = self.visit(o.find('only'), **kwargs) - # No rename-list - rename_list = None - deferred_type = SymbolAttributes(BasicType.DEFERRED, imported=True) - if module is None: - # Initialize symbol attributes as DEFERRED - for s in symbols: - if isinstance(s, tuple): # Renamed symbol - scope.symbol_attrs[s[1].name] = deferred_type.clone(use_name=s[0]) - else: - scope.symbol_attrs[s.name] = deferred_type - else: - # Import symbol attributes from module - for s in symbols: - if isinstance(s, tuple): # Renamed symbol - _type = module.symbol_attrs.get(s[0], deferred_type) - scope.symbol_attrs[s[1].name] = _type.clone( - imported=True, module=module, use_name=s[0] - ) - else: - # Need to explicitly reset use_name in case we are importing a symbol - # that stems from an import with a rename-list - _type = module.symbol_attrs.get(s.name, deferred_type) - scope.symbol_attrs[s.name] = _type.clone( - imported=True, module=module, use_name=None - ) - symbols = tuple( - s[1].rescope(scope=scope) if isinstance(s, tuple) else s.rescope(scope=scope) for s in symbols - ) - else: - # No ONLY list - symbols = () - # Rename list - rename_list = dict(self.visit(s, **kwargs) for s in o.findall('rename/rename')) - if module is not None: - # Import symbol attributes from module, if available - for k, v in module.symbol_attrs.items(): - # Don't import private module symbols - if v.private or (module.default_access_spec == "private" and not v.public): - continue - if k in rename_list: - local_name = rename_list[k].name - scope.symbol_attrs[local_name] = v.clone( - imported=True, module=module, use_name=k - ) - else: - scope.symbol_attrs[k] = v.clone( - imported=True, module=module, use_name=None - ) - elif rename_list: - # Module not available but some information via rename-list - scope.symbol_attrs.update({ - v.name: v.type.clone(imported=True, use_name=k) for k, v in rename_list.items() - }) - rename_list = tuple(rename_list.items()) if rename_list else None - return ir.Import(module=name, symbols=symbols, rename_list=rename_list, - nature=nature, label=kwargs['label'], source=kwargs['source']) - - def visit_only(self, o, **kwargs): - count = int(o.find('only-list').get('count')) - nodes = [c for c in o if c.tag in ('name', 'rename', 'defined-operator')] - return as_tuple([self.visit(c, **kwargs) for c in nodes[:count]]) - - def visit_rename(self, o, **kwargs): - if o.attrib['defOp1'] or o.attrib['defOp2']: - self.warn_or_fail('OPERATOR in rename-list not yet implemented') - return () - return (o.attrib['id2'], sym.Variable(name=o.attrib['id1'])) - - def visit_use_stmt(self, o, **kwargs): - name = o.attrib['id'] - if o.attrib['hasModuleNature'] != 'false': - self.warn_or_fail('Module nature in USE statement not implemented. Assuming INTRINSIC') - # Do not return module reference for intrinsic modules - return name, None, 'intrinsic' - return name, self.definitions.get(name), None - - def visit_directive(self, o, **kwargs): - source = kwargs['source'] - if '#include' in o.attrib['text']: - # Straight pipe-through node for header includes (#include ...) - match = re.search(r'#include\s[\'"](?P.*)[\'"]', o.attrib['text']) - module = match.groupdict()['module'] - return ir.Import(module=module, c_import=True, source=source) - return ir.PreprocessorDirective(text=source.string.strip(), source=source) - - def visit_exit(self, o, **kwargs): - stmt_tag = f'{o.tag}-stmt' - stmt = self.visit(o.find(stmt_tag), **kwargs) - if o.find('label') is not None: - stmt._update(label=o.find('label').attrib['lbl']) - return stmt - - visit_return = visit_exit - visit_continue = visit_exit - visit_cycle = visit_exit - visit_format = visit_exit - visit_print = visit_exit - visit_open = visit_exit - visit_close = visit_exit - visit_write = visit_exit - visit_read = visit_exit - - def create_intrinsic_from_source(self, o, attrib_name, **kwargs): - label = kwargs['label'] - source = kwargs['source'] - cstart = source.string.lower().find(o.attrib[attrib_name].lower()) - assert cstart != -1 - return ir.Intrinsic(text=source.string[cstart:].strip(), label=label, source=source) - - def visit_exit_stmt(self, o, **kwargs): - return self.create_intrinsic_from_source(o, 'exitKeyword', **kwargs) - - def visit_return_stmt(self, o, **kwargs): - return self.create_intrinsic_from_source(o, 'keyword', **kwargs) - - visit_contains_stmt = visit_return_stmt - - def visit_continue_stmt(self, o, **kwargs): - return self.create_intrinsic_from_source(o, 'continueKeyword', **kwargs) - - def visit_cycle_stmt(self, o, **kwargs): - return self.create_intrinsic_from_source(o, 'cycleKeyword', label=kwargs['label'], source=kwargs['source']) - - def visit_format_stmt(self, o, **kwargs): - return self.create_intrinsic_from_source(o, 'formatKeyword', label=kwargs['label'], source=kwargs['source']) - - def visit_print_stmt(self, o, **kwargs): - return self.create_intrinsic_from_source(o, 'printKeyword', label=kwargs['label'], source=kwargs['source']) - - def visit_open_stmt(self, o, **kwargs): - return self.create_intrinsic_from_source(o, 'openKeyword', label=kwargs['label'], source=kwargs['source']) - - def visit_close_stmt(self, o, **kwargs): - return self.create_intrinsic_from_source(o, 'closeKeyword', label=kwargs['label'], source=kwargs['source']) - - def visit_write_stmt(self, o, **kwargs): - return self.create_intrinsic_from_source(o, 'writeKeyword', label=kwargs['label'], source=kwargs['source']) - - def visit_read_stmt(self, o, **kwargs): - return self.create_intrinsic_from_source(o, 'readKeyword', label=kwargs['label'], source=kwargs['source']) - - def visit_call(self, o, **kwargs): - name, subscripts = self.visit(o.find('name'), **kwargs) - args = tuple(arg for arg in subscripts if not isinstance(arg, tuple)) - kw_args = tuple(arg for arg in subscripts if isinstance(arg, tuple)) - return ir.CallStatement(name=name, arguments=args, kwarguments=kw_args, - label=kwargs['label'], source=kwargs['source']) - - def visit_label(self, o, **kwargs): - assert kwargs['label'] is not None - return ir.Comment('__STATEMENT_LABEL__', label=kwargs['label'], source=kwargs['source']) - - # Expression parsing below; maye move to its own parser..? - - def visit_subscripts(self, o, **kwargs): - return tuple(self.visit(c, **kwargs) for c in o if c.tag in ('subscript', 'argument')) - - def visit_subscript(self, o, **kwargs): - if o.attrib['type'] in ('empty', 'assumed-shape'): - return sym.RangeIndex((None, None, None)) - if o.attrib['type'] == 'simple': - return self.visit(o[0], **kwargs) - if o.attrib['type'] == 'upper-bound-assumed-shape': - return sym.RangeIndex((self.visit(o[0], **kwargs), None, None)) - if o.attrib['type'] == 'range': - lower, upper, step = None, None, None - if o.find('range/lower-bound') is not None: - lower = self.visit(o.find('range/lower-bound'), **kwargs) - if o.find('range/upper-bound') is not None: - upper = self.visit(o.find('range/upper-bound'), **kwargs) - if o.find('range/step') is not None: - step = self.visit(o.find('range/step'), **kwargs) - return sym.RangeIndex((lower, upper, step)) - raise ValueError('Unknown subscript type') - - def visit_dimensions(self, o, **kwargs): - return tuple(self.visit(c, **kwargs) for c in o.findall('dimension')) - - visit_dimension = visit_subscript - - def visit_argument(self, o, **kwargs): - return o.attrib['name'], self.visit(o[0], **kwargs) - - def visit_names(self, o, **kwargs): - return tuple(self.visit(c, **kwargs) for c in o.findall('name')) - - def visit_name(self, o, **kwargs): - scope = kwargs.get('scope', None) - - if o.find('generic-name-list-part') is not None: - # From an external-stmt or use-stmt - name = o.attrib['id'] - if scope: - scope = scope.get_symbol_scope(name) - return sym.Variable(name=name, scope=scope) - - if o.find('generic_spec') is not None: - return self.visit(o.find('generic_spec'), **kwargs) - - num_part_ref = int(o.find('data-ref').attrib['numPartRef']) - subscripts = [self.visit(s, **kwargs) for s in o.findall('subscripts')] - name = None - for i, part_ref in enumerate(o.findall('part-ref')): - name, parent = self.visit(part_ref, **kwargs), name - if parent: - name = name.clone(name=f'{parent.name}%{name.name}', parent=parent) - scope = parent.scope - if scope: - scope = scope.get_symbol_scope(name.name) - name = name.clone(scope=scope) - - if part_ref.attrib['hasSectionSubscriptList'] == 'true': - if i < num_part_ref - 1 or o.attrib['type'] == 'variable': - if subscripts[0]: # If there are no subscripts it cannot be an array but must - # be a function call - arguments = subscripts.pop(0) - kwarguments = tuple(arg for arg in arguments if isinstance(arg, tuple)) - assert not kwarguments - name = name.clone(dimensions=arguments) - - # Check for leftover subscripts - assert len(subscripts) <= 1 - - if not 'type' in o.attrib or o.attrib['type'] == 'variable': - # name is just used as an identifier or without any subscripts - assert not subscripts - return name - - if o.attrib['type'] == 'procedure': - return name, subscripts[0] if subscripts else () - - if subscripts and not subscripts[0]: - # Function call with no arguments (gaaawwddd...) - return sym.InlineCall(name, parameters=()) - - # unpack into positional and keyword args - subscripts = subscripts[0] if subscripts else () - kwarguments = tuple(arg for arg in subscripts if isinstance(arg, tuple)) - arguments = tuple(arg for arg in subscripts if not isinstance(arg, tuple)) - - cast_names = ('REAL', 'INT') - if str(name).upper() in cast_names: - assert arguments - expr = arguments[0] - if kwarguments: - assert len(arguments) == 1 - assert len(kwarguments) == 1 and kwarguments[0][0] == 'kind' - kind = kwarguments[0][1] - else: - kind = arguments[1] if len(arguments) > 1 else None - return sym.Cast(name, expr, kind=kind) - - if subscripts: - # This may potentially be an inline call - intrinsic_calls = ( - 'MIN', 'MAX', 'EXP', 'SQRT', 'ABS', 'LOG', 'MOD', 'SELECTED_INT_KIND', - 'SELECTED_REAL_KIND', 'ALLOCATED', 'PRESENT', 'SIGN', 'EPSILON', 'NULL', - 'SIZE', 'LBOUND', 'UBOUND', 'LOC' - ) - if str(name).upper() in intrinsic_calls or kwarguments: - return sym.InlineCall(name, parameters=arguments, kw_parameters=kwarguments) - - _type = name._lookup_type(kwargs['scope']) - if subscripts and _type and isinstance(_type.dtype, ProcedureType): - return sym.InlineCall(name, parameters=arguments, kw_parameters=kwarguments) - - # We end up here if it is ambiguous (i.e., OFP did not know what the symbol is) - # which is either a function call, an array with subscript or a name without anything further - assert o.attrib['type'] == 'ambiguous' and not getattr(name, 'dimensions', None) - assert not kwarguments - return name.clone(dimensions=arguments or None) - - def visit_part_ref(self, o, **kwargs): - return sym.Variable(name=o.attrib['id']) - - def visit_literal(self, o, **kwargs): - source = kwargs['source'] - boz_literal = o.find('boz-literal-constant') - if boz_literal is not None: - return sym.IntrinsicLiteral(boz_literal.attrib['constant']) - - kw_args = {'source': source} - value = o.attrib['value'] - _type = o.attrib['type'] if 'type' in o.attrib else None - if _type is not None: - tmap = {'bool': BasicType.LOGICAL, 'int': BasicType.INTEGER, - 'real': BasicType.REAL, 'char': BasicType.CHARACTER} - _type = tmap[_type] if _type in tmap else BasicType.from_fortran_type(_type) - kw_args['type'] = _type - kind_param = o.find('kind-param') - if kind_param is not None: - kind = kind_param.attrib['kind'] - if kind.isnumeric(): - kw_args['kind'] = sym.Literal(value=int(kind)) - else: - kw_args['kind'] = AttachScopesMapper()(sym.Variable(name=kind), scope=kwargs['scope']) - kw_args.pop('source') - return sym.Literal(value, **kw_args) - - def visit_array_constructor_values(self, o, **kwargs): - values = [self.visit(v, **kwargs) for v in o.findall('value')] - values = [v for v in values if v is not None] # Filter empy values - return sym.LiteralList(values=as_tuple(values)) - - def visit_operation(self, o, **kwargs): - """ - Construct expressions from individual operations, using left-recursion. - """ - ops = [self.visit(op, **kwargs) for op in o.findall('operator')] - ops = [str(op).lower() for op in ops if op is not None] # Filter empty ops - exprs = [self.visit(c, **kwargs) for c in o.findall('operand')] - exprs = [e for e in exprs if e is not None] # Filter empty operands - - # Left-recurse on the list of operations and expressions - exprs = deque(exprs) - expression = exprs.popleft() - for op in ops: - - if op == '+': - expression = sym.Sum((expression, exprs.popleft())) - elif op == '-': - if len(exprs) > 0: - # Binary minus - expression = sym.Sum((expression, sym.Product((-1, exprs.popleft())))) - else: - # Unary minus - expression = sym.Product((-1, expression)) - elif op == '*': - expression = sym.Product((expression, exprs.popleft())) - elif op == '/': - expression = sym.Quotient(numerator=expression, denominator=exprs.popleft()) - elif op == '**': - expression = sym.Power(base=expression, exponent=exprs.popleft()) - elif op in ('==', '.eq.'): - expression = sym.Comparison(expression, '==', exprs.popleft()) - elif op in ('/=', '.ne.'): - expression = sym.Comparison(expression, '!=', exprs.popleft()) - elif op in ('>', '.gt.'): - expression = sym.Comparison(expression, '>', exprs.popleft()) - elif op in ('<', '.lt.'): - expression = sym.Comparison(expression, '<', exprs.popleft()) - elif op in ('>=', '.ge.'): - expression = sym.Comparison(expression, '>=', exprs.popleft()) - elif op in ('<=', '.le.'): - expression = sym.Comparison(expression, '<=', exprs.popleft()) - elif op == '.and.': - expression = sym.LogicalAnd((expression, exprs.popleft())) - elif op == '.or.': - expression = sym.LogicalOr((expression, exprs.popleft())) - elif op == '.not.': - expression = sym.LogicalNot(expression) - elif op == '.eqv.': - e = (expression, exprs.popleft()) - expression = sym.LogicalOr((sym.LogicalAnd(e), sym.LogicalNot(sym.LogicalOr(e)))) - elif op == '.neqv.': - e = (expression, exprs.popleft()) - expression = sym.LogicalAnd((sym.LogicalNot(sym.LogicalAnd(e)), sym.LogicalOr(e))) - elif op == '//': - expression = StringConcat((expression, exprs.popleft())) - else: - raise RuntimeError(f'OFP: Unknown expression operator: {op}') - - if o.find('parenthesized_expr') is not None: - # Force explicitly parenthesised operations - if isinstance(expression, sym.Sum): - expression = ParenthesisedAdd(expression.children) - if isinstance(expression, sym.Product): - expression = ParenthesisedMul(expression.children) - if isinstance(expression, sym.Power): - expression = ParenthesisedPow(expression.base, expression.exponent) - if isinstance(expression, sym.Quotient): - expression = ParenthesisedDiv(expression.numerator, expression.denominator) - - assert len(exprs) == 0 - return expression - - def visit_operator(self, o, **kwargs): - return o.attrib['operator'] - - def create_typedef_procedure_declaration(self, comps, iface=None, attrs=None, scope=None, source=None): - """ - Utility method to create individual declarations from a group of AST nodes. - """ - if iface is not None: - iface = self.visit(iface, scope=scope) - iface = AttachScopesMapper()(iface, scope=scope) - - if attrs is not None: - attrs = [a.get('attrSpecKeyword').upper() for a in attrs] - else: - attrs = [] - - type_attrs = { - 'pointer': 'POINTER' in attrs, - } - - if iface: - type_attrs['dtype'] = iface.type.dtype - - symbols = self.visit(comps, scope=scope, source=source) - scope.symbol_attrs.update({s.name: s.type.clone(**type_attrs) for s in symbols}) - symbols = tuple(s.rescope(scope=scope) for s in symbols) - return ir.ProcedureDeclaration(symbols=symbols, interface=iface, source=source) - - def create_typedef_variable_declaration(self, t, comps, attr=None, scope=None, source=None): - """ - Utility method to create individual declarations from a group of AST nodes. - """ - attrs = [] - if attr: - attrs = [a.attrib['attrKeyword'].upper() - for a in attr.findall('attribute/component-attr-spec')] - - type_attrs = { - 'pointer': 'POINTER' in attrs, - 'allocatable': 'ALLOCATABLE' in attrs, - } - stype = self.visit(t, scope=scope).clone(**type_attrs) - - # Derive variables for this declaration entry - variables = [] - for v in comps.findall('component'): - if not v.attrib: - continue - if 'DIMENSION' in attrs: - # Dimensions are provided via `dimension` keyword - attrib = attr.findall('attribute')[attrs.index('DIMENSION')] - deferred_shape = attrib.find('deferred-shape-spec-list') - else: - deferred_shape = v.find('deferred-shape-spec-list') - - initial = None - dimensions = [] - - v_children = list(v) - if v.get('hasInitialComponentValue') == 'true': - init_idx = v_children.index(v.find('component-initialization')) - initial = self.visit(v_children[init_idx-1], scope=scope) - initial = AttachScopesMapper()(initial, scope=scope) - v_children = v_children[:init_idx-1] + v_children[init_idx+1:] - - if deferred_shape is not None: - dim_count = int(deferred_shape.attrib['count']) - dimensions = [sym.RangeIndex((None, None, None)) - for _ in range(dim_count)] - else: - dimensions = as_tuple(self.visit(c, scope=scope) for c in v_children) - dimensions = as_tuple( - AttachScopesMapper()(d, scope=scope) for d in dimensions if d is not None - ) or None - - v_type = stype.clone(shape=dimensions, initial=initial) - v_name = v.attrib['name'] - - scope.symbol_attrs[v_name] = v_type - variables += [sym.Variable(name=v_name, scope=scope, dimensions=dimensions)] - - return ir.VariableDeclaration(symbols=variables, source=source) - - def create_interface_declaration(self, names, proc_stmt, scope=None, source=None): - """ - Utility method to create individual procedure declarations for an interface - from a group of AST nodes - """ - symbols = self.visit(names, scope=scope) - symbols = AttachScopesMapper()(symbols, scope=scope) - module_proc = proc_stmt.get('module').lower() == 'module' - return ir.ProcedureDeclaration(symbols=symbols, module=module_proc, source=source) - - def create_enum(self, o, **kwargs): - """ - Utility method to create an ``ENUM`` IR node from a declaration node - """ - scope = kwargs['scope'] - symbols = [] - comments = [] - value = None - - # Step through the items and keep values and enumerator stmts to build symbol list - for i in o: - - if i.tag == 'enumerator': - # The actual enumerator stmt (this gives us the symbol name) - if i.attrib['hasExpr'] == 'true': - # This has a value assigned, which we must have seen before - assert value is not None - _type = SymbolAttributes(BasicType.INTEGER, initial=value) - value = None - else: - assert value is None - _type = SymbolAttributes(BasicType.INTEGER) - symbols += [sym.Variable(name=i.attrib['id'], type=_type, scope=scope)] - - else: - # Something else: let's just recurse on it and save it as a value if - # it doesn't yield None - item = self.visit(i, **kwargs) - if isinstance(item, ir.Comment): - comments += [item] - elif item is not None: - assert value is None - value = item - - return (ir.Enumeration(symbols=as_tuple(symbols), source=kwargs['source'], label=kwargs['label']), *comments) diff --git a/loki/frontend/preprocessing.py b/loki/frontend/preprocessing.py index 997be47e5..9e3a88992 100644 --- a/loki/frontend/preprocessing.py +++ b/loki/frontend/preprocessing.py @@ -25,9 +25,6 @@ __all__ = ['preprocess_cpp', 'sanitize_input', 'sanitize_registry', 'PPRule'] -OFP = Frontend.OFP - - def preprocess_cpp(source, filepath=None, includes=None, defines=None): """ Invoke an external C-preprocessor to sanitize input files. @@ -233,17 +230,6 @@ def postprocess(self, ir, info): 'FYPP ANNOTATIONS': PPRule(match=re.compile(r'(# [1-9].*\".*\.fypp\"\n)'), replace=''), }, OMNI: {}, - OFP: { - # Remove various IBM directives - 'IBM_DIRECTIVES': PPRule(match=re.compile(r'(@PROCESS.*\n)'), replace='\n'), - - # Despite F2008 compatability, OFP does not recognise the CONTIGUOUS keyword :( - 'CONTIGUOUS': PPRule( - match=re.compile(r', CONTIGUOUS', re.I), replace='', postprocess=reinsert_contiguous), - - # Strip line annotations from Fypp preprocessor - 'FYPP ANNOTATIONS': PPRule(match=re.compile(r'(# [1-9].*\".*\.fypp\"\n)'), replace=''), - }, FP: { # Remove various IBM directives 'IBM_DIRECTIVES': PPRule(match=re.compile(r'(@PROCESS.*\n)'), replace='\n'), diff --git a/loki/frontend/util.py b/loki/frontend/util.py index 59e4ac716..89872c727 100644 --- a/loki/frontend/util.py +++ b/loki/frontend/util.py @@ -39,8 +39,6 @@ class Frontend(IntEnum): """ #: The OMNI compiler frontend OMNI = 1 - #: The Open Fortran Parser - OFP = 2 #: Fparser 2 from STFC FP = 3 #: Reduced functionality parsing using regular expressions @@ -68,7 +66,7 @@ def available_frontends(xfail=None, skip=None, include_regex=False): Use as ..code-block:: - @pytest.mark.parametrize('frontend', available_frontends(xfail=[OMNI, (OFP, 'Because...')])) + @pytest.mark.parametrize('frontend', available_frontends(xfail=[OMNI, (FP, 'Because...')])) def my_test(frontend): source = Sourcefile.from_file('some.F90', frontend=frontend) # ... @@ -112,8 +110,6 @@ def my_test(frontend): # Build the list of parameters params = [] for f in Frontend: - if f == Frontend.OFP: - continue # OFP is now deprecated! if f in skip: params += [pytest.param(f, marks=pytest.mark.skip(reason=skip[f]))] elif f in xfail: @@ -201,7 +197,7 @@ def inline_labels(ir): """ Find labels and merge them onto the following node. - Note: This is currently only required for OMNI and OFP frontends which + Note: This is currently only required for the OMNI frontend which has labels as nodes next to the corresponding statement without any connection between both. """ @@ -382,10 +378,9 @@ def sanitize_ir(_ir, frontend, pp_registry=None, pp_info=None): # Revert OMNI's array dimension expansion from `a(n)` => `arr(1:n)` _ir = RangeIndexTransformer(invalidate_source=False).visit(_ir) - if frontend in (OMNI, Frontend.OFP): _ir = inline_labels(_ir) - if frontend in (FP, Frontend.OFP): + if frontend == FP: _ir = CombineMultilinePragmasTransformer(inplace=True, invalidate_source=False).visit(_ir) _ir = RemoveDuplicateVariableDeclarationsForExternalProcedures(inplace=True, invalidate_source=False).visit(_ir) diff --git a/loki/module.py b/loki/module.py index 27fd38f5f..8aac97593 100644 --- a/loki/module.py +++ b/loki/module.py @@ -9,7 +9,7 @@ Contains the declaration of :any:`Module` to represent Fortran modules. """ from loki.frontend import ( - get_fparser_node, parse_omni_ast, parse_ofp_ast, parse_fparser_ast, + get_fparser_node, parse_omni_ast, parse_fparser_ast, parse_regex_source ) from loki.ir import ( @@ -143,31 +143,6 @@ def from_omni(cls, ast, raw_source, definitions=None, parent=None, type_map=None type_map=type_map, scope=parent ) - @classmethod - def from_ofp(cls, ast, raw_source, definitions=None, pp_info=None, parent=None): - """ - Create :any:`Module` from :any:`OFP` parse tree - - Parameters - ---------- - ast : - The OFP parse tree - raw_source : str - Fortran source string - definitions : list - List of external :any:`Module` to provide derived-type and procedure declarations - pp_info : - Preprocessing info as obtained by :any:`sanitize_input` - parent : :any:`Scope`, optional - The enclosing parent scope of the module. - """ - if ast.tag != 'module': - ast = ast.find('file/module') - return parse_ofp_ast( - ast=ast, pp_info=pp_info, raw_source=raw_source, - definitions=definitions, scope=parent - ) - @classmethod def from_fparser(cls, ast, raw_source, definitions=None, pp_info=None, parent=None): """ diff --git a/loki/program_unit.py b/loki/program_unit.py index 73a8e245d..0774cb0e1 100644 --- a/loki/program_unit.py +++ b/loki/program_unit.py @@ -9,7 +9,7 @@ from loki.expression import Variable, parse_expr from loki.frontend import ( - Frontend, parse_omni_source, parse_ofp_source, parse_fparser_source, + Frontend, parse_omni_source, parse_fparser_source, RegexParserClass, preprocess_cpp, sanitize_input ) from loki.ir import ( @@ -163,11 +163,6 @@ def from_source(cls, source, definitions=None, preprocess=False, return cls.from_omni(ast=ast, raw_source=source, definitions=definitions, type_map=type_map, parent=parent) - if frontend == Frontend.OFP: - ast = parse_ofp_source(source) - return cls.from_ofp(ast=ast, raw_source=source, definitions=definitions, - pp_info=pp_info, parent=parent) # pylint: disable=possibly-used-before-assignment - if frontend == Frontend.FP: ast = parse_fparser_source(source) return cls.from_fparser(ast=ast, raw_source=source, definitions=definitions, diff --git a/loki/sourcefile.py b/loki/sourcefile.py index 5f5c1e1fa..6a9972f80 100644 --- a/loki/sourcefile.py +++ b/loki/sourcefile.py @@ -15,9 +15,9 @@ from loki.backend.fgen import fgen from loki.backend.cufgen import cufgen from loki.frontend import ( - Frontend, OMNI, FP, REGEX, sanitize_input, Source, read_file, preprocess_cpp, - parse_omni_source, parse_ofp_source, parse_fparser_source, - parse_omni_ast, parse_ofp_ast, parse_fparser_ast, parse_regex_source, + Frontend, OMNI, FP, REGEX, sanitize_input, Source, read_file, + preprocess_cpp, parse_omni_source, parse_fparser_source, + parse_omni_ast, parse_fparser_ast, parse_regex_source, RegexParserClass ) @@ -32,9 +32,6 @@ __all__ = ['Sourcefile'] -OFP = Frontend.OFP - - class Sourcefile: """ Class to handle and manipulate source files, storing :any:`Module` and @@ -136,9 +133,6 @@ def from_file(cls, filename, definitions=None, preprocess=False, includes=includes, defines=defines, xmods=xmods, omni_includes=omni_includes) - if frontend == OFP: - return cls.from_ofp(source, filepath, definitions=definitions) - if frontend == FP: return cls.from_fparser(source, filepath, definitions=definitions) @@ -205,42 +199,6 @@ def _from_omni_ast(cls, ast, path=None, raw_source=None, definitions=None, typet source = Source(lines, string=raw_source, file=path) return cls(path=path, ir=ir, ast=ast, source=source) - @classmethod - def from_ofp(cls, raw_source, filepath, definitions=None): - """ - Parse a given source string using the Open Fortran Parser (OFP) frontend - - Parameters - ---------- - raw_source : str - Fortran source string - filepath : str or :any:`pathlib.Path` - The filepath of this source file - definitions : list - List of external :any:`Module` to provide derived-type and procedure declarations - """ - # Preprocess using internal frontend-specific PP rules - # to sanitize input and work around known frontend problems. - source, pp_info = sanitize_input(source=raw_source, frontend=OFP) - - # Parse the file content into a Fortran AST - ast = parse_ofp_source(source, filepath=filepath) - - return cls._from_ofp_ast(path=filepath, ast=ast, definitions=definitions, - pp_info=pp_info, raw_source=raw_source) - - @classmethod - def _from_ofp_ast(cls, ast, path=None, raw_source=None, definitions=None, pp_info=None): - """ - Generate the full set of :any:`Subroutine` and :any:`Module` members - in the :any:`Sourcefile`. - """ - ir = parse_ofp_ast(ast.find('file'), pp_info=pp_info, definitions=definitions, raw_source=raw_source) - - lines = (1, raw_source.count('\n') + 1) - source = Source(lines, string=raw_source, file=path) - return cls(path=path, ir=ir, ast=ast, source=source) - @classmethod def from_fparser(cls, raw_source, filepath, definitions=None): """ @@ -343,9 +301,6 @@ def from_source(cls, source, definitions=None, preprocess=False, return cls.from_omni(source, filepath=None, definitions=definitions, includes=includes, defines=defines, xmods=xmods, omni_includes=omni_includes) - if frontend == OFP: - return cls.from_ofp(source, filepath=None, definitions=definitions) - if frontend == FP: return cls.from_fparser(source, filepath=None, definitions=definitions) @@ -378,7 +333,7 @@ def make_complete(self, **frontend_args): elif frontend == OMNI: frontend_argnames = ['definitions', 'type_map', 'symbol_map', 'scope'] xmods = frontend_args.get('xmods') - elif frontend in (OFP, FP): + elif frontend == FP: frontend_argnames = ['definitions', 'scope'] else: raise NotImplementedError(f'Unknown frontend: {frontend}') @@ -402,10 +357,6 @@ def make_complete(self, **frontend_args): elif frontend == OMNI: ast = parse_omni_source(source=source, xmods=xmods) ir_ = parse_omni_ast(ast=ast, raw_source=raw_source, **sanitized_frontend_args) - elif frontend == OFP: - ast = parse_ofp_source(source=source) - ir_ = parse_ofp_ast(ast, raw_source=raw_source, pp_info=pp_info, - **sanitized_frontend_args) elif frontend == FP: # Fparser is unable to parse comment-only source files/strings, # so we see if this is only comments and convert them ourselves diff --git a/loki/subroutine.py b/loki/subroutine.py index f2f82f78f..717e26f03 100644 --- a/loki/subroutine.py +++ b/loki/subroutine.py @@ -7,7 +7,7 @@ from loki.expression import symbols as sym from loki.frontend import ( - parse_omni_ast, parse_ofp_ast, parse_fparser_ast, get_fparser_node, + parse_omni_ast, parse_fparser_ast, get_fparser_node, parse_regex_source ) from loki.ir import ( @@ -180,31 +180,6 @@ def from_omni(cls, ast, raw_source, definitions=None, parent=None, type_map=None type_map=type_map, scope=parent ) - @classmethod - def from_ofp(cls, ast, raw_source, definitions=None, pp_info=None, parent=None): - """ - Create :any:`Subroutine` from :any:`OFP` parse tree - - Parameters - ---------- - ast : - The OFP parse tree - raw_source : str - Fortran source string - definitions : list - List of external :any:`Module` to provide derived-type and procedure declarations - pp_info : - Preprocessing info as obtained by :any:`sanitize_input` - parent : :any:`Scope`, optional - The enclosing parent scope of the subroutine, typically a :any:`Module`. - """ - if ast.tag not in ('subroutine', 'function'): - ast = [r for r in as_tuple(ast.find('file')) if r.tag in ('subroutine', 'function')].pop() - return parse_ofp_ast( - ast=ast, pp_info=pp_info, raw_source=raw_source, - definitions=definitions, scope=parent - ) - @classmethod def from_fparser(cls, ast, raw_source, definitions=None, pp_info=None, parent=None): """