# Quill – a readable language where money is exact and failures can't hide

> Source: <https://github.com/Ngwangshalom/Quill>
> Published: 2026-11-03 09:09:57+00:00

**Write what you mean.**

  Intent is clear, failures are explicit, and correctness is part of the code.

**Quill is in early development.** The design is being written down, and the interpreter runs the first features so far: `say`, values, `var` / `set`, exact arithmetic, `{values}` inside text, `when`, `for` loops, `get` and `sum`, lists, records, `check` and doc examples, functions (`fn`, `return`), money (` 100 USD`), `maybe`, failures (` fails`, `on`, `try`), and contracts (` require`, `ensure`, `where`). Nothing here is ready for production. If you care about how a language should feel, this is the best moment to help shape it.

⚠️ 

Quill is a programming language where intent is clear, failures are explicit, and correctness is part of the code. It is for backend engineering, financial software, automation, reliable business applications, and AI and machine learning. A beginner should be able to read it. The same care applies when a program asks a model: the answer may be missing, and the call may fail.

The syntax stays human-readable. `when`, `found`, `missing`, `fails`, `on`, and `check` are the words that give Quill its voice.

Quill can call web services and ask AI models, each behind a `permit`. A web server and a database are next. That is the path to a service you can deploy.

What works now:

- **Code reads like what it means.** Indentation-based, no semicolons, no braces.
- **Money is exact.**`100 USD + 50 USD` is`150 USD` .`0.1 + 0.2` is`0.3` . Dollars and euros cannot be added;`convert 100 USD to EUR at 0.92` changes one at the rate you write.`split 100 USD into 3` gives`33.34, 33.33, 33.33` , so no cent is lost, and`round total to cents` rounds the way banks do.
- **Ask for the data.**`sum fee of roster` adds each fee.`get fee of roster` takes that field.`get place of desk.bookings` reads a field of a list on a record.`students where score >= 50` keeps only the records that match, and`sum fee of roster where paid == false` adds what is still owed.`count of students where paid == false` says how many still owe,`average score of students` gives the class average, and`students sorted by highest score` ranks them. A missing amount is left out.`for` remains when each record needs its own step.
- **Text tools read like English.**`trim name` ,`uppercase code` ,`length of name` ,`email contains "@"` ,`code starts with "INV"` ,`replace "-" with " " in code` ,`split line by ","` , and`join names with ", "` .
- **Nothing is missing by surprise.** There is no`null` . A`maybe` says the value might be missing, and both cases have to be handled.
- **Ask a model, and trust the answer.**`ask "Rate this review: {text}" as Review` asks Claude, or any OpenAI-compatible model, and checks the answer against every rule of`Review` . A model that fails or breaks the rules is a failure the program handles.
- **Call other services.**`bring http` , then`fetch(url)` or`post(url, json(order))` . The host needs a`permit net` , and`NetworkDown` has to be handled.
- **JSON you can trust.**`body as Booking` reads JSON and checks every field and rule before the program uses it, or fails with`Invalid` .`json(booking)` writes it back.
- **Programs grow across files.**`bring billing` uses the functions and types written in billing.quill. A mistake in a brought file names that file.
- **Failures are part of the function.**`fails Declined` names the failure,`fail Declined` leaves with it, and the caller handles it with`on` (stop, or carry on with a value in its place) or passes it on with`try` .
- **Functions state their rules.**`require` says what a function needs and`ensure` says what it promises.`where 1..500` gives a parameter its range. A broken one stops the program and names the value that broke it.
- **Errors teach.** Every error explains what went wrong, why, and how to fix it.`quill explain` shows how Quill read the code.
- **Tests live with the code.**`check` lines are tests, and examples in doc comments run as tests too.

None of these ideas is new on its own. Quill's goal is to make them work together, in one language a person can read and a team can trust.

**macOS and Linux:**

```
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/Ngwangshalom/Quill/releases/latest/download/quill-installer.sh | sh
```

**Windows (PowerShell):**

```
powershell -ExecutionPolicy Bypass -c "irm https://github.com/Ngwangshalom/Quill/releases/latest/download/quill-installer.ps1 | iex"
```

Or download a build for your system from the [Releases page](https://github.com/Ngwangshalom/Quill/releases). Then try it:

```
quill new hello
quill run hello/hello.quill
```

`quill new` asks what kind of program you want, and every template runs and passes its tests straight away. Name one to skip the menu, like `quill new orders backend`:

| For | Templates | 
|---|---|
| Everything | `app` (a whole app: a backend, an AI assistant, exact money, data from a file, and tests) | 
| Learning | `learn` (exercises to fix),`starter` (one file) | 
| Backend work | `backend` (a service for web, desktop, and mobile apps),`integration` (call another service),`webhook` (receive notices) | 
| AI | `ai` (ask a model, check its answer),`ai-backend` (a model behind a service) | 
| Business and finance | `finance` (invoices with exact money),`banking` (accounts that can't go below zero) | 
| Automation and general programs | `automation` (read a file, write a report),`library` (functions other programs bring in) | 

Each project comes with a `README.md` and `QUILL.md`, a one-page tour of the language.

To update later, run `quill update` and choose a version with the arrow keys. Quill also tells you when a new version is out (set `QUILL_NO_UPDATE_CHECK=1` to turn that off).

Building from source needs [Rust](https://rustup.rs): `cargo install --git https://github.com/Ngwangshalom/Quill`

**[The Quill Programming Language](https://ngwangshalom.github.io/Quill/)** is a book that teaches Quill from the very beginning, for new and experienced programmers. It grows with the language, and every example in it is tested. Its source is in [book/src](https://github.com/Ngwangshalom/Quill/blob/main/book/src/SUMMARY.md).

The first time you run Quill, Cursor and VS Code get Quill's own colors, the feather on every `.quill` file, and underlines for mistakes. The feather is pale on a dark editor and deep blue on a light one. Each kind of word has its own color: `say` is nib orange `#F0A35E`, `when` is feather blue `#7EAEF5`, and `true` is green `#5ECA8C`. Vim, Emacs, and Sublime detect `.quill` files from [`editors`](https://github.com/Ngwangshalom/Quill/blob/main/editors). `quill lsp` sends the same mistakes to any editor. Set `QUILL_NO_EDITOR_SETUP=1` to leave Cursor and VS Code alone.

```
say "Hello, World!"
```

`=` declares a value. Values can't change unless they're marked `var`, and changing one takes `set`, so every mutation is visible when you read the code.

``` js
name = "Amina"
var visits = 0

set visits = visits + 1
set name = "Omar"        -- error: name isn't marked var
set vistis = 2           -- error: no value called vistis (did you mean visits?)
```

Text that contains a quote is wrapped in three quotes, so the quote inside needs no backslash:

```
quoted = """She said "hi"."""
typed = "  amina@school.org "
email = lowercase trim typed

when
  email contains "@" and email ends with ".org" -> say "A school address"
  else -> say "Check the address"

parts = split "Amina,80,paid" by ","
say join parts with " / "
say replace "-" with " " in "INV-2026-001"
```

That says `A school address`, `Amina / 80 / paid`, and `INV 2026 001`. `length of email` is `16`.

``` php
fn double(n: number) -> number
  return n * 2

say double(21)

fn greet(name: text)
  say "Hello, {name}"

greet("Amina")
```

A parameter's kind is `text`, `number`, `true/false`, or `list of` one of those. Kind names such as `Int` and `Text` come later.

`when` follows one simple rule: matching on a fixed set of cases (like `maybe` or failures) must cover every case, and matching on numbers or text must end with `else`.

``` php
fn find_price(name: text) -> maybe number
  when
    name == "bread" -> return found 2.50
    else -> return missing

when find_price("bread")
  found price -> say price
  missing -> say "none"
```

Forget the `missing` branch and Quill refuses to run. Named types such as `User` arrive later.

``` php
fn charge(amount: USD) -> text
  fails Declined, NetworkDown

var receipt = charge(250 USD)
  on Declined -> say "Card declined"
  on NetworkDown -> say "Try again"
```

Inside a function that declares the same failures, `try` passes them upward explicitly:

``` php
fn checkout(amount: USD) -> text
  fails Declined, NetworkDown
  return try charge(amount)
```

A statement after `on`, like `say`, runs and then stops the program. A value takes the place of the call's answer, and the program carries on:

``` php
var receipt = charge(250 USD)
  on Declined -> "declined"
  on NetworkDown -> "try again"
```

`respond` arrives with the web module.

``` php
label = when
  score >= 70 -> "A"
  score >= 50 -> "Pass"
  else -> "Fail"
```

The first true case gives the value. `else` is required, so there is always one.

```
total = 100 USD + 50 USD
```

That is `150 USD`. `100 USD + 20 EUR` is a mistake: Quill will not mix currencies. `split`, `round`, `20% of fee` and `convert ... at RATE` keep money exact without hiding a cent. The currency itself is the kind: `amount: USD`. `amount: USD where 1 USD..10000 USD` also gives it a range.

`sum` adds one field. `get` takes that field, including a field further in. `where` keeps only the records that match. `count of` says how many, `average` gives the typical amount, and `sorted by` puts the records in order. A missing amount is left out. `for` stays when each record needs its own step.

```
sisters = record name = "Nchang Sisters", fee = 80000 USD
brass = record name = "Bamenda Brass", fee = 150000 USD
roster = list sisters, brass

say sum fee of roster
say get fee of roster
say get name of roster where fee > 100000 USD
say count of roster
say average fee of roster
say get name of roster sorted by highest fee

up = record place = "Up Station", acts = list "Bamenda Brass", "Hilltop Duo"
old = record place = "Old Town", acts = list "Market Drums"
desk = record bookings = list up, old
say get place of desk.bookings
first = desk.bookings at 1
say first.acts

people = get name, fee of roster
person = people at 1
say person.name
```

`sum fee of roster` is `230000 USD`. `sum fees of school` is that one amount. `get fee of roster` is `80000 USD, 150000 USD`. `get name of roster where fee > 100000 USD` is `Bamenda Brass`, the records whose condition is true. `count of roster` is `2`, `average fee of roster` is `115000 USD`, and `get name of roster sorted by highest fee` is `Bamenda Brass, Nchang Sisters`. `get place of desk.bookings` is `Up Station, Old Town`. `first.acts` is `Bamenda Brass, Hilltop Duo`. `get name, fee of roster` keeps those two fields together, so `person.name` is `Nchang Sisters`.

A function states what it needs and what it promises. `require` is checked when the function is called. `ensure` is checked each time it gives back, and `result` is the value it gives back.

``` php
fn withdraw(balance: USD, amount: USD) -> USD
  require amount > 0 USD
  ensure result >= 0 USD
  fails InsufficientFunds
  when
    amount > balance -> fail InsufficientFunds
  return balance - amount
```

A range goes right on the parameter with `where`. Both ends are included:

``` php
fn book(guests: number where 1..500) -> text
  return "Booked for {guests} guests"
```

`book(600)` is caught before the program runs. A value worked out while it runs is checked when `book` is called.

A broken contract stops the program and says which value broke it, and whose mistake it was:

```
Problem on line 2 of bank.quill:

      require amount > 0 USD
      ^^^^^^^^^^^^^^^^^^^^^^
withdraw requires amount > 0 USD, but amount is -5 USD.
The call on line 9 gave withdraw a value it doesn't accept. Check the value before calling withdraw.
```

Proving contracts before the program runs comes later.

A program can only reach the network, files or settings it has been granted.

```
permit read "config"
permit env "STRIPE_KEY"

rates = read_file("config/rates.txt")
  on FileMissing -> "USD 1"
```

Anything else is refused with a clear error, before the program runs when the path is written in the call. A file you bring in can't give itself permits, so a compromised dependency can't quietly read your secrets or send your data somewhere.

Examples in doc comments are real tests, so documentation can't drift out of date.

```
--- Adds 5% VAT to a price.
--- example: with_vat(100 USD) == 105 USD
fn with_vat(price: USD) -> USD
  return price * 1.05
use web
use db

permit net "localhost:5432"

type Booking
  id: Int
  venue: Text
  guests: Int where 1..500

route GET "/bookings/{id}"
  when db.find(Booking, id)
    found b -> respond 200, b
    missing -> respond 404, "Booking not found"

route POST "/bookings"
  booking = request.body as Booking
    on Invalid err -> respond 400, err.message
  respond 201, db.save(booking)
Problem on line 4 of shop.quill:

    total = price + "5"
                    ^^^
You're adding money in USD (price) and Text ("5").
Add or subtract two amounts in the same currency, like 100 USD + 50 USD.
```

And when you want to see how Quill understood your code:

``` bash
$ quill explain shop.quill:4

Line 4:  total = price + "5"

  total         a new value (can't change)
  =             store the value on the right in total
  price + "5"   a calculation, a value
  price         money in USD, created on line 2

You're adding money in USD (price) and Text ("5").
Add or subtract two amounts in the same currency, like 100 USD + 50 USD.

Quill stopped here before running anything.
```

| Command | What it does | 
|---|---|
| `quill run file.quill` | Run a program | 
| `quill check file.quill` | Find errors without running anything | 
| `quill lsp` | Underline those errors in an editor | 
| `quill test` | Run project test files and count executed checks and doc examples | 
| `quill test file.quill` | Run one file and count its executed checks and doc examples | 
| `quill test folder` | Run the test files in one folder and its subfolders | 
| `quill explain file.quill:LINE` | Show how the compiler read a line | 
| `quill new NAME [TEMPLATE]` | Start a new project from a template | 
| `quill update` | Choose a version of Quill to install (also `quill upgrade` ) | 

[docs/spec.md](https://github.com/Ngwangshalom/Quill/blob/main/docs/spec.md) defines exactly what Quill programs mean today: values and their kinds, exact numbers, operators, statements, `when`, tests and errors. This README shows the full design, including features that are still planned.

Quill is implemented in **Rust**, which gives it a single-binary install, a fast virtual machine, memory safety, and a WebAssembly build for an in-browser playground.

```
source (.quill) → lexer → parser → checker → bytecode compiler → virtual machine
```

The checker is where Quill earns its keep: type mismatches, unhandled cases, missing values, undeclared failures and broken contracts are caught before anything runs.

Quill is for backend engineering, financial software, automation, reliable business applications, and AI and machine learning. That includes asking a model and training one. Operating systems, device drivers, and 3D games stay with Rust and C++.

**Foundation**

-  [Language specification](https://github.com/Ngwangshalom/Quill/blob/main/docs/spec.md) (v0.1 draft)
- Lexer and parser
-  `say` , values,`var` /`set` , expressions
-  `when` with conditions, and`when` that gives a value
-  Loops (`for i in 1..3` , and`for hobby in hobbies` )
-  Asking for data (`sum fee of roster` ,`get fee of roster` ,`get place of desk.bookings` ,`count of roster` ,`average fee of roster` ,`roster sorted by fee` )
- Lists and records
-  Text tools (`trim` ,`uppercase` ,`length of` ,`contains` ,`starts with` ,`replace` ,`split ... by` ,`join ... with` )
-  Functions (`fn double(n: number) -> number` ,`return` , and calls)

**Safety**

- Type checker with friendly errors
-  `maybe` ,`found` ,`missing` , and exhaustive`when`
-  `fails` ,`fail` ,`on` and`try`
-  `quill explain`

**Running**

- Bytecode compiler and virtual machine
-  `check` tests and executable doc examples
-  Exact `Decimal` numbers
-  Money (`100 USD + 50 USD` ; mixed currencies are a mistake)
-  Ranges on parameters (`guests: number where 1..500` )
-  Types with rules on their fields (`type Booking` ,`guests: number where 1..500` )
-  `require` /`ensure` contracts (runtime)

**Ecosystem**

-  Permissions (`permit` ), files, and settings
-  `web` module: HTTP server and routing
-  `db` module: PostgreSQL and SQLite
-  AI module: type-checked model calls (`ask ... as Review` ), Claude and OpenAI-compatible
-  Syntax highlighting for VS Code and Cursor ([editors/vscode](https://github.com/Ngwangshalom/Quill/blob/main/editors/vscode) )
- Browser playground (WebAssembly)
- Package manager

**Later**

- Safe concurrency
- Static verification of contracts

Quill is built in the open, and contributions of every size are welcome: code, documentation, examples, bug reports, and especially opinions on the language design while it's still flexible.

1. Read [CONTRIBUTING.md](https://github.com/Ngwangshalom/Quill/blob/main/CONTRIBUTING.md)
2. Look for issues labeled [`good first issue`](https://github.com/Ngwangshalom/Quill/labels/good%20first%20issue)
3. Join the conversation in [Discussions](https://github.com/Ngwangshalom/Quill/discussions)

Please follow our [Code of Conduct](https://github.com/Ngwangshalom/Quill/blob/main/CODE_OF_CONDUCT.md) in all project spaces.

Quill is licensed under the [Apache License 2.0](https://github.com/Ngwangshalom/Quill/blob/main/LICENSE).
