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.
$ pip install tawla
That gives you the tawlac command (and python -m tawla):
$ 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 totawlac.exe, drop it in a folder likeC:\tools\tawla, and add that folder to yourPathenvironment 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.
class Main {
void main() {
print("Hello, Tawla!");
}
}
$ tawlac run hello.twl
Hello, Tawla!
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.
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.
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).
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.
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.
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.
| Modifier | Visible from |
|---|---|
public | anywhere |
protected | the class and its subclasses |
private | the declaring class only |
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(...).
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.
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
}
}
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 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.
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
}
}
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.
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.
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"
List<T> from Collections.Strings
String literals with escapes, s.length, ==, and + to join. Plus a set of builtins for working with text.
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.
| Function | Description |
|---|---|
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) |
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.
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().
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"); }
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.
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.
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.
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());
}
}
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 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.
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.
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.
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.
$ 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:
| Command | What 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 init | Scaffold a project in the current directory. |
tawlac version | Print 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.