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 USDis150 USD.0.1 + 0.2is0.3. Dollars and euros cannot be added;convert 100 USD to EUR at 0.92changes one at the rate you write.split 100 USD into 3gives33.34, 33.33, 33.33, so no cent is lost, andround total to centsrounds the way banks do. - Ask for the data.
sum fee of rosteradds each fee.get fee of rostertakes that field.get place of desk.bookingsreads a field of a list on a record.students where score >= 50keeps only the records that match, andsum fee of roster where paid == falseadds what is still owed.count of students where paid == falsesays how many still owe,average score of studentsgives the class average, andstudents sorted by highest scoreranks them. A missing amount is left out.forremains 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 ",", andjoin names with ", ". - Nothing is missing by surprise. There is no
null. Amaybesays 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 Reviewasks Claude, or any OpenAI-compatible model, and checks the answer against every rule ofReview. A model that fails or breaks the rules is a failure the program handles. - Call other services.
bring http, thenfetch(url)orpost(url, json(order)). The host needs apermit net, andNetworkDownhas to be handled. - JSON you can trust.
body as Bookingreads JSON and checks every field and rule before the program uses it, or fails withInvalid.json(booking)writes it back. - Programs grow across files.
bring billinguses the functions and types written in billing.quill. A mistake in a brought file names that file. - Failures are part of the function.
fails Declinednames the failure,fail Declinedleaves with it, and the caller handles it withon(stop, or carry on with a value in its place) or passes it on withtry. - Functions state their rules.
requiresays what a function needs andensuresays what it promises.where 1..500gives 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 explainshows how Quill read the code. - Tests live with the code.
checklines 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. 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: cargo install --git https://github.com/Ngwangshalom/Quill
The Quill Programming Language 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.
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. 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.
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.
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.
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.
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:
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:
var receipt = charge(250 USD)
on Declined -> "declined"
on NetworkDown -> "try again"
respond arrives with the web module.
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.
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:
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:
$ 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 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 (v0.1 draft)
- Lexer and parser
say, values,var/set, expressionswhenwith conditions, andwhenthat gives a value- Loops (
for i in 1..3, andfor 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 exhaustivewhenfails,fail,onandtryquill explain
Running
- Bytecode compiler and virtual machine
checktests and executable doc examples- Exact
Decimalnumbers - 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/ensurecontracts (runtime)
Ecosystem
- Permissions (
permit), files, and settings webmodule: HTTP server and routingdbmodule: 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 )
- 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.
- Read CONTRIBUTING.md
- Look for issues labeled
good first issue - Join the conversation in Discussions
Please follow our Code of Conduct in all project spaces.
Quill is licensed under the Apache License 2.0.