Tawla 1.3

Tawla is a small language that
compiles to real machine code.

A statically-typed, object-oriented language in the spirit of C# — classes, interfaces, generics, garbage collection — turned straight into native code by an LLVM JIT and run on the spot. Source files end in .twl.

JIT-compiled

LLVM turns your code into machine code and runs it immediately — no separate build step.

Truly OOP

Classes, inheritance, interfaces, abstract classes, generics, and encapsulation.

Batteries included

A standard library — collections, JSON, an HTTP server and client — much of it written in Tawla itself.

Safe by default

Static type checking, array bounds checks, and clear null-reference errors instead of crashes.

Install

You need Python 3.11+. Tawla ships on PyPI and pulls in everything it needs (including LLVM, via llvmlite). Works on Windows, macOS, and Linux.

terminal
$ pip install tawla

That gives you the tawlac command (and python -m tawla):

terminal
$ tawlac run app.twl     # compile and run a file
$ tawlac new myapp       # scaffold a new project
$ tawlac version         # print the version
$ tawlac help            # or: tawlac help run

No Python? Download a standalone binary

Every release also ships a self-contained tawlac that bundles everything it needs — no Python required. Download the build for your OS from the Releases page and put it on your PATH:

  • Windows: download tawlac-windows.exe, rename it to tawlac.exe, drop it in a folder like C:\tools\tawla, and add that folder to your Path environment variable.
  • macOS / Linux: download the binary, then mv tawlac-<os> tawlac && chmod +x tawlac && sudo mv tawlac /usr/local/bin/. On macOS, first run may need approval under Privacy & Security (unsigned binary).

After that, tawlac run app.twl works from any terminal — same command as the pip version.

Hello, Tawla

A program is a set of classes. The runtime instantiates Main and calls its main method — or you can write loose top-level statements for quick scripts.

hello.twl
class Main {
    void main() {
        print("Hello, Tawla!");
    }
}
terminal
$ tawlac run hello.twl
Hello, Tawla!
Tip: for a one-off, you can skip the class entirely — top-level statements run as the program body: print(1 + 2);

Basics & types

Tawla has int, 64-bit float (alias double), bool, and string. Use var to infer the type from the initializer, or declare without one to get a default (0, false, null). Comments are // like this.

basics.twl
int count = 67;
float pi = 3.14159;       // float and double are the same 64-bit type
bool ready = true;
string name = "Ahmed";
var inferred = 42;        // an int

int x;                    // declared without a value -> 0
bool flag;                // -> false

print(count);             // 67
print(name + "!");        // Ahmed!
print(7 / 2);             // 3   (integer division)
print(7.0 / 2);           // 3.5 (a float makes it floating point)

Operators

The usual arithmetic (+ - * /) and comparisons (== != < <= > >=), boolean logic with short-circuit evaluation, and a conditional (ternary) expression.

operators.twl
bool ok = age >= 18 && verified;   // && and  || or  ! not
bool any = retry || fallback;
if (!found) { print("missing"); }

// short-circuit: the right side is skipped when the left decides it,
// so this never calls a method on a null reference:
if (user != null && user.active()) { print("hi"); }

// ternary: cond ? a : b  (only the taken branch runs)
int max = a > b ? a : b;
string label = n == 0 ? "zero" : n < 10 ? "small" : "big";

Increment and decrement: i++, ++i, i--, and --i are shorthand for i = i + 1 / i = i - 1. They are statements (not expressions), so they stand on their own line or drive a for loop's step; they work on variables, object fields, and array elements.

Control flow

if/else, while, and a C-style for loop (its loop variable is scoped to the loop).

control.twl
int sum = 0;
for (int i = 1; i <= 10; i++) {
    sum = sum + i;
}
print(sum);               // 55

int n = 8;
while (n > 1) {
    if (n == 2 * (n / 2)) { n = n / 2; } else { n = 3 * n + 1; }
}

Functions

Top-level functions take typed parameters and return a type (or void). Recursion works fine.

functions.twl
int factorial(int n) {
    if (n <= 1) { return 1; }
    return n * factorial(n - 1);
}

void greet(string who) {
    print("Hello, " + who);
}

print(factorial(5));      // 120
greet("world");

Classes

Classes have fields, methods, a constructor, and this. Objects live on the heap and are created with new.

point.twl
class Point {
    private int x;
    private int y;

    public Point(int px, int py) {
        this.x = px;
        this.y = py;
    }

    public int sum() { return this.x + this.y; }
}

class Main {
    void main() {
        Point p = new Point(3, 4);
        print(p.sum());       // 7
    }
}

Encapsulation

Members are private by default (visible only inside the class). Mark them public to expose them, or protected to share with subclasses. Constructors are public by default. Access is checked at compile time.

ModifierVisible from
publicanywhere
protectedthe class and its subclasses
privatethe declaring class only
counter.twl
class Counter {
    private int count;                          // hidden

    public Counter() { this.count = 0; }
    public void bump() { this.count = this.count + 1; }
    public int value() { return this.count; }
}

class Main {
    void main() {
        Counter c = new Counter();
        c.bump(); c.bump();
        print(c.value());     // 2
        // c.count here would be a compile error: count is private
    }
}

Inheritance

Use : to extend a class. Methods are virtual — the right one is chosen at runtime from the actual object type (vtables). Call the base constructor with super(...).

animals.twl
class Animal {
    protected int legs;
    public Animal(int n) { this.legs = n; }
    public int legCount() { return this.legs; }
    public string speak() { return "..."; }
}

class Dog : Animal {
    public Dog() { super(4); }
    public string speak() { return "woof"; }   // override
}

class Main {
    void main() {
        Animal a = new Dog();      // static type Animal, runtime Dog
        print(a.speak());          // woof  (dynamic dispatch)
        print(a.legCount());       // 4
    }
}

Interfaces

An interface is a set of method signatures any class can implement — even classes that share no common parent. List them after the :, alongside an optional base class.

shapes.twl
interface Shape {
    int area();
}

class Square : Shape {
    private int side;
    public Square(int s) { this.side = s; }
    public int area() { return this.side * this.side; }
}

int areaOf(Shape s) { return s.area(); }   // works for any Shape

class Main {
    void main() {
        print(areaOf(new Square(5)));      // 25
    }
}
A class can extend one base class and implement any number of interfaces: class Dog : Animal, Named, Greeter { ... }. Methods that implement an interface must be public.

Abstract classes

Mark a class abstract and leave some methods without a body for subclasses to fill in. You can't instantiate an abstract class directly.

abstract.twl
abstract class Animal {
    public abstract int speak();          // no body
    public int legs() { return 4; }       // concrete, inherited
}

class Cat : Animal {
    public int speak() { return 2; }      // must implement
}

Generics

Type-parameterize a class with <T>. Generics are monomorphized: each instantiation (e.g. Box<int>) is stamped out as a concrete class at compile time.

box.twl
class Box<T> {
    private T value;
    public Box(T v) { this.value = v; }
    public T get() { return this.value; }
}

class Main {
    void main() {
        Box<int> n = new Box<int>(42);
        print(n.get());                        // 42

        var s = new Box<string>("boxed");      // var also works
        print(s.get());                        // boxed
    }
}
First-cut generics cover classes with concrete type arguments. Nested generics like Map<string, List<int>> aren't supported yet.

Null & defaults

Reference types (objects, strings, arrays) can be null; value types (int, float, bool) cannot. Using a null — calling a method, reading a field, indexing — gives a clean null reference error instead of a crash.

nullable.twl
User u;                    // a declaration with no value -> null
if (u == null) {
    print("no user yet");
}

string name = u != null ? u.name() : "guest";   // safe with short-circuit/ternary

Arrays

Fixed-size arrays with new T[n], indexed with a[i] (bounds-checked), and a.length. Slots start zeroed.

arrays.twl
int[] a = new int[5];
int i = 0;
while (i < a.length) {
    a[i] = i * i;
    i = i + 1;
}
print(a[4]);              // 16
// a[9] would abort with "array index out of bounds"
For a growable list, use List<T> from Collections.

Strings

String literals with escapes, s.length, ==, and + to join. Plus a set of builtins for working with text.

strings.twl
string s = "hello";
print(s.length);              // 5
print(charAt(s, 0));          // 104  (the character code of 'h')
print(substring(s, 1, 4));    // ell

int n = toInt("40") + 2;
print(toString(n));           // 42
print(toFloat("3.5") * 2.0);  // 7

IO

The standard library lives in modules you bring in with import "Name.twl";. They ship with the compiler.

IO.twl reads input and writes prompts.

FunctionDescription
readLine()read a line of input as a string
readInt()read a line and parse an int
readFloat()read a line and parse a float
write(s)print a string with no trailing newline (for prompts)
greet.twl
import "IO.twl";

class Main {
    void main() {
        write("What's your name? ");
        string name = readLine();
        write("How old are you? ");
        int age = readInt();
        print("Hi " + name);
        print(age + 1);          // your age next year
    }
}

Collections

Collections.twl gives you a growable List<T> and a key/value Map<K,V> — written in Tawla on top of arrays and generics.

collections.twl
import "Collections.twl";

class Main {
    void main() {
        List<int> xs = new List<int>();
        xs.add(10); xs.add(20); xs.add(30);
        print(xs.size());          // 3
        print(xs.get(1));          // 20
        xs.set(1, 99);

        Map<string, int> ages = new Map<string, int>();
        ages.put("Ahmed", 36);
        print(ages.has("Ahmed"));  // true
        print(ages.get("Ahmed"));  // 36
    }
}
List<T>Map<K,V>
add(x), get(i), set(i, x), size()put(k, v), get(k), has(k), size(), keys()
Map.get returns null for a missing object value, or the zero value for a number — pair it with has(key).

JSON

Json.twl — a JSON parser and serializer written entirely in Tawla. Parse with parseJson and navigate the result; build with jsonObject()/jsonArray() and serialize with toString().

read.twl
import "Json.twl";

Json d = parseJson("{\"name\": \"Ahmed\", \"tags\": [\"twl\", \"py\"]}");
print(d.get("name").asString());        // Ahmed
print(d.get("tags").size());            // 2
print(d.get("tags").at(0).asString());  // twl
if (d.get("admin").isNull()) { print("no admin field"); }
write.twl
import "Json.twl";

Json out = jsonObject();
out.setString("status", "ok");
out.setInt("count", 3);
print(out.toString());                  // {"status":"ok","count":3}

Read with get/at/size and asInt/asFloat/asBool/asString (plus isNull/isArray/isObject). Build with setString/setInt/setFloat/setBool/set and pushString/push…/push.

HTTP server

Http.twl gives you a single-threaded HTTP server with Express-style routing. Implement the Handler interface, register routes on a Router, and serve it.

server.twl
import "Http.twl";

class Health : Handler {
    public void handle(Request req) { req.respond(200, "ok"); }
}
class Echo : Handler {
    public void handle(Request req) { req.respond(200, req.body()); }
}

class Main {
    void main() {
        Router router = new Router();
        router.get("/health", new Health());
        router.post("/echo", new Echo());

        print("listening on http://localhost:8080");
        new Server(8080).serve(router);
    }
}

A Request exposes method(), path(), body(), respond(status, body), and respondJson(status, body) (which sends application/json). You can also drive the loop yourself with server.accept().

Routes capture path params with :name. Inside a handler, req.param("id"), req.query("page"), and req.header("Authorization") read the path param, query string, and request header — each returns null when absent.

routes.twl
class GetUser : Handler {
    public void handle(Request req) {
        req.respond(200, "user " + req.param("id"));
    }
}
// ...
router.get("/users/:id", new GetUser());

HTTP client (fetch)

The client side, also in Http.twl. fetch(url) does a GET; httpRequest(method, url, body) does anything. Both return a Response with status() and body(). Network failures come back as status() == 0.

client.twl
import "Http.twl";
import "Json.twl";

class Main {
    void main() {
        Response r = fetch("http://localhost:8080/health");
        if (r.status() == 200) {
            print(r.body());
        }

        Json payload = jsonObject();
        payload.setString("name", "Ahmed");
        Response created = httpRequest("POST", "http://localhost:8080/users", payload.toString());
        print(created.status());
    }
}
Read a request body, parse its JSON, call another service, and respond with JSON — the whole loop, in a statically-typed JIT-compiled language whose parser and router are written in Tawla itself.

SQLite

import "Sql.twl"; gives you an embedded SQL database. Open a Db, run statements, and read query results through a Rows cursor. Parameters bind by index with ? placeholders (so there's no SQL injection), and SQL errors are thrown — catch them with fuck_around / find_out.

db.twl
Db db = new Db("app.db");
db.exec("CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT, age INT)");

Stmt ins = db.prepare("INSERT INTO users(name, age) VALUES (?, ?)");
ins.bindString(0, "Ada"); ins.bindInt(1, 36); ins.exec();

Stmt q = db.prepare("SELECT name FROM users WHERE age > ?");
q.bindInt(0, 18);
Rows r = q.query();
while (r.next()) { print(r.getString("name")); }

Backend essentials

Three small modules cover what services need day to day. import "Sys.twl"; gives getenv, now() / nowMillis() / sleepMillis(ms), and uuid(). import "Fs.twl"; gives readFile / writeFile / appendFile (which throw on failure) and exists. import "Crypto.twl"; gives sha256(s) and hmacSha256(key, message) for signing.

essentials.twl
string id = uuid();
string sig = hmacSha256("secret-key", id);
writeFile("audit.log", id + " " + sig + "\n");
string mode = getenv("APP_MODE");
if (mode == null) { mode = "dev"; }

Interpolation & JSON

String literals interpolate ${expr} (a bare $ is literal). And every class has an auto-generated toJson() that serializes its fields — primitives, nested objects, and arrays — so returning JSON is one call.

ergonomics.twl
print("hi ${user.name}, ${n + 1} items");

// every class can serialize itself
req.respondJson(200, user.toJson());   // {"id":7,"name":"Ada",...}

Builtins

Functions available everywhere without an import.

Output

print(x)print an int, float, bool, or string, with a newline
panic(s)print a message and abort, for unrecoverable errors

Math

sqrt(x), pow(x, y)square root, power (float)
abs(x)absolute value (int or float, returns the same type)
min(a, b), max(a, b)smaller / larger
floor(x), ceil(x)round down / up

Strings

charAt(s, i)character code at index i
substring(s, a, b)the slice [a, b)
toInt(s), toFloat(s)parse a numeric string
toString(n)number to string (int or float)

Garbage collection

Objects, arrays, and joined strings live on a garbage-collected heap, so you never free memory by hand. Collection is conservative mark-and-sweep and runs when you ask for it.

collect()run a collection pass now
__live()how many heap objects are currently alive (handy for tests)

Exceptions

Wrap risky code in fuck_around (try) and recover in find_out (catch). The caught value e is the error message — a string. Raise your own with throw "msg";, and the built-in errors (panic, null dereference, array-out-of-bounds) are catchable too. A bare find_out { } ignores the message. An uncaught error prints its message and stops the program, exactly as before.

errors.twl
fuck_around {
    throw "something broke";
} find_out (e) {
    print(e);                 // something broke
}

fuck_around {
    int[] a = new int[2];
    print(a[9]);              // out of bounds is catchable
} find_out (e) {
    print("recovered");
}

Projects & CLI

For more than a single file, scaffold a project (à la cargo). tawlac new myapp creates a new folder with a Tawla.toml manifest and src/main.twl, while tawlac init scaffolds the same layout in the current directory. Either way, tawlac run with no file runs the project's entry point.

terminal
$ tawlac new myapp     # create ./myapp with a starter project
$ cd myapp
$ tawlac run           # runs the manifest's entry point

$ mkdir hello && cd hello
$ tawlac init          # scaffold a project in the current directory
$ tawlac run

The full command set:

CommandWhat it does
tawlac run [file]JIT-compile and run a .twl file, or the project entry point if no file is given.
tawlac new <name>Create a new project directory with a manifest and src/main.twl.
tawlac initScaffold a project in the current directory.
tawlac versionPrint the installed tawlac version.
tawlac help [command]Show general help, or help for a specific command.

Split code across files with import "other.twl"; — the path is relative to the importing file, and the .twl is optional.