payflow
RUNS IN YOUR BROWSERA small programming language for subscription billing, with its own lexer, parser, type checker and interpreter — all hand-written, no parser generator, no dependencies. Money carries its currency in the type, so adding dollars to euros is a compile error rather than a support ticket. Abonelik faturalandırması için küçük bir programlama dili; kendi sözcük çözümleyicisi, ayrıştırıcısı, tip denetleyicisi ve yorumlayıcısıyla — hepsi elle yazıldı, ayrıştırıcı üreteci yok, bağımlılık yok. Para birimi tipin içinde taşındığı için dolara euro eklemek bir destek kaydı değil, derleme hatası.
This figure is drawn by a script that first runs the program through the interpreter and refuses to render if the answer is no longer 8.91. A figure that cannot go stale is worth more than a screenshot. Bu şekil, önce programı yorumlayıcıdan geçiren ve cevap artık 8.91 değilse çizmeyi reddeden bir script tarafından üretiliyor. Bayatlayamayan bir şekil, ekran görüntüsünden daha değerlidir.
Why a language and not a library Neden kütüphane değil de dil
A library can offer a Money class and a rounding helper. What it cannot do is make the wrong program fail to compile. Three billing concepts are constructs of the language here, and each one moves a class of bug from run time to compile time. Bir kütüphane Money sınıfı ve yuvarlama yardımcısı sunabilir. Yapamadığı şey, yanlış programın derlenmesini engellemektir. Burada üç faturalandırma kavramı dilin yapı taşı ve her biri bir hata sınıfını çalışma zamanından derleme zamanına taşıyor.
Currency lives in the type Para birimi tipin içinde
9.99 USD + 2.50 EUR
Type error at line 2:
cannot + money in
different currencies
(USD vs EUR)
money is not a float with a label stapled on. Where the currencies are known statically the checker rejects the program, and there is no coercion between money and float in either direction. money, üzerine etiket iliştirilmiş bir float değil. Para birimleri statik olarak bilindiğinde denetleyici programı reddediyor ve money ile float arasında hiçbir yönde dönüşüm yok.
Exhaustiveness is grammar Bütünlük gramerin işi
paywall Onboarding {
when region == "EU" { ... }
} <- no default
Parse error at line 13:
every `paywall` must end
with a `default` clause
The default clause is written into the grammar rule itself, not checked afterwards. A paywall with an unhandled region does not parse, so it cannot reach a user with a blank screen. default bloğu sonradan denetlenmiyor, doğrudan gramer kuralının içine yazılmış. Karşılanmayan bölgesi olan bir paywall ayrıştırılamıyor, dolayısıyla kullanıcıya boş ekranla ulaşamıyor.
Deductions chain left to right Kesintiler soldan sağa zincirlenir
price split storeFee split vat
// binds looser than + and -
total + bonus split fee
// == (total + bonus) split fee
The store takes its cut first, then VAT comes off the remainder. Placing split below addition means an auditor reading left to right gets the same answer the interpreter does. Önce mağaza payını alır, sonra kalandan KDV düşer. split'i toplamanın altına koymak, soldan sağa okuyan bir denetçinin yorumlayıcıyla aynı cevaba varması demek.
Run it yourself Kendin çalıştır
This is not a simulation of the language. The five Python modules from the repository are loaded into your browser and executed there, so the errors below are the ones the real type checker raises. The runtime is about 10 MB and downloads only when you press RUN. Bu, dilin bir taklidi değil. Repodaki beş Python modülü tarayıcına yüklenip orada çalıştırılıyor, yani aşağıdaki hatalar gerçek tip denetleyicisinin ürettiği hatalar. Çalışma zamanı yaklaşık 10 MB ve yalnızca ÇALIŞTIR'a bastığında iniyor.
press RUN
The AST and TOKENS tabs show the same structures the CLI prints with --dump-ast. Ctrl+Enter runs without reaching for the button. AST ve TOKENS sekmeleri, komut satırının --dump-ast ile bastığı yapıların aynısını gösteriyor. Ctrl+Enter, butona uzanmadan çalıştırır.
The example that was proving nothing Hiçbir şey kanıtlamayan örnek
The specification says split rounds after every step, not once at the end. The repository shipped an example named after that rule, using a 30% platform cut and 18% VAT. Spesifikasyon, split'in her adımdan sonra yuvarladığını söylüyor; sonda bir kez değil. Repoda bu kuralın adını taşıyan bir örnek vardı: %30 platform payı ve %18 KDV ile.
Both rules return 5.73 USD on those numbers. The example demonstrated the rule it was named after only in the sense that it did not contradict it — its own comment admitted as much. I picked percentages where the rules disagree and added a second example, so the claim is now settled by running it rather than by trusting the comment. O sayılarda iki kural da 5.73 USD veriyor. Örnek, adını taşıdığı kuralı ancak onunla çelişmediği ölçüde gösteriyordu — kendi yorum satırı da bunu kabul ediyordu. Kuralların ayrıştığı yüzdeleri seçip ikinci bir örnek ekledim; artık iddia, yoruma güvenerek değil çalıştırılarak karara bağlanıyor.
This is the same failure as a test that passes for the wrong reason. The number was right; the example simply could not tell the two candidates apart. Bu, yanlış sebepten geçen bir testle aynı hata. Sayı doğruydu; örnek sadece iki adayı birbirinden ayırt edemiyordu.
| input | per-step | at end | verdict |
|---|---|---|---|
| 9.99 split 30% split 18% | 5.73 | 5.73 | tells nothing |
| 9.99 split 3% split 8% | 8.91 | 8.92 | decisive |
Load "rounding divergence" in the playground above to reproduce the second row. İkinci satırı yeniden üretmek için yukarıdaki playground'da "rounding divergence" örneğini seç.
Four stages, four error messages Dört aşama, dört hata mesajı
Each stage is its own module with its own exception type, and the driver turns any failure into one line naming the phase and the line number. Nothing is caught in a place that cannot say what went wrong. Her aşama kendi istisna tipiyle ayrı bir modül; sürücü, herhangi bir başarısızlığı aşamayı ve satır numarasını söyleyen tek satıra çeviriyor. Hiçbir şey, neyin bozulduğunu söyleyemeyecek bir yerde yakalanmıyor.
Source to tokens. Percent is one token; money and duration are assembled later, so the lexer never has to know the currency list. Kaynaktan token'lara. Yüzde tek token; money ve duration sonradan birleştiriliyor, böylece lexer'ın para birimi listesini bilmesi gerekmiyor.
Lex error at line 4:
unexpected character '@'
Recursive descent. Left-associativity comes from loops, not from recursing before the operator is consumed — the usual way a hand-written parser turns out right-associative by accident. Özyinelemeli iniş. Sol birleşimlilik döngülerden geliyor, operatör tüketilmeden özyinelemeden değil — elle yazılmış bir ayrıştırıcının kazara sağ birleşimli çıkmasının olağan yolu budur.
Parse error at line 3:
comparison operators do not chain
Two passes, so a paywall can reference a plan declared further down. Records use name equivalence: identical fields are still different types. İki geçiş, böylece bir paywall daha aşağıda tanımlanmış bir plana atıf yapabiliyor. Record'lar isim denkliği kullanıyor: aynı alanlar hâlâ farklı tipler.
Type error at line 26:
expects Plan, got RefundRule
Tree-walking, with money held as Decimal in the currency's minor unit. No float ever touches a price, so no drift accumulates across a chain. Ağaç yürüyen; para, para biriminin alt biriminde Decimal olarak tutuluyor. Hiçbir float fiyata dokunmuyor, dolayısıyla zincir boyunca kayma birikmiyor.
Runtime error at line 7:
division by zero
What it cannot do Yapamadıkları
Records can be passed, not built Record'lar geçirilebiliyor, inşa edilemiyor
A record type can be declared, named in a parameter and read with field access, but there is no expression that constructs one. The type checker is complete on records; the language around them is not. Bir record tipi tanımlanabiliyor, parametrede adlandırılabiliyor ve alan erişimiyle okunabiliyor; ama onu inşa eden bir ifade yok. Tip denetleyicisi record'lar için tam; etrafındaki dil değil.
No assignment, so no loops Atama yok, dolayısıyla döngü de yok
Removing assignment makes every function pure and auditable in isolation, which is what a billing rule should be. The cost is real: there is no accumulator, so anything iterative has to be expressed by composition. Atamayı kaldırmak her fonksiyonu saf ve tek başına denetlenebilir yapıyor; bir faturalandırma kuralının olması gereken de bu. Bedeli gerçek: biriktirici yok, dolayısıyla yinelemeli her şey bileşimle ifade edilmek zorunda.
The spec used syntax the parser lacked Spesifikasyon, ayrıştırıcıda olmayan sözdizimini kullandı
The design document used p.price as a worked example before field access existed in the grammar. Writing the type-checker tests surfaced it, because they were the first thing to approach the spec from a new angle. Tasarım dokümanı, gramerde alan erişimi yokken p.price'ı çözümlü örnek olarak kullanıyordu. Bunu tip denetleyicisi testlerini yazmak ortaya çıkardı, çünkü spesifikasyona yeni bir açıdan yaklaşan ilk şey onlardı.
The money annotation has no currency money anotasyonu para birimi taşımıyor
A parameter is declared money, not money(USD), so the checker only rejects mixed currency where both sides are statically known. Pass a USD and a EUR into the same function and the failure arrives at run time instead — and its message starts with "internal:", because the interpreter considers this a case the checker was supposed to catch. Currency-parameterised types would close the gap; the playground's last two examples show both sides of it. Bir parametre money(USD) değil money olarak tanımlanıyor, dolayısıyla denetleyici karışık para birimini yalnızca iki taraf da statik olarak biliniyorken reddediyor. Aynı fonksiyona bir USD ve bir EUR geçir, hata bu kez çalışma zamanında gelir — ve mesajı "internal:" ile başlar, çünkü yorumlayıcı bunu denetleyicinin yakalaması gereken bir durum sayıyor. Para birimiyle parametrelenmiş tipler bu boşluğu kapatırdı; playground'daki son iki örnek meselenin iki yüzünü de gösteriyor.
No performance claims Performans iddiası yok
Nothing here is benchmarked. A tree-walking interpreter on arbitrary-precision decimals is slow by construction, and the design accepts that openly — paywalls are evaluated about once per session. I have not measured it, so I do not quote a number. Burada hiçbir şeyin kıyaslaması yapılmadı. Rastgele duyarlıklı ondalıklar üzerinde ağaç yürüyen bir yorumlayıcı yapısı gereği yavaştır ve tasarım bunu açıkça kabul ediyor — paywall'lar oturum başına yaklaşık bir kez değerlendiriliyor. Ölçmediğim için sayı da vermiyorum.
Read further Devamını oku
The full grammar in EBNF, big-step operational semantics for split and paywall, the precedence table, and a named trade-off for every design decision. EBNF'te tam gramer, split ve paywall için büyük adım işlemsel semantik, öncelik tablosu ve her tasarım kararı için adı konmuş bir ödünleşim.
OPENAÇ arrow_forward description examples/Five programs that run and six that are meant to fail — one per error class, each with the exact message the pipeline produces. Çalışan beş program ve başarısız olması amaçlanan altı tanesi — her hata sınıfından bir tane, her biri boru hattının ürettiği tam mesajla.
OPENAÇ arrow_forward science tests/Fifty tests over the rules that distinguish this language from a generic toy: banker's rounding, short-circuiting, first-match paywalls, and left-to-right evaluation. Bu dili genel bir oyuncaktan ayıran kurallar üzerine elli test: bankacı yuvarlaması, kısa devre değerlendirme, ilk eşleşen paywall ve soldan sağa değerlendirme.
OPENAÇ arrow_forward