Quill – a readable language where money is exact and failures can't hide Quill, a programming language in early development, is being designed so that intent is clear, failures are explicit, and correctness is part of the code, with an interpreter that already runs features including exact money arithmetic, `maybe` values in place of `null`, `check` tests, and contracts via `require` and `ensure`. The language's money handling makes `0.1 + 0.2` equal `0.3`, rejects adding dollars to euros, and splits `100 USD` into `33.34, 33.33, 33.33` so no cent is lost. Quill can also call AI models through `ask "..." as Review`, checking the answer against every rule of the `Review` type, and call web services through `bring http` behind a `permit net`; its developers state nothing is ready for production and invite early feedback on the design. 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 .