grassleaff/tif
2
1
Fork
You've already forked tif
0
tif is multi-paradigm interpreted programming language implemented in Lua, designed to be simple, readable and easy to understand. The project focuses on clarity, experimentation, and language implementation as much as on day-to-day use, making it a compact but capable language for learning, prototyping, and exploring interpreter design and fundamental programming concepts.
  • Lua 76.5%
  • C 13.7%
  • Shell 6.1%
  • Makefile 2.2%
  • Vim Script 1.5%
2026年04月27日 16:00:57 -03:00
assets ADD [v0.3-saren]: New tif logo outline style 2026年04月11日 17:16:31 -03:00
editors ADD [v0.3-saren, HUGE UPDATE]: New optional typed interpolation + optional parameters to functions + compiling tif binaries on a native compiler without external runtime (and add -B flag because why not?) 2026年04月13日 20:32:10 -03:00
examples ADD [v0.3-saren, HUGE UPDATE]: New optional typed interpolation + optional parameters to functions + compiling tif binaries on a native compiler without external runtime (and add -B flag because why not?) 2026年04月13日 20:32:10 -03:00
libs/stdlib ADD [v0.3-saren]: Implement luajit runtime compatibility on tif installation 2026年04月09日 10:03:27 -03:00
mods ADD [v0.3-saren]: Add option to compile tif files into native binarys with -o flag + xld optional linker support 2026年04月12日 21:34:33 -03:00
src FIX: [v0.3.2-saren]: Fix version badge to v0.3.1 -> v0.3.2 2026年04月27日 16:00:57 -03:00
.gitignore ADD [v0.3.2-saren]: New tif portable binaries and better native binaries generations supporting more returns (TODO: Resolve imports and other returns on native binaries) 2026年04月22日 14:31:42 -03:00
clua_exec.c ADD [v0.3-saren]: Implement luajit runtime compatibility on tif installation 2026年04月09日 10:03:27 -03:00
clua_exec_impl.h ADD [v0.3-saren]: Implement luajit runtime compatibility on tif installation 2026年04月09日 10:03:27 -03:00
clua_exec_luajit.c ADD [v0.3-saren]: Implement luajit runtime compatibility on tif installation 2026年04月09日 10:03:27 -03:00
LICENSE Update copyright holder in LICENSE file 2026年02月19日 21:55:22 -03:00
Makefile ADD [v0.3-saren]: Add option to compile tif files into native binarys with -o flag + xld optional linker support 2026年04月12日 21:34:33 -03:00
README.md ADD [v0.3.2-saren]: New tif portable binaries and better native binaries generations supporting more returns (TODO: Resolve imports and other returns on native binaries) 2026年04月22日 14:31:42 -03:00
thd.c ADD [v0.3-saren]: Implement luajit runtime compatibility on tif installation 2026年04月09日 10:03:27 -03:00
tif_lua_compat.h ADD [v0.3-saren]: Implement luajit runtime compatibility on tif installation 2026年04月09日 10:03:27 -03:00
tifmenu.sh ADD [v0.3-saren]: Add option to compile tif files into native binarys with -o flag + xld optional linker support 2026年04月12日 21:34:33 -03:00

brazilian-software-engineering

tif

tif is a small interpreted programming language implemented in Lua.
It was designed to be simple, lightweight and easy to understand.

Overview

The language is built around a classic interpreter architecture:

  • Lexer – converts source code into tokens, with line and column tracking
  • Parser – builds an Abstract Syntax Tree (AST) using Pratt parsing for correct operator precedence
  • Interpreter – evaluates the AST directly with scoped environments and runtime safety limits

tif supports both Lua 5.4 and LuaJIT as host runtimes. The optimized default build targets LuaJIT, while Lua 5.4 remains available as a compatibility/runtime choice.

Stable release: v0.3-saren

This release extends the language with functional programming capabilities and performance improvements. These features enable higher-order function patterns and more idiomatic functional code while maintaining backward compatibility with v0.2.

Highlights

  • Lambda expressions — anonymous functions as first-class values
    • Syntax: fn (params) { body }, fn :: return_type (params) { body } or fn (params) -> return_type { body }
    • Enable functional patterns: map, filter, reduce with closures
  • Typed lambda returns — optional type annotations for lambda functions
    • Similar to named functions: fn :: int (x) { return x * 2 }
    • Support for typed arrays: fn :: <int>[] (n) { ... }
    • Runtime type checking on return values
  • Closures — lambdas capture their defining environment
    • Variables from enclosing scopes are accessible within lambda body
    • Enables currying and higher-order function composition
  • First-class functions — lambdas and named functions can be assigned to variables
    • Pass functions as arguments to other functions
    • Store functions in data structures
  • Expression-based lambda invocation — call lambdas inline
    • Direct syntax: (lambda_expr)(args) for immediate application
  • Improved module loading — robust thd.so search paths
    • Automatic detection from standard installation locations
    • Fallback to package.loadlib with full path resolution
    • Clearer error diagnostics for missing extensions
  • Performance optimizations — reduced interpretation overhead
    • Reduced interpreter overhead in module loading, scoping and expression handling
    • Efficient scoping and variable resolution
  • Runtime type safety for lambdas — enforce type contracts
    • Parameter and return type validation where specified
    • Safe interop with typed code
  • Pattern matching — classic match / case {} for simple value matching, and the ccl-style (complex conditional logic) match { pattern => expr } for ranges, alternatives, captures and guards
  • Portable binaries with -p — package source into a .ptf binary with embedded resolved AST for faster startup across tif and lusotif runtimes

Examples

Simple lambda:

letdouble=fn (x){returnx*2}print(double(5))// 10

Typed lambda:

letsquare=fn :: int(int:: x){returnx*x}letresult=square(7)// type-safe result

Explicit return lambda:

letincrement=fn (int:: x)-> void{returnn+1}print(increment(5))

Higher-order function:

letadder=fn :: int(int:: a){returnfn :: int(int:: b){returna+b}}letadd5=adder(5)print(add5(3))// 8

Passing lambdas as arguments (pattern for functional utilities):

letapply=fn :: int(int:: x,fn_obj){returnfn_obj(x)}letdouble=fn (int:: n){returnn*2}print(apply(5,double))// 10

Optional typed function parameters:

fn range(?int:: from,int:: to)-> <int>[]{// if omitted, `from` receives the type default (`0` for int)
}

Typed string interpolation:

letraw="12"print("value as int: #(int){raw}")print("classic mode: #{raw}")

Previous release: v0.2-praven

This release introduces major improvements over the previous v0.1.x-cute, expanding the language with modules, arrays, pointers, structured data, and a full standard library.

Highlights

  • Module systemrequire "file" with caching and circular import detection
  • Global modulesrequire <mod> loads C-based stdlib modules
  • pub keyword — explicit export control for modules
  • Typed arrays<int>, <string>, etc., with bounds checking and enforced element types
  • Array utilities — built-ins such as new_array, len, push, pop
  • for loops — range-based (1..n) and array iteration
  • String interpolation"hello #{name}" with inline expressions
  • Pointers&x, *px, powered by THD with real memory backing
  • Structs — user-defined data types with named fields for structured data modeling
  • Typed function returns — including typed array returns (fn <int>[]::name())
  • Uninitialized typed variables — safe defaults
  • Improved error messages — include file, line and column with precise diagnostics
  • CLI improvements — module search path via -I
  • Standard library — modular C-based stdlib (std, stdr/math, stdr/io, stdr/os)
  • Pattern matching — classic case matching and ccl-style => matching
  • File extension.tif

THD — Tif Heap Dialoguer

THD is the low-level C extension that gives tif real memory backing for pointer operations. It is compiled as a shared library (thd.so) and loaded by the interpreter at runtime via Lua's C module system.

Architecture

tif source code
 │
 ▼
 interpreter.lua
 │ &x — allocates a cell on the THD heap
 │ *px — reads from the C cell
 │ *px = v — writes to the C cell AND syncs back to the Lua variable
 ▼
 thd.so (Lua C extension)
 │
 ▼
 C heap arena
 ┌─────────────────────────────────────┐
 │ 65 536 cells ×ばつ tagged value │
 │ 16 MB raw byte pool │
 └─────────────────────────────────────┘

How it works

The THD manages two memory regions in a single C process:

Cell arena — an array of 65 536 fixed-size cells. Each cell holds a tagged union:

Tag C type Tif type
TAG_INT int64_t int
TAG_FLOAT double float
TAG_BOOL int32_t bool
TAG_BYTES uint8_t * raw buffer

Every cell carries a magic stamp (0x54494621"TIF!") validated on every read and write. A freed cell has its magic zeroed, so use-after-free and double-free are caught immediately.

Byte pool — a 16 MB bump allocator for TAG_BYTES cells. Raw byte buffers are allocated by advancing a single offset pointer. There is intentionally no free for individual byte allocations — the pool is reset when the interpreter resets.

Pointer lifecycle

let int :: x = 5
let *int :: px = &x
  1. &x calls alloc_int(5) on the THD — allocates a cell, returns its slot index as a handle
  2. The interpreter creates a pointer object { handle=42, base_type="int", env=<scope>, name="x" }
  3. The scope reference (env + name) is stored alongside the handle for write-back sync
*px = 10
  1. mem().write(42, 10) — writes 10 into the C cell at slot 42
  2. scope["x"] = 10 — syncs the value back to the original tif variable

This bidirectional sync ensures that both print(*px) (reads from C) and print(x) (reads from Lua scope) always agree.

Safety

  • Invalid handle — every operation validates the magic stamp and checks the slot index bounds before accessing the cell
  • Double-free — freeing a cell zeroes the magic; a subsequent access on the same handle raises an error
  • Type enforcementwrite checks that the value type matches the cell tag
  • Byte offset boundsread_byte and write_byte validate the offset against the cell size

Exposed API

THD exposes the following functions to Lua (used internally by the interpreter):

Function Description
alloc_int(v) allocate an int64 cell, returns handle
alloc_float(v) allocate a double cell, returns handle
alloc_bool(v) allocate a bool cell, returns handle
alloc_bytes(n) allocate n bytes in the pool, returns handle
free(handle) mark a cell as free
read(handle) read the value from a cell
write(handle, v) write a value to a cell (type-checked)
read_byte(handle, i) read byte at 1-based offset i from a bytes cell
write_byte(handle, i, b) write byte at 1-based offset i
tag(handle) returns the type tag string
size(handle) returns byte count for bytes cells
addr(handle) returns the real RAM address of the cell as an integer
stats() returns { allocs, frees, live, bytes_used, bytes_total }

Compiling THD

gcc -shared -fPIC -O2 -o thd.so thd.c $(pkg-config --cflags --libs lua5.4)

Place thd.so next to interpreter.lua (for source runs) or in /usr/local/lib/tif/ (for installed runs). The installer handles this automatically.

For LuaJIT builds, compile against LuaJIT instead of lua5.4:

gcc -shared -fPIC -O2 -o thd.so thd.c -I/usr/include/luajit-2.1 -L/usr/lib/x86_64-linux-gnu -lluajit-5.1

Standard library

The tif stdlib is a set of C shared libraries installed under /usr/local/lib/tif/stdlib/. Each module is a standard Lua C extension loaded transparently by the interpreter when require <mod> is used.

Installed layout

/usr/local/lib/tif/
├── *.lua
├── *.luac (Lua 5.4 installs)
├── thd.so
└── stdlib/
 ├── std.so ← require <std>
 └── stdr/
 ├── math.so ← require <stdr/math>
 ├── io.so ← require <stdr/io>
 └── os.so ← require <stdr/os>

require <std> — core utilities

Function Description
std.println(v) print value followed by newline
std.print(v) print value without newline
std.eprintln(v) print to stderr with newline
std.eprint(v) print to stderr without newline
std.tostring(v) convert any value to string
std.toint(v) convert string or float to int
std.tofloat(v) convert string or int to float
std.tobool(v) convert value to bool
std.type(v) returns type name as string
std.assert(cond, msg) abort with message if cond is false
std.panic(msg) unconditional abort
std.input(prompt?) print an optional prompt and read one line from stdin
std.is_nil(v) returns true if v is nil

require <stdr/math> — mathematics

Function Description
math.sqrt(x) square root
math.pow(x, y) x to the power of y
math.abs(x) absolute value
math.floor(x) round down
math.ceil(x) round up
math.round(x) round to nearest
math.min(...) minimum of arguments
math.max(...) maximum of arguments
math.clamp(v, lo, hi) clamp value to range
math.sin(x) sine (radians)
math.cos(x) cosine (radians)
math.tan(x) tangent (radians)
math.asin(x) arc sine
math.acos(x) arc cosine
math.atan(x) arc tangent
math.deg(x) radians to degrees
math.rad(x) degrees to radians
math.log(x) natural logarithm
math.log(x, base) logarithm with base
math.exp(x) e^x
math.sign(x) -1, 0 or 1
math.factorial(n) n! (max 20)
math.gcd(a, b) greatest common divisor
math.is_int(x) true if x has no fractional part
math.is_nan(x) true if x is NaN
math.is_inf(x) true if x is infinity
math.PI π
math.E e
math.INF positive infinity

require <stdr/io> — input and file I/O

Function Description
io.println(s) print string + newline to stdout
io.print(s) print string to stdout
io.eprint(s) print string to stderr
io.eprintln(s) print string + newline to stderr
io.flush() flush stdout
io.read_line() read a line from stdin
io.read_int() read an integer from stdin
io.read_float() read a float from stdin
io.read_char() read a single character from stdin
io.open(path, mode) open a file, returns file handle
io.close(f) close a file handle
io.read_line_file(f) read a line from an open file
io.read_all(f) read entire file contents
io.write(f, s) write string to file
io.writeln(f, s) write string + newline to file
io.seek(f, off, whence) seek within file
io.tell(f) current file position
io.eof(f) true if at end of file
io.flush_file(f) flush an open file handle
io.exists(path) true if file exists
io.size(path) file size in bytes

require <stdr/os> — operating system

Function Description
os.time() current Unix timestamp
os.clock() CPU time used by process
os.time_str(t) format timestamp as YYYY-MM-DD HH:MM:SS
os.time_parts(t) table with year, month, day, hour, minute, second
os.getenv(name) get environment variable
os.setenv(name, val) set environment variable
os.exit(code) exit with status code
os.args() returns script arguments as a 1-based tif array
os.cwd() current working directory
os.mkdir(path) create directory (with parents)
os.remove(path) delete file or empty directory
os.rename(from, to) rename or move a file
os.platform() "linux", "macos" or "windows"

Features

  • Variable declaration with let, typed or untyped
  • Arithmetic, comparison and logical operators
  • if, else, else if
  • while and for loops
  • Functions with type checking
  • Lambda expressions and closures (first-class functions)
  • Higher-order functions (functions as values)
  • Arrays with enforced element types
  • Structs for structured data
  • Pattern matching (match) with classic case and ccl-style => forms
  • String interpolation
  • Module system and imports
  • Pointers and manual memory interaction via THD
  • Scoped environments
  • Stack overflow protection
  • Basic UTF-8 tolerance in source scanning
  • Ahead-of-time compilation to native binaries (-o flag)
  • Lusotif syntax support (--luso flag)

Examples

Structs:

require<std>struct Point{float:: xfloat:: y}letp=Point{x=1.0,y=2.0}std.println(p.x)

Pointers:

require<std>fn double(*int:: n)-> void{*n=*n*2}letint:: x=5double(&x)std.println(x)

Arrays:

require<std>let<int>nums[3]=[1,2,3]forninnums{std.println(n)}

Countdown loop:

letx=5whilex>0{print(x)x=x-1}

Typed function with return type checking:

require<std>fn sum(int:: a,int:: b)-> int{returna+b}letint::x=10x=sum(x,5)std.println(x)

Boolean logic:

require<std>leta=trueletb=falseifaandnotb{std.println("ok")}

Recursive function:

require<std>fn int:: factorial(int:: n){ifn==0{return1}returnn*factorial(n-1)}std.println(factorial(6))

Lambda expressions and closures:

require<std>letdouble=fn (x){returnx*2}std.println(double(5))// 10
letmake_multiplier=fn :: int(int:: factor){returnfn :: int(int:: x){returnx*factor}}lettimes3=make_multiplier(3)std.println(times3(7))// 21

Optional parameters and typed interpolation together:

require<std>fn describe(?int:: start,int:: stop)-> string{return"range from #(int){start} to #(int){stop}"}std.println(describe(5,9))std.println(describe(9))

Binary Compilation

tif supports two binary-style output modes:

  • -o builds a standalone native ELF binary for the current platform
  • -p packages the source into a portable .ptf tif binary that can be shared across platforms and run by compatible tif and lusotif runtimes

Both modes now bundle imported .tif modules alongside the main program, so local and -I-resolved source modules can run without shipping those extra source files separately.

The current -o path no longer emits a Lua launcher: it generates GNU assembler for Linux x86-64 and links it into a self-contained executable with no Lua runtime dependency.

tif -o myprogram myprogram.tif
./myprogram

Portable package example:

tif -p myprogram myprogram.tif
tif myprogram.ptf
lusotif myprogram.ptf

The current native backend targets Linux x86-64 and focuses on a practical subset of the language:

  • require <std>
  • let, assignment, if, while
  • top-level functions, recursion, and optional typed parameters
  • print, std.print(...), std.println(...)
  • integer arithmetic and boolean comparisons
  • string literals in print calls

When the program imports extra .tif source modules, tif -o now falls back to a native launcher build that embeds the main source plus the imported module graph.

Features such as arrays, pointers, structs, modules beyond <std>, lambdas, matches, and runtime-loaded .so modules are not yet supported by the native backend. In those cases, tif -o fails with a compile-time diagnostic instead of silently producing a launcher binary.

Example native-friendly program:

require<std>fn factorial(int:: n)-> int{ifn==0{return1}returnn*factorial(n-1)}std.println(factorial(6))

There is also a ready-to-compile example in examples/native_aot.tif.

For Lusotif syntax, use:

tif --luso -o myprogram myprogram.tif

Lusotif Support

Lusotif is a variant of tif with Portuguese keywords, error messages, and standard library. The --luso flag accepts Lusotif syntax and translates it to tif internally before execution or compilation. Use --luso-dump to output the translated tif code to stdout.


Installation

tif is distributed as a native binary. By default, the build process targets LuaJIT, compiles the Lua sources to LuaJIT bytecode with luajit -b, and links them into a standalone C executable through the Lua C API. Lua 5.4 is still supported as an alternative compatibility/runtime path: in that mode tif compiles the Lua sources with luac and installs the corresponding bytecode files, because LuaJIT and Lua 5.4 bytecode are not compatible with each other.

Interactive installer

The recommended way to install, update, or uninstall tif is through the interactive menu:

git clone https://codeberg.org/grassleaff/tif.git
cd tif
sudo bash tifmenu.sh

The installer first lets you choose the embedded runtime:

  • LuaJIT — default and recommended optimized runtime
  • Lua 5.4 — compatibility/runtime alternative

It then presents a navigable menu (arrow keys + Enter) with three options:

  • install — fetches available versions from Codeberg, lets you pick one, and builds and installs it
  • uninstall — removes the binary and all bytecode files from the system
  • update / downgrade — lists all available versions so you can switch to any of them

Dependencies for Lua 5.4

Dependency Purpose
lua5.4 Runtime (luac + interpreter)
liblua5.4-dev C headers for linking
gcc Compiling the C launcher
pkg-config Resolving Lua compile/link flags
git Cloning the selected version

On Debian/Ubuntu:

sudo apt install lua5.4 liblua5.4-dev gcc pkg-config git

On Arch Linux:

sudo pacman -S lua gcc pkgconf git

Dependencies for LuaJIT

Dependency Purpose
luajit Runtime
libluajit-5.1-dev C headers for linking
gcc Compiling the C launcher
git Cloning the selected version

On Debian/Ubuntu:

sudo apt install luajit libluajit-5.1-dev gcc git

Usage

tif file.tif
tif file.tif hello world
tif -o myprogram file.tif
./myprogram
tif --help
tif --version

Lusotif translation mode (accepts Portuguese-syntax variant):

tif --luso arquivo.tif
tif --luso-dump arquivo.tif > traduzido.tif

Running from source

git clone https://codeberg.org/grassleaff/tif.git
cd tif
make all
luajit src/main.lua file.tif

Or, with Lua 5.4:

make all RUNTIME=lua
lua5.4 src/main.lua file.tif

Writing C libraries for tif

Any C shared library that exposes a luaopen_<name> function can be loaded by tif as a module. The interpreter wraps the returned Lua table into a tif namespace automatically — no changes to the interpreter are needed for third-party libraries.

Minimal example

// mylib.c
#include <lua5.4/lua.h>#include <lua5.4/lualib.h>#include <lua5.4/lauxlib.h>
static int l_greet(lua_State *L) {
 const char *name = luaL_checkstring(L, 1);
 lua_pushfstring(L, "hello, %s!", name);
 return 1;
}
static const luaL_Reg mylib[] = {
 { "greet", l_greet },
 { NULL, NULL }
};
int luaopen_mylib(lua_State *L) {
 luaL_newlib(L, mylib);
 return 1;
}
gcc -shared -fPIC -O2 -o mylib.so mylib.c $(pkg-config --cflags --libs lua5.4)
// place mylib.so next to your .tif file, or in a -I path
require"mylib"// local import (mylib.so next to source file)
// or:
tif-Ilibs/main.tif// global import from libs/
require<std>require"mylib"std.println(mylib.greet("tif"))// hello, tif!

Rules for tif-compatible C libraries

  • The entry point must be named luaopen_<stem> where <stem> matches the .so filename without the extension. For mylib.so the function is luaopen_mylib.
  • Return a single Lua table from luaopen_*. All entries in the table become callable members of the tif namespace.
  • Functions receive arguments as Lua values on the stack. Use luaL_checkstring, luaL_checknumber, luaL_checkinteger etc. to validate them.
  • Return values are pushed onto the Lua stack. Returning 1 means one return value.
  • tif arrays are Lua tables with __is_array = true and an integer field n holding the element count. Elements are 1-based.
  • tif pointers are Lua tables with __is_ptr = true, a handle integer, and base_type string.
  • Errors should be raised with luaL_error(L, "message") — the interpreter catches them and displays them with the source line.

Distributing a library

A third-party library for tif is just a .so file. Users install it by:

  1. Placing it anywhere on the filesystem
  2. Passing the directory with -I when running tif:
tif -I./libs main.tif

Or by dropping it into /usr/local/lib/tif/ for system-wide availability without -I.


License

MIT License — Copyright (c) 2026 grassleaff. All Rights Reserved.