def test_array_notation_rank(): ''' Check that the _array_notation_rank() utility handles various examples of array notation. TODO #754 fix test so that 'disable_declaration_check' fixture is not required. ''' fake_parent = Schedule() processor = Fparser2Reader() reader = FortranStringReader(" z1_st(:, 2, :) = ptsu(:, :, 3)") fparser2spec = Fortran2003.Assignment_Stmt(reader) processor.process_nodes(fake_parent, [fparser2spec]) assert processor._array_notation_rank(fake_parent[0].lhs) == 2 reader = FortranStringReader(" z1_st(:, :, 2, :) = ptsu(:, :, :, 3)") fparser2spec = Fortran2003.Assignment_Stmt(reader) processor.process_nodes(fake_parent, [fparser2spec]) assert processor._array_notation_rank(fake_parent[1].lhs) == 3 # We don't support bounds on slices reader = FortranStringReader(" z1_st(:, 1:n, 2, :) = ptsu(:, :, :, 3)") fparser2spec = Fortran2003.Assignment_Stmt(reader) processor.process_nodes(fake_parent, [fparser2spec]) with pytest.raises(NotImplementedError) as err: processor._array_notation_rank(fake_parent[2].lhs) assert ("Only array notation of the form my_array(:, :, ...) is " "supported." in str(err.value))
def test_brackets_array_constructor(left, right): ''' Test parsing of array constructor specified with both valid types of bracket. ''' fcode = "array = {0} 1, 2, 3{1}".format(left, right) reader = FortranStringReader(fcode) ast = Fortran2003.Assignment_Stmt(reader) assert isinstance(ast, Fortran2003.Assignment_Stmt) assert isinstance(ast.children[2], Fortran2003.Array_Constructor) assert isinstance(ast.children[2].children[1], Fortran2003.Ac_Value_List) assert "array = {0}1, 2, 3{1}".format(left, right) in str(ast) # Invalid content between valid brackets fcode = "array = {0}call hello(){1}".format(left, right) reader = FortranStringReader(fcode) ast = Fortran2003.Assignment_Stmt(reader) assert ast is None
def test_handling_literal_precision_2(value, dprecision, intrinsic): '''Check that the fparser2 frontend can handle literals with a specified precision value. TODO #754 fix test so that 'disable_declaration_check' fixture is not required. ''' if intrinsic == ScalarType.Intrinsic.CHARACTER: code = "x={0}_{1}".format(dprecision, value) else: code = "x={0}_{1}".format(value, dprecision) reader = FortranStringReader(code) astmt = Fortran2003.Assignment_Stmt(reader) fake_parent = Schedule() processor = Fparser2Reader() processor.process_nodes(fake_parent, [astmt]) assert not fake_parent.walk(CodeBlock) literal = fake_parent.children[0].children[1] assert isinstance(literal, Literal) assert literal.datatype.intrinsic == intrinsic if intrinsic == ScalarType.Intrinsic.BOOLEAN: assert ".{0}.".format(literal.value) == value.lower() else: assert literal.value == value assert isinstance(literal.datatype.precision, int) assert literal.datatype.precision == dprecision
def test_1d_array_not_implicit_loop(): ''' Check that we do not identify the use of array-notation in 1D loops as being implicit loops (since we don't know what the loop is over). ''' code = "z1d(:) = 1.0d0" reader = FortranStringReader(code) assign = Fortran2003.Assignment_Stmt(reader) assert not nemo.NemoImplicitLoop.match(assign)
def test_get_int_array_constructor_err(monkeypatch): ''' Check that we raise the appropriate error if we fail to parse the array constructor expression. ''' from fparser.two import Fortran2003 # First create a valid KernelType object ast = parse(DIFF_BASIS, ignore_comments=False) ktype = KernelType(ast) # Create a valid fparser2 result assign = Fortran2003.Assignment_Stmt("gh_evaluator_targets(2) = [1, 2]") # Break the array constructor expression (tuples are immutable so make a # new one) assign.items[2].items[1].items = tuple(["hello", "goodbye"]) # Use monkeypatch to ensure that that's the result that is returned # when we attempt to use fparser2 from within the routine under test def my_init(self, _): ''' dummy class ''' self.items = assign.items monkeypatch.setattr(Fortran2003.Assignment_Stmt, "__init__", my_init) with pytest.raises(InternalError) as err: _ = ktype.get_integer_array("gh_evaluator_targets") assert ("Failed to parse array constructor: '[hello, goodbye]'" in str(err.value))
def test_handling_literal_precision_1(value, dprecision, intrinsic): '''Check that the fparser2 frontend can handle literals with a specified precision kind symbol. ''' if intrinsic == ScalarType.Intrinsic.CHARACTER: code = "x={0}_{1}".format(dprecision, value) else: code = "x={0}_{1}".format(value, dprecision) reader = FortranStringReader(code) astmt = Fortran2003.Assignment_Stmt(reader) fake_parent = Schedule() # Ensure the symbol table has an entry for "x" fake_parent.symbol_table.add( DataSymbol("x", ScalarType(ScalarType.Intrinsic.INTEGER, 4))) processor = Fparser2Reader() processor.process_nodes(fake_parent, [astmt]) assert not fake_parent.walk(CodeBlock) literal = fake_parent.children[0].children[1] assert isinstance(literal, Literal) assert literal.datatype.intrinsic == intrinsic if intrinsic == ScalarType.Intrinsic.BOOLEAN: assert ".{0}.".format(literal.value) == value.lower() else: assert literal.value == value assert isinstance(literal.datatype.precision, DataSymbol) assert literal.datatype.precision.name == dprecision assert isinstance(literal.datatype.precision.datatype, ScalarType) assert (literal.datatype.precision.datatype.intrinsic == ScalarType.Intrinsic.INTEGER)
def test_call_not_implicit_loop(): ''' Check we do not incorrectly identify an implicit loop when array notation is used in the arguments to a function call. ''' code = "z3d(1,:,:) = ptr_sjk( pvtr(:,:,:), btmsk(:,:,jn)*btm30(:,:) )" reader = FortranStringReader(code) assign = Fortran2003.Assignment_Stmt(reader) assert not nemo.NemoImplicitLoop.match(assign)
def get_integer_variable(self, name): ''' Parse the kernel meta-data and find the value of the integer variable with the supplied name. Return None if no matching variable is found. :param str name: the name of the integer variable to find. :returns: value of the specified integer variable or None. :rtype: str :raises ParseError: if the RHS of the assignment is not a Name. ''' # Ensure the Fortran2003 parser is initialised _ = ParserFactory().create() for statement, _ in fpapi.walk(self._ktype, -1): if isinstance(statement, fparser1.typedecl_statements.Integer): # fparser only goes down to the statement level. We use # fparser2 to parse the statement itself (eventually we'll # use fparser2 to parse the whole thing). assign = Fortran2003.Assignment_Stmt(statement.entity_decls[0]) if str(assign.items[0]) == name: if not isinstance(assign.items[2], Fortran2003.Name): raise ParseError( "get_integer_variable: RHS of assignment is not " "a variable name: '{0}'".format(str(assign))) return str(assign.items[2]) return None
def test_get_int_array_err1(monkeypatch): ''' Tests that we raise the correct error if there is something wrong with the assignment statement obtained from fparser2. ''' from fparser.two import Fortran2003 # This is difficult as we have to break the result returned by fparser2. # We therefore create a valid KernelType object ast = fpapi.parse(MDATA, ignore_comments=False) ktype = KernelType(ast) # Next we create a valid fparser2 result my_assign = Fortran2003.Assignment_Stmt("my_array(2) = [1, 2]") # Break its `items` property by replacing the Name object with a string # (tuples are immutable so make a new one) broken_items = tuple(["invalid"] + list(my_assign.items[1:])) # Use monkeypatch to ensure that that the Assignment_Stmt that # is returned when we attempt to use fparser2 from within the # routine under test now has the broken tuple of items. def my_init(self, _): ''' dummy class ''' self.items = broken_items monkeypatch.setattr(Fortran2003.Assignment_Stmt, "__init__", my_init) with pytest.raises(InternalError) as err: _ = ktype.get_integer_array("gh_evaluator_targets") assert "Unsupported assignment statement: 'invalid = [1, 2]'" in str(err)
def test_get_int_array_section_subscript_err(monkeypatch): ''' Check that we raise the appropriate error if the parse tree for the LHS of the array declaration is broken. ''' from fparser.two import Fortran2003 # First create a valid KernelType object ast = parse(DIFF_BASIS, ignore_comments=False) ktype = KernelType(ast) # Create a valid fparser2 result assign = Fortran2003.Assignment_Stmt("gh_evaluator_targets(2) = [1, 2]") # Break the array constructor expression by replacing the # Section_Subscript_List with a str assign.children[0].items = (assign.children[0].items[0], "hello") # Use monkeypatch to ensure that that's the result that is returned # when we attempt to use fparser2 from within the routine under test def my_init(self, _): ''' dummy constructor ''' self.items = assign.items monkeypatch.setattr(Fortran2003.Assignment_Stmt, "__init__", my_init) with pytest.raises(InternalError) as err: _ = ktype.get_integer_array("gh_evaluator_targets") assert ("expected array declaration to have a Section_Subscript_List but " "found" in str(err.value))
def test_invalid_if_clause(): ''' Check that we raise the expected error if the NemoIfClause is passed something that isn't an if-clause. ''' from psyclone.nemo import NemoIfClause reader = FortranStringReader("umask(:, :, :, :) = 0") assign = Fortran2003.Assignment_Stmt(reader) with pytest.raises(InternalError) as err: _ = NemoIfClause([assign]) assert "Unrecognised member of if block: " in str(err)
def test_get_literal_precision_missing_table(): ''' Check that get_literal_precision raises the expected error if it fails to find a symbol table. ''' code = "x=0.0_rdef" reader = FortranStringReader(code) astmt = Fortran2003.Assignment_Stmt(reader) # Pass get_literal_precision just a Literal() (which does not have an # associated symbol table). with pytest.raises(InternalError) as err: get_literal_precision(astmt.children[2], Literal("1", INTEGER_TYPE)) assert ("Failed to find a symbol table to which to add the kind" in str(err.value))
def test_get_literal_precision_type(monkeypatch): '''Make sure the get_literal_precision function in fparser2.py behaves as expected when an unsupported datatype is found ''' monkeypatch.setattr(fparser2, "CONSTANT_TYPE_MAP", {}) code = "x=0.0" reader = FortranStringReader(code) astmt = Fortran2003.Assignment_Stmt(reader) fparser2_literal = astmt.children[2] with pytest.raises(NotImplementedError) as excinfo: _ = get_literal_precision(fparser2_literal, Node()) assert ("Could not process Real_Literal_Constant. Only 'real', 'integer', " "'logical' and 'character' intrinsic types are supported." in str(excinfo.value))
def get_integer_array(self, name): ''' Parse the kernel meta-data and find the values of the integer array variable with the supplied name. Returns an empty list if no matching variable is found. :param str name: the name of the integer array to find. :returns: list of values. :rtype: list of str. :raises InternalError: if we fail to parse the LHS of the array \ declaration or the array constructor. :raises ParseError: if the RHS of the declaration is not an array \ constructor. ''' # Ensure the classes are setup for the Fortran2003 parser _ = ParserFactory().create() for statement, _ in fpapi.walk(self._ktype, -1): if not isinstance(statement, fparser1.typedecl_statements.Integer): # This isn't an integer declaration so skip it continue # fparser only goes down to the statement level. We use fparser2 to # parse the statement itself. assign = Fortran2003.Assignment_Stmt(statement.entity_decls[0]) names = walk_ast(assign.items, [Fortran2003.Name]) if not names: raise InternalError( "Unsupported assignment statement: '{0}'".format( str(assign))) if str(names[0]) == name: # This is the variable declaration we're looking for if not isinstance(assign.items[2], Fortran2003.Array_Constructor): raise ParseError( "get_integer_array: RHS of assignment is not " "an array constructor: '{0}'".format(str(assign))) # fparser2 AST for Array_Constructor is: # Array_Constructor('[', Ac_Value_List(',', (Name('w0'), # Name('w1'))), ']') # Construct a list of the names in the array constructor names = walk_ast(assign.items[2].items, [Fortran2003.Name]) if not names: raise InternalError("Failed to parse array constructor: " "'{0}'".format(str(assign.items[2]))) return [str(name) for name in names] return []
def test_literal_constant_value_format(value, result): '''Test that the Fortran real literal value format which does not have a digit before the decimal point is modified to include a "0" e.g. ".3" -> "0.3", "-.3e4" -> "-0.3e4" TODO #754 fix test so that 'disable_declaration_check' fixture is not required. ''' reader = FortranStringReader("a = {0}".format(value)) astmt = Fortran2003.Assignment_Stmt(reader) fake_parent = Schedule() processor = Fparser2Reader() processor.process_nodes(fake_parent, [astmt]) literal = fake_parent.children[0].children[1] assert isinstance(literal, Literal) assert literal.value == result assert literal.datatype.intrinsic == ScalarType.Intrinsic.REAL
def test_handling_invalid_logic_literal(): ''' Test that a logic fparser2 literal with an invalid value produces an error. TODO #754 fix test so that 'disable_declaration_check' fixture is not required. ''' from psyclone.errors import GenerationError reader = FortranStringReader("x = .true.") astmt = Fortran2003.Assignment_Stmt(reader) astmt.items[2].items = ('invalid', None) fake_parent = Schedule() processor = Fparser2Reader() with pytest.raises(GenerationError) as error: processor.process_nodes(fake_parent, [astmt]) assert "Expected to find '.true.' or '.false.' as fparser2 logical " \ "literal, but found 'invalid' instead." in str(error.value)
def test_get_literal_precision(): '''Make sure the get_literal_precision function in fparser2.py behaves as expected when the arguments are invalid. ''' with pytest.raises(InternalError) as excinfo: _ = get_literal_precision(None, None) assert ("Unsupported literal type 'NoneType' found in " "get_literal_precision." in str(excinfo.value)) code = "x=0.0" reader = FortranStringReader(code) astmt = Fortran2003.Assignment_Stmt(reader) fparser2_literal = astmt.children[2] with pytest.raises(InternalError) as excinfo: _ = get_literal_precision(fparser2_literal, None) assert ("Expecting argument psyir_literal_parent to be a PSyIR Node but " "found 'NoneType' in get_literal_precision." in str(excinfo.value))
def test_handling_literal_precision_3(value, dprecision): '''Check that the fparser2 frontend can handle literals with a precision value specified by the exponent. The literal value TODO #754 fix test so that 'disable_declaration_check' fixture is not required. should always use a lower case "e" for the exponent. ''' code = "x={0}".format(value) reader = FortranStringReader(code) astmt = Fortran2003.Assignment_Stmt(reader) fake_parent = Schedule() processor = Fparser2Reader() processor.process_nodes(fake_parent, [astmt]) assert not fake_parent.walk(CodeBlock) literal = fake_parent.children[0].children[1] assert isinstance(literal, Literal) assert literal.value.lower() == "0.0e0" assert literal.datatype.intrinsic == ScalarType.Intrinsic.REAL assert literal.datatype.precision == dprecision
def test_handling_literal(code, dtype): ''' Check that the fparser2 frontend can handle literals of all supported datatypes. Note that signed literals are represented in the PSyIR as a Unary operation on an unsigned literal. TODO #754 fix test so that 'disable_declaration_check' fixture is not required. ''' reader = FortranStringReader("x=" + code) astmt = Fortran2003.Assignment_Stmt(reader) fake_parent = Schedule() processor = Fparser2Reader() processor.process_nodes(fake_parent, [astmt]) assert not fake_parent.walk(CodeBlock) literal = fake_parent.children[0].children[1] assert isinstance(literal, Literal) assert literal.datatype.intrinsic == dtype if dtype != ScalarType.Intrinsic.BOOLEAN: assert literal.value == code else: assert literal.value == code.lower()[1:-1] # Remove wrapping dots
def get_integer_array(self, name): ''' Parse the kernel meta-data and find the values of the integer array variable with the supplied name. Returns an empty list if no matching variable is found. The search is not case sensitive. :param str name: the name of the integer array to find. :return: list of values (lower-case). :rtype: list of str. :raises InternalError: if we fail to parse the LHS of the array \ declaration or the array constructor. :raises ParseError: if the array is not of rank 1. :raises ParseError: if the array extent is not specified using an \ integer literal. :raises ParseError: if the RHS of the declaration is not an array \ constructor. :raises InternalError: if the parse tree for the array constructor \ does not have the expected structure. :raises ParseError: if the number of items in the array constructor \ does not match the extent of the array. ''' # Ensure the classes are setup for the Fortran2008 parser _ = ParserFactory().create(std="f2008") # Fortran is not case sensitive so nor is our matching lower_name = name.lower() for statement, _ in fpapi.walk(self._ktype): if not isinstance(statement, fparser1.typedecl_statements.Integer): # This isn't an integer declaration so skip it continue # fparser only goes down to the statement level. We use fparser2 to # parse the statement itself. assign = Fortran2003.Assignment_Stmt(statement.entity_decls[0]) names = walk(assign.children, Fortran2003.Name) if not names: raise InternalError("Unsupported assignment statement: '{0}'". format(str(assign))) if str(names[0]).lower() != lower_name: # This is not the variable declaration we're looking for continue if not isinstance(assign.children[0], Fortran2003.Part_Ref): # Not an array declaration return [] if not isinstance(assign.children[0].children[1], Fortran2003.Section_Subscript_List): raise InternalError( "get_integer_array: expected array declaration to have a " "Section_Subscript_List but found '{0}' for: '{1}'".format( type(assign.children[0].children[1]).__name__, str(assign))) dim_stmt = assign.children[0].children[1] if len(dim_stmt.children) != 1: raise ParseError( "get_integer_array: array must be 1D but found an array " "with {0} dimensions for name '{1}'".format( len(dim_stmt.children), name)) if not isinstance(dim_stmt.children[0], Fortran2003.Int_Literal_Constant): raise ParseError( "get_integer_array: array extent must be specified using " "an integer literal but found '{0}' for array '{1}'". format(str(dim_stmt.children[0]), name)) # Get the declared size of the array array_extent = int(str(dim_stmt.children[0])) if not isinstance(assign.children[2], Fortran2003.Array_Constructor): raise ParseError( "get_integer_array: RHS of assignment is not " "an array constructor: '{0}'".format(str(assign))) # fparser2 AST for Array_Constructor is: # Array_Constructor('[', Ac_Value_List(',', (Name('w0'), # Name('w1'))), ']') # Construct a list of the names in the array constructor names = walk(assign.children[2].children, Fortran2003.Name) if not names: raise InternalError("Failed to parse array constructor: " "'{0}'".format(str(assign.items[2]))) if len(names) != array_extent: # Ideally fparser would catch this but it isn't yet mature # enough. raise ParseError( "get_integer_array: declared length of array '{0}' is {1} " "but constructor only contains {2} names: '{3}'".format( name, array_extent, len(names), str(assign))) return [str(name).lower() for name in names] # No matching declaration for the provided name was found return []
def test_array_constructor_no_match(example): ''' Check that incorrect constructors are not matched. ''' fcode = "array = " + example reader = FortranStringReader(fcode) ast = Fortran2003.Assignment_Stmt(reader) assert ast is None