ofort is a small offline interpreter for a practical subset of Fortran.
It is written in C and runs Fortran source directly from the command line or
from an interactive REPL. The project began as an extraction from
CodeBench by yu314-coder and is now organized as a
standalone command-line interpreter.
ofort is intended for experimentation, small examples, tests, and interpreter
development. It is not a production Fortran compiler.
include/ofort.hcontains the public interpreter declarations shared by the C translation units.src/main.ccontains command-line handling, the REPL, and user-facing diagnostics.src/ofort.ccontains most of the parser, evaluator, runtime, and intrinsic implementation.src/ofort_internal.hcontains internal declarations shared insidesrc.src/ofort_values.ccontains value helper routines split out from the main interpreter implementation.src/ofort_stats.candinclude/ofort_stats.hcontain C-backed helper routines used byofort_statistics_modand related extension-module intrinsics.src/ofort_fixed_form.candinclude/ofort_fixed_form.hcontain the fixed source form to free source form converter used byofort.tests/test_ofort.pycontains the main pytest suite.tests/casescontains focused Fortran regression programs. Most stdout regression cases use aname.f90source file plus a siblingname.outexpected-output file.scripts/xofort.pyis a batch runner for trying many source files one at a time.scripts/analyze_ofort_symbols.pyanalyzes internal token/keyword handling and is used by the test suite.tools/fixed2free.cbuilds a small standalone fixed-form to free-form converter.ofort_gui.pyis a small Tkinter GUI wrapper aroundofort.exe.include/ofort_c_api.handsrc/ofort_c_api.cprovide a small C ABI wrapper suitable for language bindings.bindings/fortrancontains a Fortraniso_c_bindingwrapper and demo program.examplescontains small demonstration programs when present.
Generated files such as ofort.exe, ofort.build, main*.f90, compiler
objects, module files, and temporary gfortran executables are local build/run
artifacts and should not be committed.
The interpreter currently supports a growing practical Fortran subset. It is no
longer only a Fortran 90/95-style interpreter: the core remains small and
pragmatic, but ofort accepts selected Fortran 2003/2008/2018-era features
where they are useful for real test programs. Coverage is implementation-driven
rather than standard-complete.
- scalar
INTEGER,REAL,DOUBLE PRECISION,COMPLEX,LOGICAL, andCHARACTER - arrays, array constructors, array sections, vector subscripts, allocatable arrays, allocatable scalars, and derived-type arrays
- modules,
useimports, derived types, functions, subroutines, generic interfaces, optional arguments,intent,pureprocedures, and elemental functions/subroutines classdeclarations, polymorphic-style dummy arguments, type-bound procedures, and basic dispatch for supported cases- selected parameterized derived-type syntax and derived-type parameter handling used by the regression tests
block data,bind(c)on supported declarations and common blocks, selected user-defined operators, and selected pointer/allocation features- selected
iso_fortran_envnamed constants such asreal64, includingonlyrenaming implicit none, configurable implicit typing, declaration reordering in the REPL, assignment type checks, and diagnostics that include source linesif,select case,do,do while, labeleddo, numbereddo, numbereddo while,exit,cycle,goto, computedgoto,forall,stop, andreturnformatstatements, formattedprint/write,read,open,close,rewind,backspace,endfile, internal I/O, simple external files, and simple unformatted stream I/O- free source form plus automatic fixed source form conversion for
.f,.for, and related fixed-form inputs - simple preprocessing support for
#definemacro substitution in source files - explicitly imported
ofortextension modules, currently includingofort_random_mod,ofort_la_mod,ofort_io_mod, andofort_statistics_mod - command-line arguments via
command_argument_count,get_command_argument, and the nonstandardgetarg - allocatable utilities such as
allocated,allocate,deallocate, andmove_alloc
Support for modern Fortran features is intentionally incremental rather than complete. A feature being accepted in one tested form does not imply full standard coverage for every edge case.
ofort has optional nonstandard extension modules for interpreted workflows.
They are not required to run ordinary Fortran programs, and their procedure
names are not global intrinsics. Import them explicitly with use when needed.
Current extension modules:
ofort_random_mod: normal random variatesofort_la_mod: dense linear algebra helpersofort_io_mod: simple numeric text readersofort_statistics_mod: vector, matrix, and column statistics
See docs/extension_modules.md for the full module reference, including procedure lists, arguments, examples, and current limitations.
Some old Fortran features are accepted for compatibility but reported as
obsolescent or nonstandard where appropriate. Examples include computed goto,
old alternate returns, and selected extension-style calls.
REAL and DOUBLE PRECISION are distinguished by type tag and kind, but both
are stored internally as C double.
Major modern Fortran features not currently implemented include coarrays and
select type. Some syntax from these areas may be diagnosed explicitly rather
than accepted.
Implemented intrinsics include common numeric, character, bit, inquiry, array, random-number, and date/time routines, including:
abs, sqrt, sin, cos, tan, asin, acos, atan, atan2, exp,
log, log10, gamma, hypot, mod, modulo, dim, aint, nint,
ceiling, floor, int, real, dble, dprod, kind, selected_int_kind,
selected_real_kind, huge, tiny, epsilon, digits, precision, range,
radix, nearest, spacing, rrspacing, scale, set_exponent, fraction,
exponent, len, trim, scan, verify, sum, product, maxval,
minval, maxloc, minloc, count, any, all, merge, pack, unpack,
spread, eoshift, cshift, reshape, shape, size, allocated,
associated, lbound, ubound, bit_size, btest, iand, ior, ieor,
ibclr, ibset, ibits, ishft, ishftc, maskl, maskr, random_number,
random_seed, cpu_time, date_and_time, and system_clock.
Known missing intrinsics and future candidates are tracked in
intrinsics_to_do.txt when that file is present.
Build with the default compiler:
makeExplicit compiler targets are also available:
make gcc
make clangThe generated executable is ofort.exe on Windows.
make writes a small ofort.build file describing the compiler used for the
last build. That file is generated and should not be committed.
Build the standalone fixed-form converter:
make fixed2free.exeRun one source file:
.\ofort.exe examples\xtry.f90Pass program arguments after --:
.\ofort.exe x.f90 -- alpha betaMultiple positional source files are concatenated in command-line order before execution:
.\ofort.exe part1.f90 part2.f90Run each file matched by a Windows glob as a separate program:
.\ofort.exe --each "tests\cases\x*.f90"
.\ofort.exe --each --check "tests\cases\x*.f90"
.\ofort.exe --each --check --quiet --max-fail 10 "tests\cases\x*.f90"Check syntax and semantic registration without running the program:
.\ofort.exe --check x.f90Compare ofort output with gfortran:
.\ofort.exe --check-gfortran x.f90Enable execution timing:
.\ofort.exe --time x.f90
.\ofort.exe --time-detail x.f90In --each --quiet --time mode, ofort prints only the final summary and one
total elapsed time line.
Print execution time by source line:
.\ofort.exe --profile-lines x.f90Enable interpreter fast paths:
.\ofort.exe --fast x.f90See optimizations.md for the current --fast
implementation and its limitations. Use --no-specialize with --fast to
disable specialized pattern/program fast paths while keeping the general fast
mode enabled.
Trace assignment values:
.\ofort.exe --trace-assign x.f90--trace-assign prints the value assigned by each executed assignment
statement. In the interactive REPL, top-level assignment lines run immediately
when this mode is enabled, so a line such as n = 2**5 prints 32 without
requiring . or .run.
By default, ofort follows historical Fortran implicit typing, where names
beginning with I-N are integers and other names are real. To require explicit
declarations unless an implicit statement says otherwise:
.\ofort.exe --no-implicit-typing x.f90Warnings are enabled by default. Use -w to suppress them:
.\ofort.exe -w x.f90ofort accepts free source form by default and automatically treats common
fixed-form extensions such as .f and .for as fixed source form. Use
--fixed-form or --free-form to override auto-detection:
.\ofort.exe --fixed-form oldcode.f
.\ofort.exe --free-form modern.f90The internal converter is shared with the standalone fixed2free.exe tool:
make fixed2free.exe
.\fixed2free.exe oldcode.f > oldcode.f90To save the converted free-form source next to the original input while running
ofort, use:
.\ofort.exe --save-free oldcode.fThe Python batch runner processes a file glob one source at a time:
python .\scripts\xofort.py "tests\cases\*.f90"
python .\scripts\xofort.py --check "tests\cases\*.f90"
python .\scripts\xofort.py --limit 5 "tests\cases\*.f90"
python .\scripts\xofort.py --timeout 30 "tests\cases\*.f90"
python .\scripts\xofort.py --max-lines 100 "tests\cases\*.f90"
python .\scripts\xofort.py --quiet "tests\cases\*.f90"
python .\scripts\xofort.py --filter "gfortran -std=f95" "tests\cases\*.f90"--quiet reports only files that ofort does not handle. --filter runs the
given command with the source file appended and skips files for which that
command fails.
ofort_gui.py provides a small Tkinter front end around the command-line
interpreter. It does not link to the C code directly; it writes the editor
contents to a temporary Fortran file, invokes ofort, and displays the
captured output. It can also call external compilers when they are installed
and available on PATH.
Run it with:
python .\ofort_gui.pyCurrent GUI features include:
- source editor with 4-space auto-indentation for common
if,do,select,where, and related blocks - syntax coloring for common Fortran keywords, declaration words, intrinsics, strings, comments, and numbers
- open/save support for Fortran source files
- checkboxes for
--trace-assign,--no-implicit-typing,--std=f2023, and--fast - run buttons for
ofort,gfortran,ifx, and LFortran - check/compile-only buttons:
Check ofort,Compile gfortran,Compile ifx, andCompile LFortran - stdout, stderr, assignment-trace, and problems panes, with the active pane switching automatically after run/check/compile actions
- clickable problem diagnostics that jump to the reported source line
- compiler output cleanup for common ANSI color sequences emitted by tools such as LFortran
- elapsed-time reporting for check, compile, run, and total time
The GUI should run on Windows, macOS, and Linux when Python includes Tkinter and
an ofort executable is either next to ofort_gui.py or available on PATH.
External compiler buttons require the corresponding compiler executable
(gfortran, ifx, or lfortran) to be installed and available on PATH.
ofort also includes an experimental Fortran binding. The binding is built in
two layers:
include/ofort_c_api.handsrc/ofort_c_api.cexpose a small stable C ABI with opaque interpreter handles.bindings/fortran/ofort_binding.f90wraps that C ABI usingiso_c_binding.
The Fortran wrapper provides an ofort_interpreter derived type with methods
such as:
use ofort_binding, only: ofort_interpreter
type(ofort_interpreter) :: interp
integer :: rc
call interp%create()
rc = interp%execute("integer :: n = 2; print*,n**5")
write (*, '(a)', advance='no') interp%output()
call interp%destroy()The current demo is bindings/fortran/demo_eval.f90. It creates an
interpreter, executes a small source string, prints captured ofort output,
then enables assignment tracing and prints the captured trace output. It also
defines an interpreted Fortran function and calls it directly from compiled
Fortran with the typed call_real1 wrapper:
rc = interp%execute("real function f(x); real :: x; f = x*x + 1; end")
y = interp%call_real1("f", 3.0d0)Build the demo from the repository root with:
make -f bindings\fortran\makefileRun it with:
.\bindings\fortran\demo_eval.exeExpected output:
32
56
10.000000000000000
The binding is intended for embedding ofort in Fortran programs for
experimentation, dynamic evaluation, and small interpreter-driven workflows. It
currently exposes a minimal typed direct-call API for invoking an interpreted
real/double precision function of one real argument. Broader direct-call
support, such as integer, logical, character, multi-argument, and array
signatures, would require additional typed wrappers and argument/result
marshalling.
Benchmark helpers are in scripts\bench_ofort.py and the benchmarks
directory when present. The benchmark script can compare ofort, ofort --fast, gfortran, ifx, and lfortran, depending on which tools are
installed.
Generate or resize the benchmark programs with one base problem-size parameter:
python .\scripts\generate_benchmarks.py --size 1000000
python .\scripts\generate_benchmarks.py --size 5000000 --listThe generator writes the benchmarks\xbench_*.f90 files. Each benchmark derives
its own n from the base size so that the current mix of array and scalar-loop
tests remains roughly balanced. Increase --size until benchmark run times are
large enough to be meaningful on your machine.
Example:
python .\scripts\bench_ofort.py --ofort-only
python .\scripts\bench_ofort.py --ofort-only --gfortranWith no file argument, ofort starts a REPL. Type Fortran source at the prompt
and use dot commands to run, edit, inspect, and save the current source buffer.
Common commands:
. run the current source and continue
.run run; .run n repeats n times; use -- before program arguments
.runq run and quit
.time run and print elapsed-time statistics; .time n repeats n times
.quit quit without running
.clear clear the current source
.del delete a line or range, such as .del 3, .del 2:4, .del :3, .del 4:
.ins insert a source line before a line number
.rep replace a source line
.rename rename a variable token throughout the editable source
.list list the current source
.decl list declaration lines
.vars list variable values
.info list concise declaration-style variable information
.shapes list array shapes
.sizes list array sizes
.stats list numeric array statistics
.load load a file into the current source
.load-run load a file, run it once, and keep editing
The shortcuts q and quit also quit, unless q or quit has been defined
as a variable in the current source buffer. .quit always quits.
When .load file.f90, .load-run file.f90, --load file.f90, or
--load-run file.f90 loads a complete program, the final end line is held
aside so new input is inserted before the end of the program. The held line is
restored when running or autosaving.
In interactive mode only, a bare expression line is evaluated immediately
against the current source buffer and prints its value. For example, after
entering x = 3, a line containing only x displays 3 immediately. Bare
expression lines are not added to the source buffer.
The REPL also accepts two convenience forms for quick interactive work:
let x = 2.5
const n = 100let infers a scalar type from the right-hand side, appends a standard
declaration, and then appends an assignment. For example, let x = 2.5 is
stored as ordinary Fortran source equivalent to:
real :: x
x = 2.5const infers a scalar named constant and stores a standard PARAMETER
declaration. For example, const n = 100 is stored as:
integer, parameter :: n = 100Use let only for the first definition of a variable; after that, use normal
assignment such as x = 3.0. To change a named constant during a session, use
reconst name = value, which replaces the earlier parameter declaration in the
editable source buffer. These forms are REPL conveniences, not Fortran syntax,
and saved/autosaved source uses standard Fortran declarations.
The REPL preflights source after each entered program line. Lines with syntax errors, such as malformed declarations, are rejected and are not kept in the editable source buffer.
When an interactive session exits with a non-empty source buffer, the buffer is
saved automatically as main.f90, or main1.f90, main2.f90, and so on if
earlier names already exist.
Those main*.f90 files are autosave artifacts. Move or delete them when no
longer needed; they are not intended to be committed.
pytest -qThe test suite is intentionally small-program oriented. New language features
are usually added by creating a focused tests/cases/x*.f90 source and a
matching .out file.
Only tests/cases/*.f90 files with a sibling UTF-8 text .out file are
collected as simple stdout regression cases. Files without an expected-output
file may still be used by targeted tests in tests/test_ofort.py.
Some tests compare behavior with gfortran or exercise local files. The core
test suite should pass with:
make
pytest -qgithub_check.bat is a Windows helper that clones the GitHub repository into
C:\github\ofort by default, or into the directory supplied as its first
argument, then runs make gcc and pytest -q there.
github_check.bat
github_check.bat C:\github\ofort_tempgitcheck.bat is a local Windows cmd.exe sanity check for this working copy.
It clones the committed HEAD into ofort_build_check, builds it, and runs the
tests. It is useful for catching files that were edited locally but not
committed. It is not required for portable builds.