Arjen Markus (6 april 2021) My two favourite programming languages are Tcl and Fortran. Jim, as an implementation of Tcl that has a smaller footprint, has attracted my attention from time to time and recently I was inspired to create an interface between Fortran and Jimtcl, based on the interfacing facilities offered by the Fortran 2003 standard.
The technique consists of providing formal interfaces on the Fortran side to the C API. The result is that almost the entire interface can be defined in Fortran. As an illustration find below the complete source for a rather limited set of Jim functions. It is enough to create a Jim interpreter and register new commands, thus providing scripting facilities.
Note: the code below can and should be improved. Right now there is too much "C" visible, such as the kind parameters c_int etc. Also, it would be better to have specific types for the interpreter pointer (interp) and the pointers to JimObj objects. Some C code is needed to make the various macros accessible, notably Jim_IncrRefCount and the like. And some generic interfaces, because Jim_GetLong resembles Jim_GetDouble a lot.
! fjim.f90 -- ! Interface to the Jim library ! module fjim use iso_c_binding implicit none integer, parameter :: jim_ok = 0 integer, parameter :: jim_err = 1 integer, parameter :: jim_return = 2 integer, parameter :: jim_break = 3 interface function Jim_CreateInterp() bind(C, name = 'Jim_CreateInterp') import :: c_ptr type(c_ptr) :: Jim_CreateInterp end function Jim_CreateInterp end interface interface subroutine Jim_FreeInterp( interp ) bind(C, name = 'Jim_FreeInterp') import :: c_ptr type(c_ptr), value :: interp end subroutine Jim_FreeInterp end interface interface subroutine Jim_RegisterCoreCommands( interp ) bind(C, name = 'Jim_RegisterCoreCommands') import :: c_ptr type(c_ptr), value :: interp end subroutine Jim_RegisterCoreCommands end interface interface subroutine Jim_InitStaticExtensions( interp ) bind(C, name = 'Jim_InitStaticExtensions') import :: c_ptr type(c_ptr), value :: interp end subroutine Jim_InitStaticExtensions end interface interface subroutine Jim_Eval_c( interp, string ) bind(C, name = 'Jim_Eval') import :: c_ptr type(c_ptr), value :: interp character(len=1), dimension(*) :: string end subroutine Jim_Eval_c end interface interface subroutine Jim_CreateCommand_c( interp, string, funcptr, privdata, delptr ) bind(C, name = 'Jim_CreateCommand') import :: c_ptr, c_funptr type(c_ptr), value :: interp character(len=1), dimension(*) :: string type(c_funptr), value :: funcptr type(c_ptr), value :: privdata type(c_funptr), value :: delptr end subroutine Jim_CreateCommand_c end interface interface integer function Jim_GetLong( interp, objptr, value ) bind(C, name = 'Jim_GetLong') import :: c_ptr, c_long type(c_ptr), value :: interp type(c_ptr), value :: objptr integer(kind=c_long), intent(inout) :: value end function Jim_GetLong end interface interface integer function Jim_GetDouble( interp, objptr, value ) bind(C, name = 'Jim_GetDouble') import :: c_ptr, c_double type(c_ptr), value :: interp type(c_ptr), value :: objptr real(kind=c_double), intent(inout) :: value end function Jim_GetDouble end interface interface function Jim_GetString_c( objptr, length ) bind(C, name = 'Jim_GetString') import :: c_ptr, c_int type(c_ptr), value :: objptr integer(kind=c_int), intent(out) :: length type(c_ptr) :: Jim_GetString_c end function Jim_GetString_c end interface contains ! jim_newinterp -- ! Create a new Jim interpreter ! ! Arguments: ! interp Pointer to the interpreter structure ! subroutine jim_newinterp( interp ) type(c_ptr), intent(out) :: interp interp = Jim_CreateInterp() call Jim_RegisterCoreCommands( interp ) call Jim_InitStaticExtensions( interp ) end subroutine jim_newinterp subroutine jim_deleteinterp( interp ) type(c_ptr), intent(in) :: interp call Jim_FreeInterp( interp ) end subroutine jim_deleteinterp subroutine jim_eval( interp, string ) type(c_ptr), intent(in) :: interp character(len=*), intent(in) :: string call Jim_Eval_c( interp, string // c_null_char ) end subroutine jim_eval ! jim_createcommand -- ! Create a new command ! ! Arguments: ! interp Jim interpreter ! cmdname Name of the command ! cmdptr Function that implements the command ! ! Note: ! Other arguments suppressed for the moment ! subroutine jim_createcommand( interp, cmdname, cmdptr ) !!, privdata, delptr ) type(c_ptr), intent(in) :: interp character(len=*), intent(in) :: cmdname interface integer function cmdptr( interp, argc, argv ) import :: c_ptr type(c_ptr), value :: interp integer, value :: argc type(c_ptr), dimension(*) :: argv end function cmdptr end interface call Jim_CreateCommand_c( interp, trim(cmdname) // c_null_char, c_funloc(cmdptr), c_null_ptr, c_null_funptr ) end subroutine jim_createcommand ! jim_getstring -- ! Get the string represntation from a JimObj object ! ! Arguments: ! interp Pointer to the interpreter structure ! objptr Pointer to the JimObj object in question ! ! Return value: ! Pointer to the string ! function jim_getstring( objptr ) type(c_ptr), intent(in) :: objptr character(kind=c_char,len=:), pointer :: jim_getstring type(c_ptr) :: c_string integer(kind=c_int) :: length c_string = jim_getstring_c( objptr, length ) block character(kind=c_char, len=length), pointer :: t_string call c_f_pointer( c_string, t_string ) jim_getstring => t_string t_string => null() end block end function jim_getstring end module fjim module mycommands use fjim implicit none contains integer function myputs( interp, argc, argv ) type(c_ptr), value :: interp integer, value :: argc type(c_ptr), dimension(*) :: argv integer :: i integer(kind=c_long_long) :: ivalue real(kind=c_double) :: dvalue write(*,*) 'Number of arguments: ', argc do i = 1,argc if ( jim_getlong( interp, argv(i), ivalue ) == jim_ok ) then write(*,*) i, ': ', ivalue elseif ( jim_getdouble( interp, argv(i), dvalue ) == jim_ok ) then write(*,*) i, ': ', dvalue else write(*,*) i, ': ', jim_getstring( argv(i) ) endif enddo end function myputs end module mycommands ! test -- ! Simple test, copy of the "jim_hello.c" program ! program test_fjim use fjim use mycommands implicit none type(c_ptr) :: interp call jim_newinterp( interp ) call jim_createcommand( interp, "printargs", myputs ) call jim_eval( interp, 'puts "Hello, world!"' ) call jim_eval( interp, 'printargs 2 "Hello, world!" 3.1415926' ) call jim_deleteinterp( interp ) end program test_fjim
I built it using Cygwim on Windows, using:
gfortran -o demo fjim.f90 libjim.a
and the output of the program is:
Hello, world! Number of arguments: 4 1 : printargs 2 : 2 3 : Hello, world! 4 : 3.1415926000000001
arjen - 2021-09-28 06:26:12
I am currently extending this proof-of-concept to make it actually useable in applications.