def test_multi_stmt_line1(): '''Check that simple statements separated by ; on a single line are correctly split into multiple lines by FortranReaderBase ''' code = "do i=1,10;b=20 ; c=30" reader = FortranStringReader(code) mode = FortranFormat(True, False) reader.set_format(mode) line1 = reader.next() assert isinstance(line1, Line) assert line1.line == "do i=1,10" assert line1.span == (1, 1) assert line1.label is None assert line1.name is None assert line1.reader is reader line2 = reader.next() assert isinstance(line2, Line) assert line2.line == "b=20" assert line2.span is line1.span assert line2.label is None assert line2.name is None assert line2.reader is reader line3 = reader.next() assert isinstance(line3, Line) assert line3.line == "c=30" assert line3.span is line1.span assert line3.label is None assert line3.name is None assert line3.reader is reader
def __init__(self, parent, expr="UNSET", typeselect=False): ''' Construct a SelectionGen for creating a SELECT block :param parent: node to which to add this select block as a child :type parent: :py:class:`psyclone.f2pygen.BaseGen` :param str expr: the CASE expression :param bool typeselect: whether or not this is a SELECT TYPE rather than a SELECT CASE ''' self._typeselect = typeselect reader = FortranStringReader( "SELECT CASE (x)\nCASE (1)\nCASE DEFAULT\nEND SELECT") reader.set_format(FortranFormat(True, True)) # free form, strict select_line = reader.next() self._case_line = reader.next() self._case_default_line = reader.next() end_select_line = reader.next() if self._typeselect: select = SelectType(parent.root, select_line) else: select = SelectCase(parent.root, select_line) endselect = EndSelect(select, end_select_line) select.expr = expr select.content.append(endselect) BaseGen.__init__(self, parent, select)
def parse(cls, line, label='', isfree=True, isstrict=False): '''Tries to parse a Fortran line using the given class cls. If successful, it then converts the parsed statement back to a string. If isstrict is false, it will then try to parse this string again (recursively calling itself, with isstrict set to true) and make sure that the re-parsed string is identical to the input. It returns the string representation of the parsed input line. ''' if label: line = label + ' : ' + line reader = FortranStringReader(line) reader.set_format(fparser.common.sourceinfo.FortranFormat( isfree, isstrict)) item = next(reader) if not cls.match(item.get_line()): raise ValueError('%r does not match %s pattern' % (line, cls.__name__)) stmt = cls(item, item) if stmt.isvalid: # Check that we can successfully parse the string representation # of the parsed object stmt_string = str(stmt) if not isstrict: reparsed_stmt_string = parse(cls, stmt_string, isstrict=True) if stmt_string != reparsed_stmt_string: raise ValueError( 'Failed to parse %r with %s pattern in Pyf ' 'mode, got %r' % (stmt_string, cls.__name__, reparsed_stmt_string)) return stmt_string raise ValueError('parsing %r with %s pattern failed' % (line, cls.__name__))
def test_implicit_topyf(monkeypatch): ''' Tests for the topyf() method of HasImplicitStmt. ''' from fparser.common.readfortran import FortranStringReader from fparser.common.sourceinfo import FortranFormat from fparser.one.parsefortran import FortranParser # We can't just create a HasImplicitStmt object so we get the parser # to create a module object as that sub-classes HasImplicitStmt (amongst # other things). string = '''\ module some_block implicit real (a-e) implicit integer (f-z) end module some_block ''' reader = FortranStringReader(string) reader.set_format(FortranFormat(True, False)) parser = FortranParser(reader) parser.parse() # Get the module object mod = parser.block.content[0] code = mod.topyf() assert "! default IMPLICIT rules apply" in code mod.content[0].analyze() mod.content[1].analyze() code = mod.topyf() assert "REAL (a, b, c, d, e)" in code assert "INTEGER (f, g, h" in code monkeypatch.setattr(mod.a, "implicit_rules", None) code = mod.topyf() assert "IMPLICIT NONE" in code
def adduse(name, parent, only=False, funcnames=None): ''' Adds a use statement with the specified name to the supplied object. This routine is required when modifying an existing AST (e.g. when modifying a kernel). The classes are used when creating an AST from scratch (for the PSy layer). :param str name: name of module to USE :param parent: node in fparser1 AST to which to add this USE as a child :type parent: :py:class:`fparser.one.block_statements.*` :param bool only: whether this USE has an "ONLY" clause :param list funcnames: list of quantities to follow the "ONLY" clause :returns: an fparser1 Use object :rtype: :py:class:`fparser.one.block_statements.Use` ''' reader = FortranStringReader("use kern,only : func1_kern=>func1") reader.set_format(FortranFormat(True, True)) # free form, strict myline = reader.next() # find an appropriate place to add in our use statement while not (isinstance(parent, fparser1.block_statements.Program) or isinstance(parent, fparser1.block_statements.Module) or isinstance(parent, fparser1.block_statements.Subroutine)): parent = parent.parent use = fparser1.block_statements.Use(parent, myline) use.name = name use.isonly = only if funcnames is None: funcnames = [] use.isonly = False use.items = funcnames parent.content.insert(0, use) return use
def __init__(self, parent, name="", args=None, implicitnone=False): ''' :param parent: node in AST to which to add Subroutine as a child :type parent: :py:class:`psyclone.f2pygen.BaseGen` :param str name: name of the Fortran subroutine :param list args: list of arguments accepted by the subroutine :param bool implicitnone: whether or not we should specify "implicit none" for the body of this subroutine ''' reader = FortranStringReader( "subroutine vanilla(vanilla_arg)\nend subroutine") reader.set_format(FortranFormat(True, True)) # free form, strict subline = reader.next() endsubline = reader.next() from fparser.one.block_statements import Subroutine, EndSubroutine self._sub = Subroutine(parent.root, subline) self._sub.name = name if args is None: args = [] self._sub.args = args endsub = EndSubroutine(self._sub, endsubline) self._sub.content.append(endsub) ProgUnitGen.__init__(self, parent, self._sub) if implicitnone: self.add(ImplicitNoneGen(self))
def test_get_type_by_name(monkeypatch): ''' Tests for HasImplicitStmt.get_type_by_name(). ''' from fparser.common.utils import AnalyzeError from fparser.common.readfortran import FortranStringReader from fparser.common.sourceinfo import FortranFormat from fparser.one.typedecl_statements import Real, Integer from fparser.one.parsefortran import FortranParser # We can't just create a HasImplicitStmt object so we get the parser # to create a module object as that sub-classes HasImplicitStmt (amongst # other things). string = '''\ module some_block end module some_block ''' reader = FortranStringReader(string) reader.set_format(FortranFormat(True, False)) parser = FortranParser(reader) parser.parse() mod = parser.block.content[0] # Now we have a Module object, we can call get_type_by_name()... rtype = mod.get_type_by_name("a_real") assert isinstance(rtype, Real) itype = mod.get_type_by_name("i_int") assert isinstance(itype, Integer) # Check that we raise the correct error if we don't have any implicit # rules set monkeypatch.setattr(mod.a, "implicit_rules", None) with pytest.raises(AnalyzeError) as err: _ = mod.get_type_by_name("i_int") assert "Implicit rules mapping is null" in str(err)
def test_get_type_by_name_implicit(): ''' Tests for HasImplicitStmt.get_type_by_name() when the source code contains IMPLICIT statements. ''' from fparser.common.readfortran import FortranStringReader from fparser.common.sourceinfo import FortranFormat from fparser.one.typedecl_statements import Real, Integer from fparser.one.parsefortran import FortranParser # We can't just create a HasImplicitStmt object so we get the parser # to create a module object as that sub-classes HasImplicitStmt (amongst # other things). string = '''\ module some_block implicit real (a-e) implicit integer (f-z) end module some_block ''' reader = FortranStringReader(string) reader.set_format(FortranFormat(True, False)) parser = FortranParser(reader) parser.parse() # Get the module object mod = parser.block.content[0] # We have to run the analyze method on the Implicit objects # produced by the parser in order to populate the implicit_rules # of the module. mod.content[0].analyze() mod.content[1].analyze() # Now we can call get_type_by_name()... rtype = mod.get_type_by_name("a_real") assert isinstance(rtype, Real) itype = mod.get_type_by_name("f_int") assert isinstance(itype, Integer)
def test_base_fixed_continuation(log): ''' Tests that FortranReaderBase.get_source_item() logs the correct messages when there are quote mismatches across a continuation in fixed format. ''' code = ' character(4) :: cheese = "a & !\n & b' log.reset() unit_under_test = FortranStringReader(code) mode = FortranFormat(False, False) unit_under_test.set_format(mode) # Force sloppy fixed format unit_under_test.get_source_item() assert log.messages['debug'] == [] assert log.messages['info'] == [] assert log.messages['error'] == [] assert log.messages['critical'] == [] expected = 'following character continuation: \'"\', expected None.' result = log.messages['warning'][0].split('<==')[1].lstrip() assert result == expected code = ' x=1 &\n +1 &\n -2' log.reset() unit_under_test = FortranStringReader(code) mode = FortranFormat(False, False) unit_under_test.set_format(mode) # Force sloppy fixed format unit_under_test.get_source_item() assert log.messages['debug'] == [] assert log.messages['info'] == [] assert log.messages['error'] == [] assert log.messages['critical'] == [] expected = 'free format line continuation character `&\' detected ' \ + 'in fix format code\n 2: +1 &\n 3: -2' result = log.messages['warning'][0].split('<==')[1].lstrip() assert result == expected
def test_base_handle_multilines(log): ''' Tests that FortranReaderBase.get_source_item() logs the correct messages when there are quote discrepancies. ''' code = 'character(8) :: test = \'foo"""bar' log.reset() unit_under_test = FortranStringReader(code) mode = FortranFormat(True, True) unit_under_test.set_format(mode) # Force strict free format unit_under_test.get_source_item() assert log.messages['debug'] == [] assert log.messages['info'] == [] assert log.messages['error'] == [] assert log.messages['critical'] == [] expected = 'multiline prefix contains odd number of "\'" characters' result = log.messages['warning'][0].split('<==')[1].lstrip() assert result == expected code = 'goo """boo\n doo""" soo \'foo' log.reset() unit_under_test = FortranStringReader(code) mode = FortranFormat(True, True) unit_under_test.set_format(mode) # Force strict free format unit_under_test.get_source_item() assert log.messages['debug'] == [] assert log.messages['info'] == [] assert log.messages['error'] == [] assert log.messages['critical'] == [] expected = 'following character continuation: "\'", expected None.' result = log.messages['warning'][0].split('<==')[1].lstrip() assert result == expected
def __init__(self, parent, datatype="", entity_decls=None, intent="", pointer=False, kind="", dimension="", allocatable=False): ''' :param parent: node to which to add this declaration as a child :type parent: :py:class:`psyclone.f2pygen.BaseGen` :param str datatype: the (intrinsic) type for this declaration :param list entity_decls: list of variable names to declare :param str intent: the INTENT attribute of this declaration :param bool pointer: whether or not this is a pointer declaration :param str kind: the KIND attribute to use for this declaration :param str dimension: the DIMENSION specifier (i.e. the xx in DIMENSION(xx)) :param bool allocatable: whether this declaration is for an ALLOCATABLE quantity :raises RuntimeError: if no variable names are specified :raises RuntimeError: if datatype is not one of "integer" or "real" ''' if entity_decls is None: raise RuntimeError( "Cannot create a variable declaration without specifying the " "name(s) of the variable(s)") fort_fmt = FortranFormat(True, False) # free form, strict if datatype.lower() == "integer": reader = FortranStringReader("integer :: vanilla") reader.set_format(fort_fmt) myline = reader.next() self._decl = fparser1.typedecl_statements.Integer(parent.root, myline) elif datatype.lower() == "real": reader = FortranStringReader("real :: vanilla") reader.set_format(fort_fmt) myline = reader.next() self._decl = fparser1.typedecl_statements.Real(parent.root, myline) else: raise RuntimeError( "f2pygen:DeclGen:init: Only integer and real are currently" " supported and you specified '{0}'".format(datatype)) # make a copy of entity_decls as we may modify it local_entity_decls = entity_decls[:] self._decl.entity_decls = local_entity_decls my_attrspec = [] if intent != "": my_attrspec.append("intent({0})".format(intent)) if pointer is not False: my_attrspec.append("pointer") if allocatable is not False: my_attrspec.append("allocatable") self._decl.attrspec = my_attrspec if dimension != "": my_attrspec.append("dimension({0})".format(dimension)) if kind is not "": self._decl.selector = ('', kind) BaseGen.__init__(self, parent, self._decl)
def test_nonblock_do_construct_tofortran_non_ascii(): ''' Check that the tofortran() method works when the non-block do-construct contains a character string with non-ascii characters. ''' from fparser.common.readfortran import FortranStringReader from fparser.common.sourceinfo import FortranFormat code = (u" DO 50\n" u" 50 WRITE(*,*) ' for e1=1\xb0'\n") reader = FortranStringReader(code) # Ensure reader in in 'fixed-format' mode reader.set_format(FortranFormat(False, True)) obj = Nonblock_Do_Construct(reader) out_str = str(obj) assert "for e1=1" in out_str
def __init__(self, parent, content): ''' :param parent: node in AST to which to add the Comment as a child :type parent: :py:class:`psyclone.f2pygen.BaseGen` :param str content: the content of the comment ''' reader = FortranStringReader("! content\n") reader.set_format(FortranFormat(True, True)) # free form, strict subline = reader.next() my_comment = Comment(parent.root, subline) my_comment.content = content BaseGen.__init__(self, parent, my_comment)
def __init__(self, parent, clause): ''' :param parent: Node to which to add this IfThen as a child :type parent: :py:class:`psyclone.f2pygen.BaseGen` :param str clause: the condition, xx, to evaluate in the if(xx)then ''' reader = FortranStringReader("if (dummy) then\nend if") reader.set_format(FortranFormat(True, True)) # free form, strict ifthenline = reader.next() endifline = reader.next() my_if = fparser1.block_statements.IfThen(parent.root, ifthenline) my_if.expr = clause my_endif = fparser1.block_statements.EndIfThen(my_if, endifline) my_if.content.append(my_endif) BaseGen.__init__(self, parent, my_if)
def test_do(name, label, control_comma, terminal_expression, end_name, end_label): # pylint: disable=redefined-outer-name, too-many-arguments, too-many-locals ''' Checks that the "do" loop parser understands the "for-next" variant of the syntax. This is defined in BS ISO/IEC 1539-1:2010 with R814-R822. TODO: Only the terminal expression is tested. This is a short-cut and relies on expression handling being applied identically across all expressions. This was true at the time of writing the test. ''' name_snippet = name + ': ' if name else None label_snippet = label + ' ' if label else None comma_snippet = ', ' if control_comma else None # TODO: Although the Fortran standard allows for "continue" to be used in # place of "end do" fparser does not support it. end_snippet = 'continue' if end_name == 'continue' \ else get_end_do(end_name) do_code = '''{name}do {label}{comma}variable = 1, {term}, 1 write (6, '(I0)') variable {endlabel} {end} '''.format(name=name_snippet or '', label=label_snippet or '', comma=comma_snippet or '', term=terminal_expression, endlabel=end_label or '', end=end_snippet) do_expected = ''' {name}DO {label}variable = 1, {term}, 1 WRITE (6, '(I0)') variable {endlabel} {endstmt} '''.format(name=name_snippet or '', label=label_snippet or '', term=terminal_expression, endlabel=end_label or ' ', endstmt=get_end_do(end_name)) do_reader = FortranStringReader(do_code) do_reader.set_format(FortranFormat(True, False)) do_parser = FortranParser(do_reader) if (name != end_name) or (label and (label != end_label)): with pytest.raises(AnalyzeError): do_parser.parse() else: do_parser.parse() loop = do_parser.block.content[0] assert str(loop).splitlines() == do_expected.splitlines()
def test_base_free_continuation(log): ''' Tests that FortranReaderBase.get_source_item() logs the correct messages when there are quote mismatches across a continuation in free format. ''' code = 'character(4) :: "boo & que' log.reset() unit_under_test = FortranStringReader(code) mode = FortranFormat(True, False) unit_under_test.set_format(mode) # Force sloppy free format unit_under_test.get_source_item() assert log.messages['debug'] == [] assert log.messages['info'] == [] assert log.messages['warning'] == [] assert log.messages['critical'] == [] expected = 'following character continuation: \'"\', expected None.' result = log.messages['error'][0].split('<==')[1].lstrip() assert result == expected
def __init__(self, parent, name="", args=None): ''' :param parent: node in AST to which to add CallGen as a child :type parent: :py:class:`psyclone.f2pygen.BaseGen` :param str name: the name of the routine to call :param list args: list of arguments to pass to the call ''' reader = FortranStringReader("call vanilla(vanilla_arg)") reader.set_format(FortranFormat(True, True)) # free form, strict myline = reader.next() from fparser.one.block_statements import Call self._call = Call(parent.root, myline) self._call.designator = name if args is None: args = [] self._call.items = args BaseGen.__init__(self, parent, self._call)
def __init__(self, parent): ''' :param parent: node in AST to which to add 'implicit none' as a child :type parent: :py:class:`psyclone.f2pygen.ModuleGen` or :py:class:`psyclone.f2pygen.SubroutineGen` :raises Exception: if `parent` is not a ModuleGen or SubroutineGen ''' if not isinstance(parent, ModuleGen) and not isinstance(parent, SubroutineGen): raise Exception( "The parent of ImplicitNoneGen must be a module or a " "subroutine, but found {0}".format(type(parent))) reader = FortranStringReader("IMPLICIT NONE\n") reader.set_format(FortranFormat(True, True)) # free form, strict subline = reader.next() from fparser.one.typedecl_statements import Implicit my_imp_none = Implicit(parent.root, subline) BaseGen.__init__(self, parent, my_imp_none)
def __init__(self, parent, variable_name, start, end, step=None): ''' :param parent: the node to which to add this do loop as a child :type parent: :py:class:`psyclone.f2pygen.BaseGen` :param str variable_name: the name of the loop variable :param str start: start value for Do loop :param str end: upper-limit of Do loop :param str step: increment to use in Do loop ''' reader = FortranStringReader("do i=1,n\nend do") reader.set_format(FortranFormat(True, True)) # free form, strict doline = reader.next() enddoline = reader.next() dogen = fparser1.block_statements.Do(parent.root, doline) dogen.loopcontrol = variable_name + "=" + start + "," + end if step is not None: dogen.loopcontrol = dogen.loopcontrol + "," + step enddo = fparser1.block_statements.EndDo(dogen, enddoline) dogen.content.append(enddo) BaseGen.__init__(self, parent, dogen)
def test_class_internal_error(monkeypatch, capsys): ''' Check that expected errors are raised when invalid CLASS statements are encountered ''' from fparser.one.block_statements import ClassIs from fparser.common.readfortran import FortranStringReader reader = FortranStringReader('CLASS IS (yes)') reader.set_format(fparser.common.sourceinfo.FortranFormat(True, False)) item = next(reader) stmt = ClassIs(item, item) # Monkeypatch our valid Case object so that get_line() now # returns something invalid. We have to do it this way # because if we started with this text then we wouldn't get # past the match() method monkeypatch.setattr(stmt.item, "get_line", lambda: "class invalid") # Monkeypatch the Case object so that a call to self.warning # (which normally results in a call to the logger) gets replaced # with a call to our print_wrapper() function monkeypatch.setattr(stmt, "warning", print_wrapper) stmt.process_item() output, _ = capsys.readouterr() assert "Internal error when parsing CLASS statement" in output
def __init__(self, parent, name="", only=False, funcnames=None): ''' :param parent: node in AST to which to add UseGen as a child :type parent: :py:class:`psyclone.f2pygen.BaseGen` :param str name: name of the module to USE :param bool only: whether this USE has an ONLY clause :param list funcnames: list of names to follow ONLY clause ''' reader = FortranStringReader("use kern,only : func1_kern=>func1") reader.set_format(FortranFormat(True, True)) # free form, strict myline = reader.next() root = parent.root from fparser.one.block_statements import Use use = Use(root, myline) use.name = name use.isonly = only if funcnames is None: funcnames = [] use.isonly = False local_funcnames = funcnames[:] use.items = local_funcnames BaseGen.__init__(self, parent, use)
def __init__(self, parent, content): ''' :param parent: node to which to add this DEALLOCATE as a child :type parent: :py:class:`psyclone.f2pygen.BaseGen` :param content: string or list of variables to deallocate :type content: list of strings or a single string :raises RuntimeError: if `content` is not of correct type ''' reader = FortranStringReader("deallocate(dummy)") reader.set_format(FortranFormat(True, False)) # free form, strict myline = reader.next() self._decl = fparser1.statements.Deallocate(parent.root, myline) if isinstance(content, str): self._decl.items = [content] elif isinstance(content, list): self._decl.items = content else: raise RuntimeError( "DeallocateGen expected the content argument to be a str" " or a list, but found {0}".format(type(content))) BaseGen.__init__(self, parent, self._decl)
def test_multi_stmt_line3(): '''Check that named do loops separated by ; on a single line are correctly split into multiple lines by FortranReaderBase ''' code = "name:do i=1,10;name2 : do j=1,10" reader = FortranStringReader(code) mode = FortranFormat(True, False) reader.set_format(mode) line1 = reader.next() assert isinstance(line1, Line) assert line1.line == "do i=1,10" assert line1.span == (1, 1) assert line1.label is None assert line1.name == "name" assert line1.reader is reader line2 = reader.next() assert line2.line == "do j=1,10" assert line2.span is line1.span assert line2.label is None assert line2.name == "name2" assert line2.reader is reader
def test_multi_stmt_line2(): '''Check that format statements separated by ; on a single line are correctly split into multiple lines by FortranReaderBase ''' code = "10 format(a); 20 format(b)" reader = FortranStringReader(code) mode = FortranFormat(True, False) reader.set_format(mode) line1 = reader.next() assert isinstance(line1, Line) assert line1.line == "format(a)" assert line1.span == (1, 1) assert line1.label == 10 assert line1.name is None assert line1.reader is reader line2 = reader.next() assert line2.line == "format(b)" assert line2.span is line1.span assert line2.label == 20 assert line2.name is None assert line2.reader is reader
def __init__(self, parent, lhs="", rhs="", pointer=False): ''' :param parent: the node to which to add this assignment as a child :type parent: :py:class:`psyclone.f2pygen.BaseGen` :param str lhs: the LHS of the assignment expression :param str rhs: the RHS of the assignment expression :param bool pointer: whether or not this is a pointer assignment ''' if pointer: reader = FortranStringReader("lhs=>rhs") else: reader = FortranStringReader("lhs=rhs") reader.set_format(FortranFormat(True, True)) # free form, strict myline = reader.next() if pointer: self._assign = fparser1.statements.PointerAssignment(parent.root, myline) else: self._assign = fparser1.statements.Assignment(parent.root, myline) self._assign.expr = rhs self._assign.variable = lhs BaseGen.__init__(self, parent, self._assign)
def test_do_while(name, label, control_comma, terminal_expression, end_name, end_label): # pylint: disable=redefined-outer-name, too-many-arguments ''' Checks that the "do" loop parser understands the "do-while" variant of the syntax. This is defined in BS ISO/IEC 1539-1:2010 with R814-R822. ''' name_snippet = name + ': ' if name else None label_snippet = label + ' ' if label else None comma_snippet = ', ' if control_comma else None code = '''{name}do {label}{comma}while ({term}) write (6, '(I0)') variable {endlabel} {endstmt} '''.format(name=name_snippet or '', label=label_snippet or '', comma=comma_snippet or '', term=terminal_expression, endlabel=end_label or '', endstmt=get_end_do(end_name)) expected = ''' {name}DO {label}while ({term}) WRITE (6, '(I0)') variable {endlabel} {endstmt} '''.format(name=name_snippet or '', label=label_snippet or '', term=terminal_expression, endlabel=end_label or ' ', endstmt=get_end_do(end_name)) print(code) reader = FortranStringReader(code) reader.set_format(FortranFormat(True, False)) parser = FortranParser(reader) if (name != end_name) or (label and (label != end_label)): with pytest.raises(AnalyzeError): parser.parse() else: parser.parse() loop = parser.block.content[0] assert str(loop).splitlines() == expected.splitlines()
def __init__(self, parent, language, position, directive_type, content): ''' :param parent: node in AST to which to add directive as a child :type parent: :py:class:`psyclone.f2pygen.BaseGen` :param str language: the type of directive (e.g. OMP or ACC) :param str position: "end" if this is the end of a directive block :param str directive_type: the directive itself (e.g. "PARALLEL DO") :param str content: any additional arguments to add to the directive (e.g. "PRIVATE(ji)") :raises RuntimeError: if an unrecognised directive language is specified ''' self._supported_languages = ["omp"] self._language = language self._directive_type = directive_type reader = FortranStringReader("! content\n") reader.set_format(FortranFormat(True, True)) # free form, strict subline = reader.next() if language == "omp": my_comment = OMPDirective(parent.root, subline, position, directive_type) my_comment.content = "$omp" if position == "end": my_comment.content += " end" my_comment.content += " " + directive_type if content != "": my_comment.content += " " + content else: raise RuntimeError( "Error, unsupported directive language. Expecting one of " "{0} but found '{1}'".format(str(self._supported_languages), language)) BaseGen.__init__(self, parent, my_comment)
def __init__(self, parent, datatype="", entity_decls=None, intent="", pointer=False, attrspec=None): ''' :param parent: the node to which to add this type delcn as a child :type parent: :py:class:`psyclone.f2pygen.BaseGen` :param str datatype: the derived type :param list entity_decls: List of variable names to declare :param str intent: the intent attribute for the declaration :param bool pointer: whether or not this is a pointer declaration :param attrspec: list of other attributes to add to declaration :raises RuntimeError: if no variable names are specified ''' if entity_decls is None: raise RuntimeError( "Cannot create a declaration of a derived-type variable " "without specifying the name(s) of the variable(s)") # make a copy of entity_decls as we may modify it local_entity_decls = entity_decls[:] if attrspec is None: attrspec = [] my_attrspec = [spec for spec in attrspec] if intent != "": my_attrspec.append("intent({0})".format(intent)) if pointer is not False: my_attrspec.append("pointer") self._names = local_entity_decls reader = FortranStringReader("type(vanillatype) :: vanilla") reader.set_format(FortranFormat(True, False)) # free form, strict myline = reader.next() self._typedecl = fparser1.typedecl_statements.Type(parent.root, myline) self._typedecl.selector = ('', datatype) self._typedecl.attrspec = my_attrspec self._typedecl.entity_decls = local_entity_decls BaseGen.__init__(self, parent, self._typedecl)
def test_base_fixed_nonlabel(log): ''' Tests that FortranReaderBase.get_source_item() logs the correct messages when there is an unexpected character in the initial 6 columns. ''' # Checks that a bad character in the first column causes an event to be # logged. code = 'w integer :: i' log.reset() unit_under_test = FortranStringReader(code) mode = FortranFormat(False, True) unit_under_test.set_format(mode) # Force fixed format unit_under_test.get_source_item() assert log.messages['debug'] == [] assert log.messages['info'] == [] assert log.messages['error'] == [] assert log.messages['critical'] == [] result = log.messages['warning'][0].split('<==')[1].lstrip() expected = "non-space/digit char 'w' found in column 1 of fixed " \ + "Fortran code, interpreting line as comment line" assert result == expected # Checks a bad character in columns 2-6 for i in range(1, 5): code = ' ' * i + 'w' + ' ' * (5 - i) + 'integer :: i' log.reset() unit_under_test = FortranStringReader(code) mode = FortranFormat(False, True) unit_under_test.set_format(mode) # Force strict fixed format unit_under_test.get_source_item() assert log.messages['debug'] == [] assert log.messages['info'] == [] assert log.messages['error'] == [] assert log.messages['critical'] == [] result = log.messages['warning'][0].split('<==')[1].lstrip() expected = "non-space/digit char 'w' found in column {col} " \ + "of fixed Fortran code" assert result == expected.format(col=i + 1) # Checks for a bad character, not in the first column, with "sloppy" mode # engaged. code = ' w integer :: i' log.reset() unit_under_test = FortranStringReader(code) mode = FortranFormat(False, False) unit_under_test.set_format(mode) # Force sloppy fixed format unit_under_test.get_source_item() assert log.messages['debug'] == [] assert log.messages['info'] == [] assert log.messages['error'] == [] assert log.messages['critical'] == [] expected = "non-space/digit char 'w' found in column 2 " \ + "of fixed Fortran code, switching to free format mode" result = log.messages['warning'][0].split('<==')[1].lstrip() assert result == expected
def update(self): ''' Update the underlying fparser2 parse tree to implement the profiling region represented by this Node. This involves adding the necessary module use statement as well as the calls to the profiling API. TODO #435 - remove this whole method once the NEMO API uses the Fortran backend of the PSyIR. :raises NotImplementedError: if the routine which is to have \ profiling added to it does not already have a \ Specification Part (i.e. some declarations). :raises NotImplementedError: if there would be a name clash with \ existing variable/module names in the code to \ be transformed. :raises InternalError: if we fail to find the node in the parse tree \ corresponding to the end of the profiling region. ''' from fparser.common.sourceinfo import FortranFormat from fparser.common.readfortran import FortranStringReader from fparser.two.utils import walk_ast from fparser.two import Fortran2003 from psyclone.psyGen import object_index, Schedule, InternalError # Ensure child nodes are up-to-date super(ProfileNode, self).update() # Get the parse tree of the routine containing this region ptree = self.root.invoke._ast # Rather than repeatedly walk the tree, we do it once for all of # the node types we will be interested in... node_list = walk_ast([ptree], [ Fortran2003.Main_Program, Fortran2003.Subroutine_Stmt, Fortran2003.Function_Stmt, Fortran2003.Specification_Part, Fortran2003.Use_Stmt, Fortran2003.Name ]) for node in node_list: if isinstance( node, (Fortran2003.Main_Program, Fortran2003.Subroutine_Stmt, Fortran2003.Function_Stmt)): names = walk_ast([node], [Fortran2003.Name]) routine_name = str(names[0]).lower() break for node in node_list: if isinstance(node, Fortran2003.Specification_Part): spec_part = node break else: # This limitation will be removed when we use the Fortran # backend of the PSyIR (#435) raise NotImplementedError( "Addition of profiling regions to routines without any " "existing declarations is not supported and '{0}' has no " "Specification-Part".format(routine_name)) # Get the existing use statements found = False for node in node_list[:]: if isinstance(node, Fortran2003.Use_Stmt) and \ self.fortran_module == str(node.items[2]).lower(): # Check that the use statement matches the one we would # insert (i.e. the code doesn't already contain a module # with the same name as that used by the profiling API) if str(node).lower() != self.use_stmt.lower(): raise NotImplementedError( "Cannot add profiling to '{0}' because it already " "'uses' a module named '{1}'".format( routine_name, self.fortran_module)) found = True # To make our check on name clashes below easier, remove # the Name nodes associated with this use from our # list of nodes. names = walk_ast([node], [Fortran2003.Name]) for name in names: node_list.remove(name) if not found: # We don't already have a use for the profiling module so # add one. reader = FortranStringReader( "use profile_mod, only: ProfileData, ProfileStart, ProfileEnd") # Tell the reader that the source is free format reader.set_format(FortranFormat(True, False)) use = Fortran2003.Use_Stmt(reader) spec_part.content.insert(0, use) # Check that we won't have any name-clashes when we insert the # symbols required for profiling. This check uses the list of symbols # that we created before adding the `use profile_mod...` statement. if not self.root.profiling_name_clashes_checked: for node in node_list: if isinstance(node, Fortran2003.Name): text = str(node).lower() # Check for the symbols we import from the profiling module for symbol in self.profiling_symbols: if text == symbol.lower(): raise NotImplementedError( "Cannot add profiling to '{0}' because it " "already contains a symbol that clashes with " "one of those ('{1}') that must be imported " "from the PSyclone profiling module.".format( routine_name, symbol)) # Check for the name of the profiling module itself if text == self.fortran_module: raise NotImplementedError( "Cannot add profiling to '{0}' because it already " "contains a symbol that clashes with the name of " "the PSyclone profiling module ('profile_mod')". format(routine_name)) # Check for the names of profiling variables if text.startswith(self.profiling_var): raise NotImplementedError( "Cannot add profiling to '{0}' because it already" " contains symbols that potentially clash with " "the variables we will insert for each profiling " "region ('{1}*').".format(routine_name, self.profiling_var)) # Flag that we have now checked for name clashes so that if there's # more than one profiling node we don't fall over on the symbols # we've previous inserted. self.root.profiling_name_clashes_checked = True # Create a name for this region by finding where this profiling # node is in the list of profiling nodes in this Invoke. sched = self.root pnodes = sched.walk(ProfileNode) region_idx = pnodes.index(self) region_name = "r{0}".format(region_idx) var_name = "psy_profile{0}".format(region_idx) # Create a variable for this profiling region reader = FortranStringReader( "type(ProfileData), save :: {0}".format(var_name)) # Tell the reader that the source is free format reader.set_format(FortranFormat(True, False)) decln = Fortran2003.Type_Declaration_Stmt(reader) spec_part.content.append(decln) # Find the parent in the parse tree - first get a pointer to the # AST for the content of this region. if isinstance(self.children[0], Schedule) and \ not self.children[0].ast: # TODO #435 Schedule should really have a valid ast pointer. content_ast = self.children[0][0].ast else: content_ast = self.children[0].ast # Now store the parent of this region fp_parent = content_ast._parent # Find the location of the AST of our first child node in the # list of child nodes of our parent in the fparser parse tree. ast_start_index = object_index(fp_parent.content, content_ast) # Finding the location of the end is harder as it might be the # end of a clause within an If or Select block. We therefore # work back up the fparser2 parse tree until we find a node that is # a direct child of the parent node. ast_end_index = None if self.children[-1].ast_end: ast_end = self.children[-1].ast_end else: ast_end = self.children[-1].ast # Keep a copy of the pointer into the parse tree in case of errors ast_end_copy = ast_end while ast_end_index is None: try: ast_end_index = object_index(fp_parent.content, ast_end) except ValueError: # ast_end is not a child of fp_parent so go up to its parent # and try again if hasattr(ast_end, "_parent") and ast_end._parent: ast_end = ast_end._parent else: raise InternalError( "Failed to find the location of '{0}' in the fparser2 " "Parse Tree:\n{1}\n".format(str(ast_end_copy), str(fp_parent.content))) # Add the profiling-end call reader = FortranStringReader("CALL ProfileEnd({0})".format(var_name)) # Tell the reader that the source is free format reader.set_format(FortranFormat(True, False)) pecall = Fortran2003.Call_Stmt(reader) fp_parent.content.insert(ast_end_index + 1, pecall) # Add the profiling-start call reader = FortranStringReader( "CALL ProfileStart('{0}', '{1}', {2})".format( routine_name, region_name, var_name)) reader.set_format(FortranFormat(True, False)) pscall = Fortran2003.Call_Stmt(reader) fp_parent.content.insert(ast_start_index, pscall)