Language Reference

Practical reference for the current tgFortran implementation.

Overview

  • Case-sensitive
  • Free-form source
  • Curly-brace blocks
  • Zero-indexed arrays and strings
  • Function-scoped locals
  • Statement separators are newline or ;

Top-level declarations

Supported top-level declarations:

  • program
  • function
  • module
  • enum
  • udt
  • extern function
  • extern udt
module linalg {
    function twice(int64 x) -> int64 {
        return x * 2
    }
}

enum mode {
    fast = 1,
    accurate
}

udt Point {
    real64 x
    real64 y
}

Types

Built-in scalar types:

  • int32
  • int64
  • real32
  • real64
  • complex64
  • complex128
  • bool
  • string

Structured and user types:

  • dict[K, V]
  • set[T]
  • user-defined enum
  • user-defined udt

FFI-only boundary types:

  • c_ptr and c_ptr<T>
  • c_ptr_ptr
  • c_string
  • c_enum<Name>
  • c_fn<Return, Args...>

Declarations may be grouped:

real64 x, y, z
real64 px[n], py[n], vx[n], vy[n]

Arrays

Arrays are zero-based and row-major.

real64 a[8]
real64 m[4, 4]

print(a[0])
print(m[1, 2])
print(a[2:6])
print(m[:, 1])

Important current semantics:

  • assignment performs a deep copy
  • slice assignment is overlap-safe
  • scalar broadcast to arrays is supported
  • general NumPy-style rank broadcasting is not

Containers

Dict

dict[string, int64] counts
counts["a"] = 1
counts["b"] = 2

print(len(counts))
print(contains(counts, "a"))
remove(counts, "b")

Typed literals are supported:

dict[string, int64]{"x": 1, "y": 2}

Set

set[int64] active
insert(active, 4)
insert(active, 7)
print(contains(active, 7))

Container iteration order is unspecified.

Enum

enum boundary_kind {
    dirichlet = 10,
    neumann,
    periodic
}

boundary_kind bc = boundary_kind.dirichlet
if (bc == boundary_kind.neumann) {
    print("flux")
}

Enums are distinct semantic types even though they are integer-backed internally.

UDT

UDTs are value-semantics records.

udt Vec2 {
    real64 x
    real64 y
}

udt Particle {
    Vec2 pos
    real64[:] rho
}

Vec2 a = Vec2{1.0, 2.0}
Vec2 b = Vec2{x: 3.0, y: 4.0}

Supported now:

  • positional literals
  • named literals
  • nested UDTs
  • arrays of UDT
  • container fields
  • field-subscript assignment like p.rho[i] = ...
  • UDT as dict key or set element when recursively hashable

Control flow

if (x > 0) {
    print("positive")
} else {
    print("non-positive")
}

do i = 0, 10, 2 {
    print(i)
}

while (done == false) {
    break
}

do bounds are inclusive. Negative steps are supported. Zero step is a runtime error.

Parallel loops

do parallel i = 0, n - 1 {
    out[i] = a[i] + b[i]
}

do parallel distributes iterations across CPU threads via OpenMP. Each thread receives a contiguous chunk of iterations and loops internally, preserving LLVM auto-vectorization of inner loops.

  • use it for independent numeric loops
  • nested serial loops inside a parallel loop are supported
  • nested do parallel loops are supported
  • heavier kernels benefit the most
  • break, continue, and return are not allowed inside do parallel

GPU syntax exists separately, but it is experimental and gated behind --experimental-gpu. The stable documented path here is CPU do parallel.

Atomic compound assignment

Use atomic to safely update shared array elements from multiple threads:

do parallel i = 0, n - 1 {
    atomic counts[bucket[i]] += 1
}
  • supported operators: += and -= on integer and real types
  • only valid on array element assignments inside do parallel
  • maps directly to hardware atomic instructions

Explicit iteration

do key in keys(counts) {
    print(key)
}

do key, value in items(counts) {
    print(key, value)
}

Modules

module stats {
    function twice(int64 x) -> int64 {
        return x * 2
    }
}

program main {
    use stats: twice
    print(twice(21))
}

use mod and use mod: name are supported. Cross-file module resolution works.

Functions

function factorial(int64 n) -> int64 {
    if (n <= 1) {
        return 1
    }
    return n * factorial(n - 1)
}

Current semantics:

  • non-void functions must return on all control paths
  • void functions omit the return type
  • module-owned functions and extern declarations are supported

C ABI and FFI

The current interop model is C ABI oriented. It is for consuming external C libraries, not for treating native tgFortran runtime types as C-compatible by accident.

extern udt Vec2 {
    real64 x
    real64 y
}

enum file_mode {
    read = 0,
    write = 1
}

extern function vec2_scale(c_ptr<Vec2> value, real64 scale)
extern function set_mode(c_enum<file_mode> mode) -> c_enum<file_mode>
extern function register_progress(c_fn<void, c_string, real64> cb)
  • extern udt has foreign C-layout storage, distinct from native udt.
  • ffi.address_of, ffi.null_ptr, ffi.is_null, ffi.load_library, ffi.lookup_symbol, ffi.bind_symbol, and ffi.close_library are module-owned helpers.
  • ffi.bind_symbol(handle, "name") binds a loaded symbol to a declared extern function.
  • c_fn<...> is currently for extern callback parameters, not general first-class function values.

Numeric modules

linalg provides dense helpers such as dot, matvec, matmul, transpose, and trace.

lapack currently provides:

  • lapack.solve(A, b) for square real32 and real64 systems
  • lapack.cholesky(A) returning the lower-triangular factor of a positive-definite matrix
  • lapack.lu(A) returning the standard packed LU factor matrix
program main {
    use lapack

    real64 A[2, 2] = [[3.0, 1.0], [1.0, 2.0]]
    real64 b[2] = [9.0, 8.0]
    real64 x[2]

    x = lapack.solve(A, b)
    print(x[0], x[1])
}

Process and CLI modules

Command-line programs should use the current module surface:

program main {
    use os
    use cli

    string[:] args
    args = os.argv()

    if (cli.has_flag(args, "--verbose")) {
        print("verbose")
    }

    print(cli.option_or(args, "--config", "default.json"))
}
  • os.argc()
  • os.argv(i)
  • os.argv() returning string[:]
  • cli.has_flag, cli.option, cli.option_or