Documentation

eTamil user manual

The complete guide to writing, running and deploying eTamil programs — from your first line to a database-backed HTTP service.

This manual covers everything needed to write and run eTamil programs. It assumes you can use a terminal, and nothing else.

If you only want a taste, the language tour is shorter. If you want the full keyword list, that is the keyword reference.

Conventions. Code is shown in Tamil script. Every keyword also has a romanized and often an English spelling, all interchangeable — see Three spellings. Where a feature needs a build flag or an environment variable, it says so.

Contents

Getting startedInstall · Run a program · Three spellings

The languageVariables and types · Numbers and money · Strings · Input and output · Operators · Conditionals and loops · Functions · Arrays and records · Iteration · Results · Modules · Type checking · Naming rules · Errors · Files

LibrariesStandard library · Accounting and GST

Building servicesDatabases · HTTP server · JSON · Authentication · Signing · Bytes and encoding · Outbound HTTP · Scheduled work

PracticalitiesEditor · Checking and testing · Environment variables · Next steps


1. Install {: #1-install}

The quickest route needs no Rust and no C toolchain.

Windows

Expand-Archive etamil-windows-x64.zip -DestinationPath .
.\etamil-windows-x64\install.ps1

Linux

tar -xzf etamil-linux-x64.tar.gz
./etamil-linux-x64/install.sh

macOS — pick arm64 for Apple Silicon or x64 for Intel; uname -m tells you which.

tar -xzf etamil-macos-arm64.tar.gz
./etamil-macos-arm64/install.sh
xattr -dr com.apple.quarantine ~/.local/lib/etamil

That last line is required, not optional: the packages are not notarized, so macOS quarantines anything downloaded through a browser and Gatekeeper refuses to run it. Clearing the flag once is enough.

Open a new terminal afterwards — the installer edits PATH, and a shell already running will not see it — then check:

etamil --version

Removal is just as simple: the Windows installer puts everything under %LOCALAPPDATA%\Programs\eTamil, the Linux one under ~/.local. Neither needs administrator rights.

To build from source instead — needed for the PostgreSQL and MySQL drivers, the LLVM backend, or work on the compiler itself — see the install page.

2. Run a program {: #2-run-a-program}

echo 'அச்சு "வணக்கம் உலகம்!";' > hello.etamil
etamil --vm hello.etamil

On Windows write the file as UTF-8:

'அச்சு "வணக்கம் உலகம்!";' | Out-File hello.etamil -Encoding UTF8
etamil --vm hello.etamil
Flag Effect
--vm Run on the bytecode VM. This is the default and what you want
--check Lex, parse and type check, then stop. Reports every error and never runs the program
--server Start the HTTP server with a worker pool
--async Concurrent server: async accept, handlers on the blocking pool. Ctrl-C stops it
--llvm LLVM backend. Needs --features llvm; Linux and macOS only
--port <PORT> Server port, default 8080
--host <HOST> Server host, default 127.0.0.1

File extensions are conventional, not enforced: .etamil and .qmz are both used in the repository.

Three spellings

Every keyword accepts Tamil script, a romanized spelling typeable on a plain keyboard, and where one exists an English alias. All produce the same token.

எண் வருவாய் = 100000;     // Tamil script
eN varuvAy = 100000;       // romanized (ezuqqu)
int income = 100000;       // English alias

The romanization is its own scheme, deliberately not ISO 15919: one ASCII character per Tamil letter, no diacritics, no digraphs. It keeps the three Tamil nasals distinct where English collapses them into one n is N, is n, is Z. The full letter table is on the language page.

Pick one spelling per program. Mixing them is legal but produces variables that look identical and are not — see Naming rules.


3. Variables and types {: #3-variables-and-types}

எண் age = 25;          // number
எண் price = 99.99;     // fixed-point decimal
எண் rate = 15%;        // percentage literal — exactly 0.15
சொல் name = "Ravi";    // string

Declaring a type is optional; assigning without one works and infers nothing. When you do declare, the compiler holds you to it, including on later assignments.

The types are எண் (number), சொல் (string), அணி (array), வரிசை (record), and the boolean values மெய் and பொய்.

4. Numbers and money {: #4-numbers-and-money}

Every number is a fixed-point decimal. There is no f64 anywhere in the arithmetic path, which is the single most important fact about this language for financial work.

அச்சு 0.1 + 0.2;      // 0.3        — not 0.30000000000000004
அச்சு 99.99 * 3;      // 299.97     — not 299.96999999999997
அச்சு 18%;            // 0.18       exactly

Equality is exact too. Two amounts a fraction of a paisa apart are never treated as equal — which matters the moment you reconcile anything.

Division keeps full precision rather than rounding at each step, because Indian tax computation rounds once at the end. Round explicitly when you need to:

அச்சு வட்டமிடு(மொத்தம் / 3, 2);   // round to 2 places
அச்சு தரை(12.7);                  // 12
அச்சு மேல்(12.1);                 // 13

எண் is currently the only numeric type — there is no separate integer, and no money type carrying a currency, so the checker cannot yet reject adding rupees to a count.

5. Strings {: #5-strings}

& concatenates. Strings are measured in written letters, not code points:

அச்சு நீளம்("வணக்கம்");     // 5, not 7

A Tamil letter is often a consonant plus a vowel sign or pulli. Counting code points would give the wrong answer and every string helper would inherit it.

Escapes are \n \t \r \" \\. An unknown escape keeps both characters rather than guessing.

6. Input and output {: #6-input-and-output}

எண் வருவாய்;
அச்சு "Enter income: ";
உள்ளிடு வருவாய்;
அச்சு "Income: " & வருவாய்;

உள்ளிடு always returns text. It converts when compared with or used as a number, so reading a figure and comparing it against a slab works without an explicit cast.

7. Operators {: #7-operators}

Kind Operators
Arithmetic + - * /, and unary -
Comparison == != < <= > >=
Logical மற்றும் / maRRum / _and, அல்லது / allaqu / _or, இல்லை / illY / _not
String &

Precedence, loosest first: orandnot → comparison → + -* /.

(வருவாய் > 800000 மற்றும் வயது < 60) எனில் {
    அச்சு "Taxable";
}

Both sides of a logical operator are always evaluated. There is no short-circuiting, so do not rely on the left side guarding the right.

8. Conditionals and loops {: #8-conditionals-and-loops}

(வருவாய் > 800000) எனில் {
    அச்சு "High";
}
இன்றேல் {
    அச்சு "Low";
}

எண் i = 0;
(i < 3) சுற்று {
    அச்சு i;
    i = i + 1;
}

The condition comes first and is parenthesised; எனில் follows it.

9. Functions {: #9-functions}

செயல் declares, திரும்பு returns. Parameters, local scope and recursion all work. Functions have no declared signatures yet, so parameter types are not checked.

செயல் வரிசை_மதிப்பு(உருப்படி) {
    திரும்பு உருப்படி.அளவு * உருப்படி.விலை;
}

அச்சு வரிசை_மதிப்பு({அளவு: 3, விலை: 54999});

There are no first-class functions — you cannot pass a function as a value yet, which is why the standard library has no map or filter.

10. Arrays and records {: #10-arrays-and-records}

Arrays use […] and are zero-indexed. Records use {…} with key: value.

விலைகள் = [100, 250, 375];
அச்சு விலைகள்[0];                 // 100

உருப்படி = {விவரம்: "மடிக்கணினி", விலை: 54999};
அச்சு உருப்படி.விலை;              // field access
அச்சு உருப்படி["விலை"];           // same thing, key computed at runtime

That second form matters: because a key can be computed, the JSON parser is written in eTamil rather than in the host.

11. Iteration {: #11-iteration}

ஒவ்வொரு … இல் walks arrays, records and strings.

ஒவ்வொரு உருப்படி இல் உருப்படிகள் {
    அச்சு உருப்படி.விவரம்;
}

12. Results — handling failure {: #12-results-handling-failure}

Failure is a value, not an exception. A fallible call returns சரி (ok) or தவறு (error), following Rust’s semantics.

Builtin Purpose
சரியா(r) true if the result succeeded
தவறா(r) true if it failed
மதிப்பு(r) unwrap the value
இயல்பு(r, d) unwrap, or d if it failed
? propagate a failure to the caller
ப = ஜேசான்_படி(request_body);
(தவறா(ப)) எனில் {
    பதில் 400, "malformed JSON";
}
தரவு = மதிப்பு(ப);

Because parsing returns a result, malformed input is something you handle rather than something that crashes the handler.

13. Modules {: #13-modules}

இறக்கு imports another file. Paths resolve beside the importing file first, then along ETAMIL_PATH, then next to the compiler binary.

இறக்கு "nUlakam/paNam.qmz";
இறக்கு "../../nUlakam/kaNiqam.qmz";

To use the standard library from anywhere:

export ETAMIL_PATH=/path/to/etamil_compiler

14. Type checking {: #14-type-checking}

--check parses and type checks without running:

etamil --check my_program.qmz

A declared type is enforced with a position:

✗ வரி 2, நெடுவரிசை 6: 'கொடியா' ஈர்ம (Irma, a boolean) என அறிவிக்கப்பட்டது,
  ஆனால் ஒரு அணி (an array) வழங்கப்பட்டது
  (line 2, column 6: 'கொடியா' is declared a boolean, but was given an array)

The checker is deliberately narrow. A number satisfies சொல், because every value renders as text and உள்ளிடு hands back text routinely compared with numbers. A call, an index and a field access make no claim at all, because functions have no signatures yet — silence there is the absence of a claim, not approval.

15. Naming rules

A name is stored exactly as you typed it, including when the word is also a keyword. வங்கி = 5 creates a variable called வங்கி; {வரி: 100} produces the field வரி.

The consequence: {வரி: 1} and {vari: 1} are different fields, and வருவாய் and varuvAy are different variables. Pick one spelling per program. A field name is data — what you typed — not a language construct.

Two positions keep the canonical English name, because what they name belongs to the host rather than to you: the database type in தளம்_இணை, and the HTTP method in வழி.

Hard-reserved words cannot be used as names at all — the type keywords and SQL clause keywords: எண், சொல், அணி, வரிசை, விதி, இடம், உள், வெளி, குழு, சேர்.

Financial keywords are not reserved: தொகை is a perfectly good variable name.

16. Reading error messages {: #16-reading-error-messages}

Every parse error carries a line and column, in Tamil and English:

✗ வரி 3, நெடுவரிசை 1: ';' எதிர்பார்க்கப்பட்டது, 'அச்சு' கிடைத்தது
  (line 3, column 1: expected ';', found 'அச்சு')

Columns count written letters, not bytes, so the position is the one you would point at on screen.

Anything unimplemented fails with an explicit message rather than quietly doing nothing — a silent no-op in a tax calculator is worse than an error.

17. Files and CSV {: #17-files-and-csv}

கோப்பு_திற "output.txt", "write";     // opening for write truncates
கோப்பு_எழுது "output.txt", "வணக்கம்";  // subsequent writes append
கோப்பு_மூடு "output.txt";

கோப்பு_படி "output.txt", data;
அச்சு data;

Counting CSV rows, excluding the header:

தரவுரை_படி "students.csv", total;
அச்சு total;

18. The standard library {: #18-the-standard-library}

nUlakam/ is written in eTamil, not Rust. If the standard library needed a systems language, the DSL would not be sufficient for what it exists to do.

Module Contents
col.qmz strings — துண்டு தேடு பிரி ஒன்றிணை ஒழுங்கு தொடங்குகிறதா முடிகிறதா இடமிருந்து_நிரப்பு
kaNiqam.qmz math — முழுமதிப்பு சிறியது பெரியது கூட்டு சராசரி சதவீதம்
aNi.qmz arrays — உள்ளதா இடம்_காண் தலைகீழ் வெட்டு புலம்_எடு காலியா
paNam.qmz money — ரூபாய் காசு_வடிவம் காசாக லட்சம் கோடி
jEcAZ.qmz JSON — ஜேசான்_ஆக்கு ஜேசான்_படி
kuRiyAkkam.qmz encoding — அறுபத்துநான்கு_ஆக்கு அறுபத்துநான்கு_படி பதினாறு_ஆக்கு பதினாறு_படி
இறக்கு "nUlakam/paNam.qmz";

அச்சு ரூபாய்(12345678.5);      // ₹1,23,45,678.50 — lakh and crore

What the host provides, because a language cannot express it: நீளம் இணை வகை · சரி தவறு சரியா தவறா மதிப்பு இயல்பு · வட்டமிடு தரை மேல் · சொல்லாக்கு எண்ணாக்கு · மேல்_எழுத்து கீழ்_எழுத்து · இன்று நாள்_வேறுபாடு நாள்_கூட்டு · கடவுச்சொல்_மறை கடவுச்சொல்_சரியா சீட்டு_ஆக்கு சீட்டு_சரிபார் · கையொப்பம் கையொப்பம்_சரியா · வலை_பெறு வலை_பதி வலை_அனுப்பு · பைட்டுகள் பைட்டுச்_சரம்.

Everything else is built from those.

19. Accounting and GST {: #19-accounting-and-gst}

The financial vocabulary is in the language — வரவு, பற்று, வரி, இருப்புநிலை, பேரேடு, மூலதனம் are keywords. The framework above them lives in nUlakam/kaNakkiyal/, also written in eTamil.

இறக்கு "nUlakam/kaNakkiyal/kaNakkukaL.qmz";
இறக்கு "nUlakam/kaNakkiyal/pErEtu.qmz";

பேரேடு = மதிப்பு(பதிவிடு(பேரேடு, பரிவர்த்தனை_ஆக்கு(
    "JV001", "2026-04-01", "தொடக்க மூலதனம்", [
        பற்று_வரிசை("1000", 500000),
        வரவு_வரிசை("3000", 500000)
    ])));

பதிவிடு refuses an unbalanced transaction and returns தவறு, so nothing unbalanced ever reaches the ledger. The ledger is a value: posting returns a new one rather than editing the old, so an entry cannot be changed after the fact.

GST splits into CGST and SGST within a state, or IGST across states. Full worked examples are on the finance page.


20. Databases {: #20-databases}

Queries are always parameterised. There is deliberately no way to splice a value into SQL text, so the injection class of bug is not available to write.

தளம்_இணை எசுகியூஎல்லைட், "kaNakku.db";
தளம்_வினா "SELECT peyar, qokY FROM pativukaL WHERE vakY = ?", ["வரவு"], வரவுகள்;

Rows come back as an array of records, so a result set iterates like any table.

Engine Status Notes
SQLite Working Built in. Decimals stored as text, so no precision is lost
PostgreSQL Working --features postgres. Money in native NUMERIC; placeholders are $1, $2
MySQL / MariaDB Live verified --features mysql. Placeholders are ?
MongoDB, Redis Not implemented Neither fits a SQL-shaped trait; both need a design first

Connections are reused from a process-wide idle cache rather than reopened per request; leases are exclusive, so a transaction keeps its connection to itself. ETAMIL_DB_IDLE caps how many stay warm.

PostgreSQL folds unquoted identifiers to lower case — write "qokY" to get that column name back as you spelled it.

21. The HTTP server {: #21-the-http-server}

etamil --server --port 8080 my_service.qmz
etamil --async  --port 8080 my_service.qmz

வழி declares a route, பதில் answers:

வழி பெறு, "/kaNakku/:id" {
    பதில் 200, "id=" & param_id & " vakY=" & query_params["vakY"];
}

வழி பதி, "/pativu" {
    பதில் 201, "got body: " & request_body;
}

வழி பெறு, "/aRikkY.csv" {
    பதில் 200, வரிசைகள், {"Content-Type": "text/csv"};
}
Variable Contents
request_method the HTTP method
request_path the path as requested
request_body the raw body
query_params query string, as a record
headers request headers, as a record
path_params matched :name segments, as a record
param_<name> each path parameter, bound directly

Response headers are an ordinary record; omit them and the server answers application/json.

--async changes only how connections are accepted — a connection costs a task rather than a thread. The VM stays synchronous, and handlers run on tokio’s blocking pool, which is what lets the blocking database drivers keep working.

22. JSON {: #22-json}

இறக்கு "nUlakam/jEcAZ.qmz";

ப = மதிப்பு(ஜேசான்_படி(request_body));
அச்சு ப["qokY"] + 1;                       // a number, not text
பதில் 200, ஜேசான்_ஆக்கு({நிலுவை: 1500});

Fields serialize in sorted order, which makes a response body stable enough to assert on in tests. \uXXXX escapes are not decoded. The ஜேசான்_உரை statement parses but the VM refuses it — build the body with ஜேசான்_ஆக்கு instead.

23. Authentication {: #23-authentication}

bcrypt and JWT live in the host, because hashing and HMAC need bytes and randomness the language cannot reach.

மறை = கடவுச்சொல்_மறை(கடவுச்சொல்);
(கடவுச்சொல்_சரியா(கடவுச்சொல், மறை)) எனில் {
    சீட்டு = சீட்டு_ஆக்கு({பயனர்: "ravi", பங்கு: "admin"});
    பதில் 200, சீட்டு;
}

Set ETAMIL_JWT_SECRET before starting the server. Everything above these four builtins — who a user is, which route needs which role — stays in eTamil.

24. Signing and webhooks {: #24-signing-and-webhooks}

கையொப்பம் produces an HMAC-SHA256 signature; கையொப்பம்_சரியா verifies one. The comparison is constant-time, so it does not leak the expected signature a byte at a time to anything measuring how long the check takes.

வழி பதி, "/webhook" {
    (கையொப்பம்_சரியா(request_body, headers["X-Signature"], ரகசியம்)) எனில் {
        பதில் 200, "ok";
    }
    பதில் 401, "bad signature";
}

25. Bytes, base64 and hex {: #25-bytes-base64-and-hex}

பைட்டுகள் turns text into bytes; பைட்டுச்_சரம் turns them back. A byte array is an ordinary array of numbers — deliberately not a new value type, so every array helper already works on it.

இறக்கு "nUlakam/kuRiyAkkam.qmz";

க = அறுபத்துநான்கு_ஆக்கு(பைட்டுகள்("வணக்கம்"));
அச்சு பைட்டுச்_சரம்(அறுபத்துநான்கு_படி(க));

26. Calling other services {: #26-calling-other-services}

வலை_பெறு, வலை_பதி and வலை_அனுப்பு make outbound requests, behind --features http-client, which is on by default. A non-2xx response is a result, not a failure — a 404 from a gateway is something you branch on.

ப = வலை_பெறு("https://api.example.in/rates");
(சரியா(ப)) எனில் {
    அச்சு மதிப்பு(ப).உடல்;
}

27. Scheduled work {: #27-scheduled-work}

இடைவெளி runs a block on a timer under either server:

இடைவெளி 3600 {
    அச்சு "hourly reconciliation";
}

The number is the gap between runs, not a fixed period. If a run overruns the interval, the next starts late rather than starting on top of the one still going — for a job posting ledger entries, that is the difference between late and wrong.


28. Editor support {: #28-editor-support}

The VS Code extension is in eTamil_Code/ in the compiler repository: highlighting for all 201 keywords in every spelling, completions for the 23 builtins and 122 nUlakam functions, and --check errors shown as you type.

Its grammar and completion data are generated from lexer.rs, and CI fails if they drift, so the editor cannot fall behind the compiler.

29. Checking and testing {: #29-checking-and-testing}

etamil --check my_program.qmz     # parse and type check, do not run

In the compiler repository:

cargo test                        # 196 language tests + 59 unit + 8 --check
./scripts/run_examples.sh         # every example, with expected outcomes

The language tests assert on program results, not exit codes — every bug they cover exited 0 while producing the wrong answer.

30. Environment variables {: #30-environment-variables}

Variable Effect
ETAMIL_PATH Where இறக்கு looks after the importing file’s own directory
ETAMIL_JWT_SECRET Signing secret for சீட்டு_ஆக்கு / சீட்டு_சரிபார்
ETAMIL_DB_IDLE How many database connections stay warm in the idle cache
ETAMIL_TEST_MYSQL Set to 1 to include the live MySQL example in the runner

31. Next steps {: #31-next-steps}