Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ if pyprojectwheelbuild_enabled
'src/example_fgen_basic/fpyfgen/derived_type_manager_helpers.f90',
'src/example_fgen_basic/get_wavelength.f90',
'src/example_fgen_basic/kind_parameters.f90',
'src/example_fgen_basic/result/result.f90',
'src/example_fgen_basic/result/result_int.f90',
)

# All Python files (wrappers and otherwise)
Expand Down
2 changes: 1 addition & 1 deletion src/example_fgen_basic/error_v/creation.f90
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module m_error_v_creation

use m_error_v, only: ErrorV, NO_ERROR_CODE

implicit none (type, external)
implicit none(type, external)
private

public :: create_error, create_errors
Expand Down
2 changes: 1 addition & 1 deletion src/example_fgen_basic/error_v/creation_wrapper.f90
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module m_error_v_creation_w
error_v_manager_set_instance_index_to => set_instance_index_to, &
error_v_manager_ensure_instance_array_size_is_at_least => ensure_instance_array_size_is_at_least

implicit none (type, external)
implicit none(type, external)
private

public :: create_error, create_errors
Expand Down
5 changes: 3 additions & 2 deletions src/example_fgen_basic/error_v/error_v.f90
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
!>
!> Fortran doesn't have a null value.
!> As a result, we introduce this derived type
!> with the convention that a code of 0 indicates no error.
!> with the convention that a code of `NO_ERROR_CODE` (0)
!> indicates no error (i.e. is our equivalent of a null value).
module m_error_v

implicit none (type, external)
implicit none(type, external)
private

integer, parameter, public :: NO_ERROR_CODE = 0
Expand Down
37 changes: 30 additions & 7 deletions src/example_fgen_basic/error_v/error_v_manager.f90
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module m_error_v_manager

use m_error_v, only: ErrorV

implicit none (type, external)
implicit none(type, external)
private

type(ErrorV), dimension(:), allocatable :: instance_array
Expand Down Expand Up @@ -68,13 +68,16 @@ subroutine get_available_instance_index(available_instance_index)

instance_available(i) = .false.
available_instance_index = i
! TODO: switch to returning a Result type
! res = ResultInt(data=i)
return

end if

end do

! TODO: switch to returning a Result type with an error set
! res = ResultInt(ErrorV(code=1, message="No available instances"))
error stop 1

end subroutine get_available_instance_index
Expand Down Expand Up @@ -114,17 +117,37 @@ subroutine check_index_claimed(instance_index)
!! Instance index to check

if (instance_available(instance_index)) then
! TODO: switch to errors here - will require some thinking
! TODO: Switch to using Result here
! Use `ResultNone` which is a Result type
! that doesn't have a `data` attribute
! (i.e. if this succeeds, there is no data to check,
! if it fails, the error_v attribute will be set).
! So the code would be something like
! res = ResultNone(ErrorV(code=1, message="Index ", instance_index, " has not been claimed"))
print *, "Index ", instance_index, " has not been claimed"
error stop 1
end if

if (instance_index < 1) then
! TODO: switch to errors here - will require some thinking
! TODO: Switch to using Result here
! Use `ResultNone` which is a Result type
! that doesn't have a `data` attribute
! (i.e. if this succeeds, there is no data to check,
! if it fails, the error_v attribute will be set).
! So the code would be something like
! res = ResultNone(ErrorV(code=2, message="Requested index is ", instance_index, " which is less than 1"))
print *, "Requested index is ", instance_index, " which is less than 1"
error stop 1
end if

! ! Here, result becomes
! ! Now that I've thought about this, it's also clear
! ! that we will only use functions
! ! or subroutines with a result type that has `intent(out)`.
! ! We will no longer have subroutines that return nothing
! ! (like this one currently does).
! res = ResultNone()

end subroutine check_index_claimed

subroutine ensure_instance_array_size_is_at_least(n)
Expand All @@ -137,19 +160,19 @@ subroutine ensure_instance_array_size_is_at_least(n)

if (.not. allocated(instance_array)) then

allocate(instance_array(n))
allocate (instance_array(n))

allocate(instance_available(n))
allocate (instance_available(n))
! Race conditions ?
instance_available = .true.

else if (size(instance_available) < n) then

allocate(tmp_instances(n))
allocate (tmp_instances(n))
tmp_instances(1:size(instance_array)) = instance_array
call move_alloc(tmp_instances, instance_array)

allocate(tmp_available(n))
allocate (tmp_available(n))
tmp_available(1:size(instance_available)) = instance_available
tmp_available(size(instance_available) + 1:size(tmp_available)) = .true.
call move_alloc(tmp_available, instance_available)
Expand Down
2 changes: 1 addition & 1 deletion src/example_fgen_basic/error_v/error_v_wrapper.f90
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module m_error_v_w
error_v_manager_get_instance => get_instance, &
error_v_manager_ensure_instance_array_size_is_at_least => ensure_instance_array_size_is_at_least

implicit none (type, external)
implicit none(type, external)
private

public :: build_instance, finalise_instance, finalise_instances, &
Expand Down
2 changes: 1 addition & 1 deletion src/example_fgen_basic/error_v/passing.f90
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module m_error_v_passing

use m_error_v, only: ErrorV, NO_ERROR_CODE

implicit none (type, external)
implicit none(type, external)
private

public :: pass_error, pass_errors
Expand Down
4 changes: 2 additions & 2 deletions src/example_fgen_basic/error_v/passing_wrapper.f90
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ module m_error_v_passing_w

! The manager module, which makes this all work
use m_error_v_manager, only: &
error_v_manager_get_instance => get_instance
error_v_manager_get_instance => get_instance
! error_v_manager_get_available_instance_index => get_available_instance_index, &
! error_v_manager_set_instance_index_to => set_instance_index_to, &
! error_v_manager_ensure_instance_array_size_is_at_least => ensure_instance_array_size_is_at_least

implicit none (type, external)
implicit none(type, external)
private

public :: pass_error, pass_errors
Expand Down
4 changes: 2 additions & 2 deletions src/example_fgen_basic/fpyfgen/base_finalisable.f90
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
!> across the Python-Fortran interface.
module fpyfgen_base_finalisable

implicit none (type, external)
implicit none(type, external)
private

integer, parameter, public :: INVALID_INSTANCE_INDEX = -1
Expand Down Expand Up @@ -38,7 +38,7 @@ subroutine derived_type_finalise(self)

import :: BaseFinalisable

implicit none (type, external)
implicit none(type, external)

class(BaseFinalisable), intent(inout) :: self

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module fpyfgen_derived_type_manager_helpers

use fpyfgen_base_finalisable, only: BaseFinalisable, invalid_instance_index

implicit none (type, external)
implicit none(type, external)
private

public :: get_derived_type_free_instance_number, &
Expand Down
2 changes: 1 addition & 1 deletion src/example_fgen_basic/get_wavelength.f90
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module m_get_wavelength

use kind_parameters, only: dp

implicit none (type, external)
implicit none(type, external)
private

real(kind=dp), parameter, public :: speed_of_light = 2.99792e8_dp
Expand Down
2 changes: 1 addition & 1 deletion src/example_fgen_basic/get_wavelength_wrapper.f90
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module m_get_wavelength_w ! Convention to date: just suffix wrappers with _w
! and the original function should have the same name.
! ("o_" for original)

implicit none (type, external)
implicit none(type, external)
private

public :: get_wavelength
Expand Down
2 changes: 1 addition & 1 deletion src/example_fgen_basic/kind_parameters.f90
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
!> See https://fortran-lang.org/learn/best_practices/floating_point/
module kind_parameters

implicit none (type, external)
implicit none(type, external)
private

!> Single precision real numbers, 6 digits, range 10⁻³⁷ to 10³⁷-1; 32 bits
Expand Down
75 changes: 75 additions & 0 deletions src/example_fgen_basic/result/result.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
!> Result value
!>
!> Inspired by the excellent, MIT licensed
!> https://github.com/samharrison7/fortran-error-handler
module m_result

use m_error_v, only: ErrorV, NO_ERROR_CODE

implicit none (type, external)
private

type, abstract, public :: Result
!! Result type
!!
!! Holds either the result or an error.

! class(*), allocatable :: data_v(..)
! MZ: assumed rank can only be dummy argument NOT type/class argument
! Data i.e. the result (if no error occurs)
!
! Assumed rank array
! (https://fortran-lang.discourse.group/t/assumed-rank-arrays/1049)
! Technically a Fortran 2018 feature,
! so maybe we need to update our file extensions.
! If we can't use this, just comment this out
! and leave each subclass of Result to set its data type
! (e.g. ResultInteger will have `integer :: data`,
! ResultDP1D will have `real(dp), dimension(:), allocatable :: data`)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
! class(*), allocatable :: data_v(..)
! MZ: assumed rank can only be dummy argument NOT type/class argument
! Data i.e. the result (if no error occurs)
!
! Assumed rank array
! (https://fortran-lang.discourse.group/t/assumed-rank-arrays/1049)
! Technically a Fortran 2018 feature,
! so maybe we need to update our file extensions.
! If we can't use this, just comment this out
! and leave each subclass of Result to set its data type
! (e.g. ResultInteger will have `integer :: data`,
! ResultDP1D will have `real(dp), dimension(:), allocatable :: data`)
! Sub-classess hould have a `data` attribute
! We do not have that here, because Fortran does not have an `Any` type.


class(ErrorV), allocatable :: error_v
!! Error

contains

private

! procedure, public:: build
! TODO: Think about whether build should be on the abstract class
! or just on each concrete implementation
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
! procedure, public:: build
! TODO: Think about whether build should be on the abstract class
! or just on each concrete implementation
! Sub-classes should implement a build method too.
! As above, we can't implement this here
! because Fortran doesn't have an `Any` type
! (which is what we would need for `data`)

procedure, public :: is_error
procedure, public :: clean_up

end type Result

! interface Result
!! Constructor interface - see build (TODO: figure out cross-ref syntax) for details
! module procedure :: constructor
! end interface Result

contains

subroutine clean_up(self)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is clean_up the name to use for automatic deallocation ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it is not. The automatic deallocation is done using the final :: my_procedure.

But for abstract types I've found a problem:

  • Fortran requires that the dummy argument of a final procedure must be declared with the exact type, not the polymorphic class;
  • But an abstract type cannot be instantiated.

So final requires an exact type, but abstract types forbid exact-type dummies and that's a conflict I could not resolve.

The solution might be to have a clean_up procedure in the abstract type that gets called in the final routine of the child type to then make error_v to deallocate. I do not know if this makes sense.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super makes sense to me

!! Finalise the instance (i.e. free/deallocate)

class(Result), intent(inout) :: self
! Hopefully can leave without docstring (like Python)

if (allocated(self % error_v)) deallocate (self % error_v)

end subroutine clean_up

pure function is_error(self) result(is_err)
!! Determine whether `self` contains an error or not

class(Result), intent(in) :: self
! Hopefully can leave without docstring (like Python)

logical :: is_err
! Whether `self` is an error or not

is_err = allocated(self % error_v)

end function is_error

end module m_result
94 changes: 94 additions & 0 deletions src/example_fgen_basic/result/result_int.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
!> Result value for integers
!>
!> Inspired by the excellent, MIT licensed
!> https://github.com/samharrison7/fortran-error-handler
module m_result_int

use m_error_v, only: ErrorV
use m_result, only: Result

implicit none (type, external)
private

type, extends(Result), public :: ResultInteger1D
!! Result type that holds integer values
!!
!! Holds either an integer value or an error.

integer, allocatable :: data_v(:)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
integer, allocatable :: data_v(:)
integer :: data_v
! or
integer, allocatable :: data_v

To start, let's just do an int, rather than a 1D array of int

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did an array because one has to take care of the allocation. But ok, no problem in doing scalars first.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it

!! Data i.e. the result (if no error occurs)

! class(ErrorV), allocatable :: error_v
!! Error
Comment thread
znicholls marked this conversation as resolved.
Outdated

contains

private

procedure, public :: build
! `finalise` and `is_error` come from abstract base class
final :: finalise

end type ResultInteger1D

interface ResultInteger1D
!! Constructor interface - see build (TODO: figure out cross-ref syntax) for details
module procedure :: constructor
end interface ResultInteger1D

contains

function constructor(data_v, error_v) result(self)
!! Build instance

type(ResultInteger1D) :: self
! Hopefully can leave without docstring (like Python)

class(ErrorV), intent(inout), optional :: error_v
!! Error message

integer, allocatable, intent(in), optional :: data_v(:)
!! Data

call self % build(data_v_in=data_v, error_v_in=error_v)

end function constructor

subroutine build(self, data_v_in, error_v_in)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
subroutine build(self, data_v_in, error_v_in)
function build(self, data_v_in) result(error_v_in)
! or would the following work
function build(self, data_v_in) result(result_v)
! and we return a `ResultNone` type

!! Build instance

class(ResultInteger1D), intent(inout) :: self
! Hopefully can leave without docstring (like Python)

integer, intent(in), optional :: data_v_in(:)
!! Data

class(ErrorV), intent(inout), optional :: error_v_in
!! Error message

if (present(data_v_in) .and. present(error_v_in)) then
error_v_in % message = "Both data and error were provided"
else if (present(data_v_in)) then
allocate (self % data_v, source=data_v_in)
! No error - no need to call res % build
else if (present(error_v_in)) then
allocate (self % error_v, source=error_v_in)
! No error - no need to call res % build
else
error_v_in % message = "Neither data nor error were provided"
end if

end subroutine build

subroutine finalise(self)
!! Finalise instance

type(ResultInteger1D), intent(inout) :: self
! Hopefully can leave without docstring (like Python)

if (allocated(self % data_v)) deallocate (self % data_v)
if (allocated(self % error_v)) call self % clean_up()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (allocated(self % error_v)) call self % clean_up()
call self % clean_up()


end subroutine finalise

end module m_result_int
Loading