Tcl/Tk
Tcl is a cross-platform bytecode-interpreted scripting language that was initially developed at the University of California, Berkeley, in 1988, and is popular for its graphical widget library Tk, that is also Python’s default toolkit (known as Tkinter). Tcl provides an API to embed scripts into programs written in C and other languages. At least two packages are available to call Tcl from Fortran and vice versa:
- The Ftcl library is a non-standard bridge to Tcl for Fortran 90.
- The fortran-tcl86 library contains bindings to a subset of the Tcl/Tk 8.6 API, to directly interact with the Tcl interpreter, to extend the Tcl language through commands implemented in Fortran, and to create graphical user interfaces based on Tk.
Alternatively, we may just rely on standard input/output for inter-process communication to connect Tcl with Fortran.
- Fig. 1: Enhanced Tcl/Tk console tkcon
Installation
Tcl/Tk packages are available for most Unix-like operating systems. On
FreeBSD, install the Tcl shell tclsh
, the Tcl/Tk interpreter
wish
, and the Tk graphical user interface toolkit with:
# pkg install lang/tcl86 x11-toolkits/tk86
We can then invoke the interactive Tcl shell:
$ tclsh8.6
% puts "Hello, World!"
Hello, World!
tkcon is a platform-independent Tk-based replacement for the standard console that provides an input history among other features (fig. 1).
Anonymous Pipe
The example program in Fortran will convert temperature values from °Ré to °C. The historic Réaumur scale was common in Europe until the mid-19th century, and is still used in niche markets. In comparison to °C, the freezing and boiling points of water are defined as 0 and 80 degrees respectively instead. Today, references can sometimes be found in Russian literature:
Simply because a poor student, unhinged by poverty and hypochondria, on the eve of a severe delirious illness (note that), suspicious, vain, proud, who has not seen a soul to speak to for six months, in rags and in boots without soles, has to face some wretched policemen and put up with their insolence; and the unexpected debt thrust under his nose, the I.O.U. presented by Tchebarov, the new paint, thirty degrees Reaumur and a stifling atmosphere, a crowd of people, the talk about the murder of a person where he had been just before, and all that on an empty stomach--he might well have a fainting fit!
— Fyodor Dostoevsky: Crime and Punishment, Part III, Chapter IV, p. 276
The input value in °Ré is simply read-in from stdin, converted to °C, and written to stdout:
! re2c.f90
program re2c
implicit none
integer :: stat
real :: re
do
read (*, *, iostat=stat) re
if (stat /= 0) exit
print '(f0.2)', re * 5 / 4
end do
end program re2c
Then, simply compile the source code to the executable re2c
:
$ gfortran13 -o re2c re2c.f90
The binary re2c
will later be executed by our Tcl/Tk script.
- Fig. 2: Tcl/Tk graphical user interface that converts temperature values (fonts, widget style, and window decorations depend on the window manager and the selected Tk theme)
The front-end script script.tcl
in Tcl creates a Tk window with
entry widgets, label widgets, and a single button widget. The callback routine
callback
will be invoked on button press events, and then opens an
anonymous pipe to the Fortran program re2c
.
The input value in °Re is passed through standard output to the Fortran program, and the result is read back through standard input. The converted temperature value in °C is then displayed in one of the entry widgets:
#!/usr/bin/env tclsh8.6
package require Tk 8.6
variable input 0.0
variable output 0.0
set title "Réaumur to Celsius"
set font "Helvetica 12"
set theme "classic"
# Set theme (optional), one of: clam, alt, default, classic.
ttk::style theme use $theme
# Set window title and widget font.
wm title . $title
option add *font $font
# Create widgets.
ttk::frame .frame -padding "10 10 10 10"
ttk::label .frame.text -text "Convert from °Ré to °C:"
ttk::entry .frame.input -textvariable input -width 7
ttk::label .frame.deg_re -text "°Ré"
ttk::button .frame.button -command callback -text ">"
ttk::entry .frame.output -textvariable output -width 7
ttk::label .frame.deg_c -text "°C"
# Set initial focus to input widget.
focus .frame.input
# Create grid.
grid .frame -column 0 -row 0 -sticky nwes
grid .frame.text -column 0 -row 1 -columnspan 6 -sticky we
grid .frame.input -column 1 -row 2 -sticky we
grid .frame.deg_re -column 2 -row 2 -sticky we
grid .frame.button -column 3 -row 2 -sticky w
grid .frame.output -column 4 -row 2 -sticky we
grid .frame.deg_c -column 5 -row 2 -sticky we
grid columnconfigure . 0 -weight 1
grid rowconfigure . 0 -weight 1
# Add padding to widgets.
foreach w [winfo children .frame] {
grid configure $w -padx 5 -pady 5
}
# Add key bindings.
bind . <Return> {callback}
bind all <Escape> {exit}
# Prevent resizing of the window.
update idletasks ;# Show window first to get correct size.
set size [wm geometry .] ;# Get current window size.
regexp {(\d+)x(\d+)} $size all w h ;# Extract width and height.
wm aspect . $w $h $w $h
wm minsize . $w $h
wm maxsize . $w $h
# The callback routine.
proc callback {} {
variable input
variable output
if {![string length $input]} return
set f [open "| ./re2c" r+] ;# Open anonymous pipe to program.
puts $f $input ;# Write to stdout.
flush $f ;# Flush buffer.
gets $f output ;# Read from stdin.
close $f ;# Close file handle.
}
We can execute the Tcl/Tk script script.tcl
either with
tclsh
or with wish
, the Tcl interpreter for graphical
Tk applications:
$ tclsh8.6 ./script.tcl
The script can be made executable:
$ chmod a+x script.tcl
$ ./script.tcl
Make sure the Fortran application re2c
is located in the same
directory as the Tcl/Tk script:
<project>/
re2c
re2c.f90
script.tcl
The graphical user interface shows the converted temperature returned from the Fortran program (fig. 2).
fortran-tcl86
The library fortran-tcl86 provides interoperability with Tcl/Tk 8.6 through Fortran 2018 interface bindings, and allows us:
- to embed the Tcl interpreter into Fortran,
- to create Tcl extensions in Fortran (with Tcl Stubs),
- to access (a subset of) the Tcl/Tk C API from Fortran,
- to use Tcl as an evaluatable configuration file format, and
- to add graphical user interfaces based on Tk to Fortran programs.
On Linux, the library requires Tcl/Tk 8.6 to be installed with development
headers. Fetch the source code and compile the static libraries
libftcl86.a
, libftclstub86.a
, and
libftk86.a
:
$ git clone https://github.com/interkosmos/fortran-tcl86
$ cd fortran-tcl86/
$ make
The following program re2c
shows the same graphical user
interface as the last example, but this time we create a Tcl environment in
Fortran, using the Tcl/Tk C API. Furthermore, we extend the interpreter by an
additional Tcl command re2c
written in Fortran. The UI script has
to be slightly modified to execute this command.
! re2c.f90
program main
use, intrinsic :: iso_c_binding
use :: tcl
use :: tcl_ext
use :: tk
implicit none (type, external)
character(len=*), parameter :: SCRIPT_FILE = 'script.tcl'
character(len=32) :: argv0
integer :: rc
logical :: file_exists
type(c_ptr) :: interp
type(c_ptr) :: ptr
inquire (exist=file_exists, file=SCRIPT_FILE)
if (.not. file_exists) stop 'Error: Tcl script not found'
! Set name of executable. Fills internal Tcl variable
! used by `info nameofexecutable`.
call get_command_argument(0, argv0)
call tcl_find_executable(trim(argv0))
! Create Tcl interpreter.
interp = tcl_create_interp()
if (.not. c_associated(interp)) stop 'Error: Tcl_CreateInterp() failed'
! Initialise Tcl.
if (tcl_init(interp) /= TCL_OK) then
call tcl_delete_interp(interp)
stop 'Error: Tcl_Init() failed'
end if
! Initialise Tk.
if (tk_init(interp) /= TCL_OK) then
call tcl_delete_interp(interp)
stop 'Error: Tk_Init() failed'
end if
! Expose the Fortran function as command `re2c` to Tcl.
ptr = tcl_create_obj_command(interp, 're2c', c_funloc(re2c_cmd))
! Run the Tcl/Tk script.
rc = tcl_eval_file(interp, SCRIPT_FILE)
! Show the Tk window.
call tk_main_loop()
call tcl_exit(0)
end program main
The Tcl command re2c
registered with function
tcl_create_obj_command()
is implemented in module
tcl_ext
. The Fortran function re2c_cmd()
reads the
argument passed from Tcl, converts the given temperature, and returns the
result:
! tcl_ext.f90
module tcl_ext
use, intrinsic :: iso_c_binding
use, intrinsic :: iso_fortran_env, only: r8 => real64
use :: tcl
implicit none (type, external)
private
public :: re2c
public :: re2c_cmd
contains
elemental function re2c(re) result(c)
!! Converts temperature from degrees Reaumur to degrees Celsius.
real(kind=r8), intent(in) :: re
real(kind=r8) :: c
c = re * 5 / 4
end function re2c
function re2c_cmd(client_data, interp, objc, objv) bind(c)
!! Provides the Tcl extension `re2c`.
type(c_ptr), intent(in), value :: client_data
type(c_ptr), intent(in), value :: interp
integer(kind=c_int), intent(in), value :: objc
type(c_ptr), intent(in) :: objv(*)
integer(kind=c_int) :: re2c_cmd
integer :: rc
real(kind=r8) :: re
type(c_ptr) :: return_value
re2c_cmd = TCL_ERROR
! Check if argument has been passed.
if (objc < 2) then
call tcl_wrong_num_args(interp, 1, objv, 're number')
return
end if
! Get passed temperature value in deg Re.
rc = tcl_get_double_from_obj(interp, objv(2), re)
if (rc /= TCL_OK) return
! Convert value and return temperature in deg C.
return_value = tcl_new_double_obj(re2c(re))
if (.not. c_associated(return_value)) return
call tcl_set_obj_result(interp, return_value)
re2c_cmd = TCL_OK
end function re2c_cmd
end module tcl_ext
Finally, we replace the callback routine in script.tcl
from the previous example to call the Tcl extension re2c
provided
by our interpreter instead of the binary:
proc callback {} {
variable input
variable output
if {![string length $input]} return
set output [re2c $input]
}
Compile the extension module and build the executable re2c
:
$ gfortran13 -c tcl_ext.f90
$ gfortran13 -I/usr/local/include/tcl8.6 -I/usr/local/include/tk8.6 \
-L/usr/local/lib/tcl8.6 -L/usr/local/lib/tk8.6 \
-o re2c re2c.f90 tcl_ext.o libftcl86.a libftk86.a -ltcl86 -ltk86
The include and library search paths for Tcl 8.6 and Tk 8.6, as well as the
names of the libraries depend on the operating system and may differ (for
instance, -ltk8.6 -ltcl8.6
on Linux). It is probably easier to just
invoke pkg-config(1) to return the correct compiler and linker
flags:
$ gfortran13 `pkg-config --cflags tk86` -o re2c re2c.f90 tcl_ext.o \
libftcl86.a libftk86.a `pkg-config --libs tk86`
Start the graphical application (fig. 2) by running:
$ ./re2c
Fortran Libraries
- fortran-tcl86: Fortran 2018 interface bindings to Tcl/Tk 8.6
- Ftcl: Fortran 90 bridge to Tcl
- ftk: Fortran interface to Tcl/Tk for building GUIs
GUI Builders
- GRIDPLUS2: Free grid-based GUI builder for Tcl/Tk
- Visual Tcl: Free GUI builder for Tcl/Tk
- vTcl 8.6: Visual Tcl for Tcl/Tk 8.6
Further Reading
- Tcl Developer Xchange: Official website
- Tcl Wiki: Tutorial Lessons
- Tcl the Misunderstood: Introduction to Tcl
References
- C. Flynt: Tcl/Tk. A Developer’s Guide, Third Edition. Elsevier, 2012
- M. Harrison & M. McLennan: Effective Tcl/Tk Programming. Writing Better Programs with Tcl and Tk. Addison-Wesley, 1998
- J. K. Ousterhout & K. Jones: Tcl and the Tk Toolkit, Second Edition. Addison-Wesley, 2010
< Lua | [Index] |