Dil
Dil Referansı
Luma'nın yüzey sözdizimi dar tutulmuştur — tamamı bu sayfaya sığar. Bu bir eksiklik değil kapsam kararıdır: dil genel amaçlı hesaplama için değil, model sınırını tarif etmek için tasarlandı. Buna karşılık gramer satır tabanlıdır ve karşılaştırma operatörleri her yerde geçerli değildir; en sık takılınan iki nokta bunlar olduğu için en başta ele alınıyor.
Program yapısı
Bir .luma dosyası üst düzey deklarasyonlardan ve operation'lardan oluşur. Deklarasyonların sırası önemsizdir; operation'lar ise kaynakta göründükleri sırayla çalışır.
| Deklarasyon | Biçim | Amaç |
|---|---|---|
import | import name from "std/math" | Pure modül yükler. |
input | input topic: string | CLI girdisi bildirir (string veya file). |
context | context b = file("docs/b.md") | Dosya içeriğini kaynak kimliğiyle bağlar. |
schema | schema Name { … } | Veri sözleşmesi tanımlar. → Schema |
schema … = A + B | schema Admin = User + Audit | Schema composition. |
model | model Primary { … } | Model/provider profili. → Model |
run_policy | run_policy R { … } | Çok modelli çalıştırma stratejisi. |
tool | tool name(p: t) -> Out { … } | Tool contract'ı. → Tool |
pure fn / io fn | pure fn f(x: int) -> int { … } | Deterministik fonksiyon. |
agent | agent Name { fn m() -> S { … } } | Method taşıyan birim. → Agent |
datasource | datasource Name { … } | Veritabanı bağlantı contract'ı. → Veri |
query | query Name(p: t) -> Row[] { … } | Datasource üzerinde tipli okuma. → Veri |
command | command Name(p: t) -> int { … } | Tipli yazma; side_effects bildirmek zorundadır. → Veri |
Operation'lar (üst düzey, sırayla çalışır)
x = ask "..." -> Schema [retry 2] // direct LLM call
y = tool calculate({ a: 1, b: 2 }) // direct tool call
inst = AgentName(constructorArg) // agent instance
z = inst.method(arg1, arg2) // agent method call
p = parallel [options] { ... } // fan-out / fan-in block
rows = query Orders(since) // datasource read (v0.4.0)
n = command MarkExported(since, tag) // datasource write (v0.4.0)
out = parallel over rows as row limit 4 -> Out[] { // per-element fan-out (v0.4.0)
return { id: row.id }
}Program en az bir operation içermelidir ve yukarıdaki biçimlerin hepsi sayılır — tek başına bir query yeter. Hiçbiri yoksa alınan mesaj hâlâ yalnızca ilk üçünü adlandırır (ask, agent call or parallel not found.); bu, veri biçimleri hakkında bir kural değil, onlardan eski kalmış bir ifadedir. Çıktı olarak son operation'ın sonucu yazdırılır; --all ise her operation'ın sonucunu, atandığı değişken adıyla birlikte basar. Düz atamalar (x = 42) operation değildir ve iki çıktıda da görünmez.
parallel ile parallel over, aynı kelimeyi paylaşan iki ayrı operation'dır. parallel { … } yazdığın dallar üzerinde fan-out yapar — sabit, adlandırılmış adımlar kümesi — ve agent method'unun içinde de yazılabilir. parallel over rows as row limit 4 -> Out[] { … } ise veri üzerinde fan-out yapar: eleman başına bir gövde, opsiyonel olmayan bir limit ve yalnızca üst düzey. Kendi kuralları — giriş sırası, on_error, memory scope tuzağı — Veri & Entegrasyon sayfasındadır.
Üst düzeyde bir satır ya deklarasyon (import, input, context, schema, model, run_policy, tool, pure fn, io fn, agent, datasource, query, command) ya da operation (ask, tool çağrısı, agent instance'ı, agent method çağrısı, parallel, parallel over, query çağrısı, command çağrısı) olmalıdır. Başka her şey program başlamadan, parse zamanında reddedilir.
Kısıt ters yönde de işler ve beklenmeyen yön bu: query, command ve parallel over yalnızca üst düzeydedir. Birini agent method'unun ya da fn gövdesinin içine koyarsan adıyla reddedilir — Error: 'query' can only be used as a top-level operation, not in an agent method or fn body. → sebebi
Aşağıdaki statement'lar method ve fn gövdesi yapılarıdır; üst düzeyde yazılırsa yapının adını veren ayrı bir mesaj alırsın:
Error: 'filter' can only be used in an agent method or fn body (line 2).
map · filter · reduce · if · while · switch · try · for
Hiçbir kurala uymayan diğer her şey — üst düzeyin hiçbir zaman desteklemediği n = 42 gibi düz bir atama dahil — satırı olduğu gibi bildirir:
Error: Unrecognized top-level line (line 1): n = 42
Bu sayfadaki kısa örnekler okunabilirlik için statement'ları tek başına gösterir; çoğu bir agent method'unun ya da pure fn'in içinde yer alır.
Gramer satır tabanlıdır
Luma kaynağı satır-satır regex ile ayrıştırır; bu yüzden iki statement'ı aynı satıra koymak sözdizimi hatası verir ve aynı kural schema alanları için de geçerlidir: satır başına bir alan. Tek bir statement ise bölünebilir — bir satır, devam token'ı ile başlıyorsa kendinden önceki satıra eklenir.
schema Result {
count: int
total: float
}
pure fn f() -> Result {
a = 1
b = 2
return { count: a, total: b }
}schema Result {
count: int, total: float
}
// A schema line may define only one field: count: int, total: float
pure fn f() -> Result {
a = 1 b = 2
return {
count: a
}
}Bir satır; @ ile ya da ikili operatör, virgül veya nokta ile — yani şunlardan biriyle — başlıyorsa önceki satırın devamı sayılır: || · && · === · !== · <= · >= · == · != · + · - · * · / · < · > · , · .. => bu kümede değildir. @ biçimi sayesinde uzun annotation zincirleri bölünebilir:
schema Answer {
summary: string
@desc("Yalnızca verilen belgeye dayalı özet")
@length(20, 400)
confidence: float [0..1]
}Uzun bir where veya when ifadesi, her devam satırı bir operatörle başladığı sürece bölünebilir. Keyword öncesinde bölmek çalışmaz:
// ✓ valid — the continuation line starts with ||
where a == null || b == null
|| num.approximatelyEqual(a * b, t, 0.01)
@feedback("a * b toplamına uymuyor")
// ✗ invalid — 'require' is a keyword, not a continuation token
when kind == "kurumsal"
require [tax_id]Geçersiz biçim Error: Invalid schema field: when kind == "kurumsal" ile düşer. İfade gerçekten uzuyorsa mantığı bir pure fn'e taşı → where bölümü.
Girinti, alan aralığı ve devam satırının nereye oturduğu tam olarak yerleşik biçimlendiricinin normalize ettiği şeylerdir; yani yukarıdaki yerleşimi elle tutturmak zorunda değilsin, uygulatabilirsin. --fmt kanonik biçimi standart çıktıya yazar ve dosyaya dokunmaz; --fmt --write dosyayı yerinde yeniden yazar ve Formatted: program.luma, değişecek bir şey yoksa Already formatted: program.luma bildirir. Tek başına --write ise Error: --write only applies to --fmt; add --fmt to rewrite the file in place. ile reddedilir.
luma program.luma --fmt # kanonik biçimi bas luma program.luma --fmt --write # dosyayı yerinde yeniden yaz
Uyguladığı kurallar — hepsi yerleşimle ilgili:
- Blok seviyesi başına iki boşluk. Kapanış parantezleriyle başlayan satır —
},} else {— bitirdiği gövdeyle değil, kendi başlığıyla hizalanır. - Devam satırı —
@annotation'ı ya da ikili operatör, virgül veya noktayla başlayan satır — sürdürdüğü satırın bir seviye altına girintilenir. Parser'ın "bu satır devamdır" kararını verirken baktığı token kümesi de aynıdır; böylece girinti sana parser'ın gördüğünü gösterir. - Alanlar, config girdileri ve tipli yerel değişkenler
name: typeolur: iki noktadan önce boşluk yok, sonra tam bir boşluk. Sütun hizalaması korunmaz —score : floatdamodel: Primaryda tek boşluğa iner. - Blok başlığı bir boşluk ve
{ile biter. - Satır sonu boşlukları silinir, ardışık boş satırlar tek boş satıra iner ve dosya tam bir satır sonuyla biter.
// Grading schema
schema Answer {
title:string
@desc("Kısa bir başlık")
score : float
where score > 0
&& score < 100
}
agent Grader {
// one method
fn grade() -> Answer {
result: Answer = { title: "ok", score: 10 }
return result
}
}
grader = Grader()
result = grader.grade()// Grading schema
schema Answer {
title: string
@desc("Kısa bir başlık")
score: float
where score > 0
&& score < 100
}
agent Grader {
// one method
fn grade() -> Answer {
result: Answer = { title: "ok", score: 10 }
return result
}
}
grader = Grader()
result = grader.grade()Asla yapmadığı şey programın anlamını değiştirmektir: mantıksal satırı ne birleştirir ne böler, hiçbir şeyi yeniden sıralamaz, string literal'ın ya da yorumun içeriğine dokunmaz. Dolayısıyla yukarıdaki "aynı satıra iki statement" ve "keyword'den önce bölme" hatalarını onaramaz — bunlar sözdizimi hatası olarak kalır ve biçimlendiriciden geçmemiş kodu okuyabilmek için bu sayfadaki kurallara hâlâ ihtiyacın var.
Bu depodaki her .luma dosyası zaten bu biçimdedir; biçimlendirmek onlarda hiçbir şeyi değiştirmez. Bu da onları kanonik Luma'nın nasıl göründüğü konusunda güvenilir bir referans yapar — ve biçimlendiriciyi CI'da bir kontrol olarak kullanılabilir kılar → test stratejisi.
input — dışarıdan gelen değerler
input topic: string input document: file
| Tür | Nasıl verilir | Programda |
|---|---|---|
string | --topic "teslimat süresi" veya --topic=teslimat | Değer doğrudan değişken olur. |
file | --document docs/contract.md | Dosya içeriği okunur ve adı bir context kaynağı olarak kaydedilir; sources doğrulamasında ve contexts: [document] allowlist'inde kullanılabilir. |
Yol, .luma dosyasının bulunduğu klasöre göre çözülür. Bildirilen bir input verilmezse program Input missing: --document ile durur.
context — dosyayı kaynak olarak bağlama
context contract = file("docs/contract.md")
context spec = file("docs/spec.md")
result = ask "Yalnızca bu belgelere dayan:\n\n{contract}\n\n{spec}" -> Answercontext, dosya içeriğini programa bağlar ve adını bir kaynak kimliği yapar. Schema'nda sources: string[] alanı varsa modelin oraya yazdığı her değer tanımlı context adlarından biri olmak zorundadır — bu, kaynak uydurmayı engellemenin doğrudan yoludur. Erişimi agent bazında daraltmak için contexts: allowlist'i kullanılır.
Yorumlar
// Line comment — a // inside a string literal is not affected url = "https://example.com/v1" // this is a comment, the one above is not
Yalnızca // satır yorumu vardır; blok yorumu (/* */) yoktur.
Değişkenler ve atama
number = 42
text = "Luma"
flag = true
empty = null
list = [1, 2, 3]
object = { name: "Ayşe", active: true }
field = object.nameTipli yerel değişkenler
Bir atamaya tip eklersen değer atandığı anda doğrulanır ve bu tip değişkene yapışır — sonraki atamalar da aynı tipe göre kontrol edilir.
request: CalcInput = { a: 4, b: 6, operation: "add" }
result: CalcResult = tool calculate(request)
items: LineItem[] = [{ amount: 10 }, { amount: 5 }]Yerel değişken annotation'ı yalnızca Name veya Name[] biçimini kabul eder. map<T>, union veya tuple gibi bileşik tipler yerel değişken üzerinde yazılamaz — bunlar schema alanlarında tanımlanır. Tip vermezsen değer doğrulanmadan taşınır ve yalnızca kullanıldığı yerde (tool input'u, method dönüşü) kontrol edilir.
String interpolasyonu
Prompt ve role metinleri içinde {variable} ve istediğin derinlikte noktalı yol yazılabilir — {variable.field}, {cift.sol.ad}:
ask "Şu konuyu özetle: {topic}\n\nBelge:\n{document}" -> Summary
ask "Önceki bulgu: {analysis.finding} (güven {analysis.confidence})" -> Report- Değer
stringise doğrudan yazılır; değilseJSON.stringify(value, null, 2)ile 2 boşluk girintili JSON'a çevrilir. - Tanımsız değişken referansı
'x' variable is not defined yet., kuyruğu var olmayan bir yol iseField 'x.y' was not found.hatası verir. - Süslü parantez arasındaki metin geçerli bir referans değilse —
{ },{1x},{a.}— prompt'ta yazıldığı gibi kalır, hata verilmez. Bu bilinçli: modele JSON örneği gösteren bir prompt süslü parantez doludur ve onları bozuk yer tutucu saymak o prompt'ları yazılamaz kılardı. - Agent'ta
contexts: [...]listesi varsa, listede olmayan bir context adını interpole etmekAgent context access was denied: xhatası verir.
İnterpolasyon yalnızca prompt/role string'lerinde çalışır — normal bir atamadaki string literal'da {x} düz metindir. Orada birleştirme için + kullan.
Expression dili
Operatörler ve öncelik
| Öncelik | Operatör | Notlar |
|---|---|---|
| 1 (en yüksek) | ( ) | Gruplama. |
| 2 | fn(...) · .field · [i] | Fonksiyon çağrısı, alan erişimi ve indeksleme. Çağrı sonucunda zincirlenirler — aşağıya bak. |
| 3 | ! | Mantıksal değil (önek). |
| 4 | * / | Soldan birleşimli. |
| 5 | + - | Soldan birleşimli. + string birleştirmesi de yapar. |
| 6 | == === != !== < <= > >= | Her expression bağlamında geçerlidir. Sıralama karşılaştırmaları iki tarafın da sayı olmasını ister. |
| 7 | && | Mantıksal ve. ||'dan daha sıkı bağlar. Soldan sağa kısa devre yapar. |
| 8 | || | Mantıksal veya. Soldan sağa kısa devre yapar. |
| 9 (en düşük) | ?? | Null-coalescing. v0.10.0. Parantezsiz &&/|| ile karıştırılamaz. |
Çağrı sonucunu doğrudan okumak
Çağrı bir ifadedir, dolayısıyla döndürdüğü şey yerinde okunabilir — ara değişken gerekmez:
flag = mk(1).flag // alan deep = mk(1).inner.deep // zincir first = mk(1).arr[0] // alan, sonra indeks pick = mk(1).arr[idx(0)] // indeksin kendisi de çağrı olabilir name = schema.describe(Doc).name // standard library çağrısı da
Bu, where, when ve @valid içinde de geçerlidir — @valid(v => chk(v).ok) gibi çıplak boolean biçimi dahil. v0.11.0'a kadar çağrı sonucunda yalnız indeksleme çalışıyordu; alan erişimi çağrıyı tanımsız değişken olarak bildiriyor, standard library biçimi ise suçu modülün kendisine atıyordu.
Sonuç üzerinde method çağırmak — f(x).method(...) — desteklenmiyor; @valid içinde parse zamanında @valid expression is not supported: … ile reddediliyor.
Dependency çağrısı hâlâ bir adımdır, ifade değil. dep.m().field burada meşrulaşmıyor; kendi mesajını koruyor, çünkü dependency method'u modele gidebilir ve ifade değerlendirici senkrondur:
Error: Dependency call 'd.m' must be its own step — write 'result = d.m(...)' first, then use 'result'; a dependency method may reach a model, so it cannot run inside an expression.
Orada olmayan bir alan sessizce null olmak yerine hata olarak kalır — mk(1).yok için Field 'mk(1).yok' was not found., değişkenin izlediği kuralın aynısı. Bir nesneyi sayıyla indekslemek kendi tip hatasını korur: mk(1) key must be a string, got int.
Tek bir değerlendirici var: atama, map/filter/reduce gövdesi, if/while, switch case > n, where, when ve @valid aynı öncelik kurallarını paylaşır.
b = a > 3 positive = filter item in items => item.amount > 0 actives = map item in items => item.active && item.amount > 0
&& ve || soldan sağa kısa devre yapar; nullable alanlarda önce "veri yoksa geç" koşullarını yazma kalıbı bu davranışa dayanır.
İkisi aynı seviyede değildir — çoğu dilde olduğu gibi && daha sıkı bağlar. Tanık, iki okumayı ayırt eden tek dizilim:
true || false && false // true — şöyle okunur: true || (false && false)
// eşit olsalardı: (true || false) && false = false?? ikisinin de altında yer alır ve parantezsiz karıştırılması sessizce çözülmek yerine adıyla reddedilir — a ?? b || c'nin okunuşu insan için gerçekten belirsizdir, dil hangisini kastettiğini söylemeni ister:
Error: '??' cannot be mixed with '&&' or '||' without parentheses: n ?? b || b
Parantezli iki biçim de meşrudur: (n ?? b) || b ve b || (n ?? b).
?? — null için varsayılan
a ?? b, a null değilse a'ya, null ise b'ye değerlenir. Sağ taraf yalnızca gerektiğinde değerlendirilir; tıpkı && ve || gibi.
amount = raw_amount ?? 0.0 // nullable alan, varsayılanla label = (title ?? subtitle) ?? "-" // soldan sağa zincirlenir total = reduce item in items with 0 => acc + (item.amount ?? 0)
Var olma sebebi şu: || bu işi yapamaz. Luma'da || katı boolean'dır, dolayısıyla null bir değer üzerinde value || 5 varsayılan vermek yerine Boolean if condition expected: value ile düşer. Nullable alanlar ise extraction'ın olağan hâli — T | null, bir şemanın modele "bulamadım" dedirtme biçimidir — yani varsayılan vermek bir operatörü hak ediyor.
?? orada olmayan bir alanı kurtarmaz. Tanımsız bir alanı okumak kendi hatasıdır ve öyle kalır:
Error: Field 'm.missing' was not found.
Bu red bilinçlidir — alan adındaki bir yazım hatası sessizce varsayılana dönüşmemeli. ?? "değer null geldi" sorusunu yanıtlar; "anahtar yok" bundan başka bir sorudur.
İfade yazılabilen her yerde geçerlidir: atama, pure fn gövdesi, agent method'u, nesne ya da liste literal'inin içi, where, when, @valid, map/filter/reduce, if/while. Bu bölümün dayandığı "tek evaluator" sözünün aynısı.
Metin UTF-16 olarak saklanır ve karşılaştırılır; v0.11.1'den beri BMP dışı bir karakter her metin konumunda sağlam kalır: string literal'leri, @desc ve @prompt argümanları, enum değerleri, test literal'leri, where/@valid içindeki literal'ler. Yorumlarda zaten çalışıyordu.
v0.11.1'e kadar tek bir yapıştırılmış emoji parser'ı kaydırıyor ve hata başka bir yerde yüzeye çıkıyordu — şema bloğunun içinde hiç satır vermeyen Parser block heading could not be restored., method gövdesinde ise sonraki bir satırı gösteren Unrecognized top-level line. Bir belgeden cümle kopyalamak bunu tetiklemeye yetiyordu.
Uçtan uca sağlam gider: provider'a ulaşan description, --fmt çıktısı ve transpile edilen artifact aynı kod noktalarını taşır.
İstisna tanımlayıcılardır. Şema, alan, fonksiyon ya da değişken adı astral karakter içeremez; red gürültülüdür, sessiz değil — şema adı olarak Out😀 parse zamanında düşer.
! tekli operatörü && ve ||'dan daha sıkı bağlar: !a && b ifadesi (!a) && b demektir. !(a && b) istiyorsan parantezi açıkça yaz.
Ayrıca ! yalnızca boolean üzerinde çalışır; sayı veya string verirsen Boolean if condition expected: … hatası alırsın.
Karmaşık bir predicate'i okunaklı tutmak için hâlâ pure fn kullanabilirsin:
positive = filter item in items => item.amount > 0 large = filter item in items => item.amount > 100 && item.active
pure fn isEligible(item: LineItem) -> bool {
if item.amount == null {
return false
}
return item.amount > 100 && item.active
}
eligible = filter item in items => isEligible(item)Literal'ler
| Tür | Örnek | Not |
|---|---|---|
| Sayı | 42 · 3.14 · -7 | Tümü JS number'dır; int kontrolü tip doğrulamasında yapılır. |
| String | "metin" | Yalnızca çift tırnak. \n, \t, \\, \" kaçışları geçerlidir. |
| Bool | true · false | |
| Null | null | |
| Array | [1, 2, 3] | Elemanlar expression olabilir. |
| Object | { name: "Ayşe", n: 1 } | Anahtar tırnaklı da yazılabilir: { "name": "Ada" }. |
| Schema referansı | Invoice | Bir schema adı expression'da geçtiğinde std/schema fonksiyonlarına verilebilecek referansa dönüşür. |
String birleştirme
root = "@" + folder // "@0213" label = "adet: " + length(list) // the number is appended as a string automatically path = root + "/ext.json"
+ operatörü JavaScript semantiği taşır: taraflardan biri string ise sonuç string olur.
Import gerektirmeyen fonksiyonlar
Aşağıdaki 11 fonksiyon dilin içindedir ve import olmadan her yerde (agent method'u, pure fn, where, @valid) çağrılabilir. Bunların dışındaki her şey standard library üzerinden gelir.
Bu 11 tanesi dilin kendisine ait. Arkalarında runtime, standard library'nin sardığı host implementasyonlarını da taşır — num.round için numericRound, http.get için httpGet, num.approximatelyEqual için numericApproximatelyEqual, ve her modül için aynısı. Onlar da import olmadan çözülür ve v0.9.0'dan beri bu bir kaza değil resmî (DECISIONS.md D35): çalışan bir yüzey ya desteklenir ya erişilemez olur, bu çalışıyor.
Desteklenmek tercih edilmek demek değil. Bu dokümantasyonun kullandığı yazım modül biçimidir; bir çağrının hangi kütüphaneden geldiğini söyleyen ve modül yeniden adlandırıldığında ayakta kalan da odur. Çıplak ada bir sebebin olduğunda uzan — tek bir çağrı için import gerektirecek bir where kuralı gibi — varsayılan olarak değil.
String'in UTF-16 birimi cinsinden uzunluğu ya da array eleman sayısı. Başka tip verilirse hata. Astral bir karakter — emoji, CJK uzantı ideografı — iki birimdir, yani length("😀") 2'dir; @length kuralları da aynı birimle ölçer.
n = length("Luma") // 4
m = length([1, 2, 3]) // 3String için alt-dize, array için eleman araması. Array'de karşılaştırma referans/ilkel eşitliğidir.
contains("Ayşe Yılmaz", "Ayşe") // true
contains(["vip", "active"], "vip") // trueBüyük/küçük harfe çevirir.
Yalnızca baştaki ve sondaki boşlukları siler. Ortadaki boşluklara dokunmaz.
trim(" Ayşe Yılmaz ") // "Ayşe Yılmaz"Önek / sonek kontrolü.
Tüm eşleşmeleri değiştirir (regex değil, düz metin).
replace("a-b-c", "-", "/") // "a/b/c"String array'i birleştirir. Array elemanlarının tümü string olmalıdır.
Tekrarları atar. Karşılaştırma JSON.stringify üzerinden yapılır, yani nesneler de derin karşılaştırılır.
İç içe array'leri tamamen düzleştirir (sonsuz derinlik).
flatten([["a"], ["b", ["c"]]]) // ["a", "b", "c"]
Kontrol akışı
if / else
if stats.words > 3 {
bonus = tool calculate({ a: stats.words, b: 2, operation: "multiply" })
} else if stats.words > 0 {
bonus = tool calculate({ a: stats.words, b: 1, operation: "add" })
} else {
bonus = tool calculate({ a: 0, b: 0, operation: "add" })
}Koşul boolean üretmelidir; sayısal karşılaştırmalarda iki taraf da sayı olmalıdır, aksi halde Numeric if comparison expected. hatası alınır.
switch
switch counter {
case 1 {
result = tool calculate({ a: 1, b: 0, operation: "add" })
return result
}
case > 1 {
result = tool calculate({ a: 0, b: 0, operation: "add" })
return result
}
case _ {
return { result: 0 }
}
}| case biçimi | Anlamı |
|---|---|
case 1 · case "metin" | Değer eşitliği (===). |
case > 10 | Operatörle başlayan case, switch ifadesine karşı karşılaştırma olarak değerlendirilir. < <= > >= == != kullanılabilir. |
case _ | Her zaman eşleşen joker; ilk eşleşen kazanır. |
default { … } | Hiçbir case eşleşmezse çalışır. |
Bir switch en az bir case veya default içermelidir. Case'ler sırayla denenir, ilk eşleşen çalışır ve düşme (fall-through) yoktur.
match
match, tagged union değerinin çalışma zamanındaki etiketine göre dallanır. Her case union'ın bir üyesini adlandırır ve değeri yeni bir isme bağlar; o dalın içinde bu isim üyenin kendi somut tipini taşır, dolayısıyla alanlarına doğrudan erişilir ve alanlar nullable değildir.
schema Invoice {
invoice_number: string
}
schema Receipt {
reference: string
}
schema Document = Invoice | Receipt tagged by kind
agent Router {
fn route(document: Document) -> Result {
match document {
case Invoice invoice {
return { value: invoice.invoice_number }
}
case Receipt receipt {
return { value: receipt.reference }
}
}
}
}| Biçim | Kural |
|---|---|
match value { … } | value, parser'ın görebildiği bir tagged union tipi taşımalıdır — örneğin method parametresi, tipli bir yerel değişken ya da bir ask sonucu. Tipi görünmeyen bir değerde: Match expression 'plain' requires a tagged union type. |
case Member name { … } | Etiket Member'a eşitse çalışır; değeri name adıyla, o üyenin somut tipiyle bağlar. Başka bir üyeye ait alana erişmek çalıştırmadan önce yakalanır: Router.route: schema field 'invoice.reference' was not found. |
| Bütünlük | Her üyenin kendi case'i olmalıdır; tekrarlanan case reddedilir: Match case 'Invoice' is duplicated. |
default yok | Yakalayıcı dal yoktur — bütünlük kuralını anlamlı kılan da tam olarak budur. |
Eksik üye, default kullanımı ve union dışından bir schema adlandıran case — üçü de parse zamanında reddedilir:
Error: Match for tagged union 'Document' must handle member 'Receipt'. Error: Match does not allow a default branch. Error: Match case 'Result' is not a member of tagged union 'Document'.
Parser'ın görebildiği ve göremediği
"Parser'ın görebildiği bir tip" kuralın tamamıdır ve bunu somutlaştırmaya değer. Alan yolu her adımı bildirilmiş bir alanı adlandırdığı sürece istediğiniz derinlikte çözülür; match extraction.data ve match envelope.inner.data araya yerel değişken koymadan çalışır. for döngüsü bağı da çözülür — koleksiyonun eleman tipi bağa taşınır — ve doküman listesini yönlendirmeyi sıradan bir döngü hâline getiren şey budur:
for document in batch.documents {
match document {
case Invoice invoice { … }
case Receipt receipt { … }
}
}Parser'ın reddettiği şey, hiçbir şekilde bir union'a çözemediği ifadedir — union olmayan bir alan ya da any tipli bir değer. O zaman Match expression 'x' requires a tagged union type. alırsınız ve çözüm döngüyü yeniden kurmak değil, değere parser'ın okuyabileceği bir tip vermektir.
Union listesini katlamanın iki yolu
Bir match dalı sıradan bir bloktur: atama yapabilir ve atama daldan sağ çıkar, dolayısıyla liste boyunca biriktirme başka her yerde nasıl yazılıyorsa öyle yazılır. Yalnızca bağlanan member değişkeni (invoice, receipt) dala özeldir ve sonrasında yoktur.
agent Desk {
fn tally(documents: Document[]) -> Tally {
invoices = 0
receipts = 0
for document in documents {
match document {
case Invoice invoice { invoices = invoices + 1 }
case Receipt receipt { receipts = receipts + 1 }
}
}
return { invoices: invoices, receipts: receipts }
}
}Diğer yol pure fn'dir; o, union'ı doğrudan açmaya yetkilidir: içinde tag ve member alanları sıradan alan okumasıdır, match de yoktur bütünlük kontrolü de.
pure fn label(document: Document) -> string {
if document.kind == "Invoice" {
return document.invoice_number
}
return document.reference
}
agent Desk {
fn labels(documents: Document[]) -> Labels {
found = map document in documents => label(document)
return { values: found }
}
}Aynı document.kind bir agent method'unda yazılırsa reddedilir — Desk.labels: schema field 'document.kind' was not found. — çünkü union schema'sının kendine ait alanı yoktur. Yani seçim neyin denetlenmesini istediğinizle ilgili: agent method'undaki match bütünlük açısından denetlenir, union'a bir member eklediğinizde onu ele alana kadar her match düşer. pure fn denetlenmez; map ve reduce içine girebilmesini sağlayan da budur, yeni bir member'ın sessizce son dala düşecek olmasının sebebi de. Member kümesi önemliyse döngüye, pipeline'ın şekli önemliyse pure fn'e uzanın.
İkisi birbirine benzer ama yer değiştiremez. switch, elinde zaten olan bir değere göre dallanır — bir string, bir sayı, bir alan — ve birden fazla olasılık arasından tek bir sonuç üretmenin yoludur. match ise union değerinin taşıdığı etikete göre dallanır ve onu tüketmenin yoludur; union'ı somut üyesine açan tek yapı odur.
Yani tagged union döndüren bir method içeride genellikle switch kullanır, sonucu alan taraf ise onun üzerinde match kullanır. → Union dönüş tipi
for / while / break / continue
for page in pages {
files = io.readFiles([page])
part = ask "Bu sayfayı Markdown'a çevir: {files}" -> Page
markdown = markdown + part.text + "\n\n"
}
i = 0
while i < 3 {
i = i + 1
if i == 2 {
continue
}
if i > 2 {
break
}
}| Kısıt | Değer |
|---|---|
for kaynağı | Array olmalıdır; değilse the for source must be an array hatası. |
Agent while limiti | Varsayılan 100 yineleme; loop_limit ile değiştirilir. Aşılırsa Agent loop limit exceeded. |
pure fn while limiti | Sabit 1000 yineleme. |
break / continue | Yalnızca loop içinde. Dışarıda kullanılırsa parse zamanında reddedilir. |
while true | İçinde break yoksa parser infinite while loop without a break hatası verir. |
try / catch
try {
data = ask "1-3 arası sayısal item üret." -> Data
clean = map item in data.items => item.value
total = reduce item in clean with 0 => acc + item
result = tool calculate({ a: total, b: 0, operation: "add" })
return result
} catch error {
fallback = tool calculate({ a: 0, b: 0, operation: "add" })
return fallback
}catch zorunludur (try tek başına kullanılamaz). Yakalanan değişken yapılandırılmış bir hata nesnesidir:
{ "code": "TOOL_INPUT_INVALID", "message": "calculate.input.a must be a number.", "retryable": false }Tool ve model hataları code / message / retryable (ve varsa details) taşır; diğer hatalar yalnızca message içerir. Bu alanlara std/agent veya std/json ile erişilir:
import agent from "std/agent"
} catch error {
code = agent.errorCode(error)
if agent.isRetryable(error) {
...
}
}try bloğu içindeki return, break ve continue yakalanmaz — normal şekilde dışarı taşınır. catch yalnızca gerçek hataları yakalar.
İndeksleme (v0.4.0)
Listeler ve map'ler [] ile indekslenir — okuma ve yazma, zincirler dahil:
first = rows[0] name = rows[i].customer.name value = m["key"] buckets[key] = col.append(existing, row) // yazma: mutasyon, x = v ile aynı scope kuralları xs[0].field = 3 // ✗ Error: Unrecognized step in A.run (line 5): xs[0].field = 3
Her sınır durumu adlandırılmış hatadır — indeksleme asla sessiz null dönmez:
| Durum | Davranış |
|---|---|
Aralık dışı okuma (xs[5], uzunluk 2) | xs[5] is out of bounds (length 2). |
| Aralık dışı yazma | xs[5] is out of bounds (length 2); use col.append to grow a list. |
| Okumada olmayan map anahtarı | m["missing"] is not present. |
| Yazmada olmayan map anahtarı | Anahtar oluşturulur — map bunun içindir. |
| Yanlış türde indeks | Ne geldiğini söyleyen adlandırılmış tip hatası. |
| Container olmayanı indekslemek | <path> cannot be indexed; only a list or a map supports [] access. |
v0.4.0'dan önce method gövdesindeki tanınmayan assignment-biçimli bir satır — en ünlüsü m[k] = v — sessizce yok sayılıyordu. Bu sınıf kapandı: m[k] = v çalışır, diğer tüm tanınmayan assignment-biçimli satırlar (m[k] += 2, m.b = 3) adlandırılmış parse hatasıdır.
Koleksiyon işlemleri
map, filter ve reduce dilin yerleşik statement'larıdır — fonksiyon değil, kendi söz dizimleri vardır ve her biri tek bir statement'tır — diğer statement'lar gibi operatörden bölünerek satırlara yayılabilir.
actives = filter item in items => item.active names = map item in actives => upper(item.name) total = reduce line in lines with 0 => acc + line.amount
| Biçim | Sonuç |
|---|---|
x = filter i in COLLECTION => EXPR | EXPR truthy olan elemanların array'i. i her yinelemede geçerli elemandır. |
x = map i in COLLECTION => EXPR | Her eleman için EXPR sonucundan oluşan array. |
x = reduce i in COLLECTION with INITIAL => EXPR | Birikimli tek değer. acc ayrılmış değişkendir ve birikeni tutar. |
Kaynak array değilse the map/filter/reduce source must be an array hatası alınır.
=> sonrası mantıksal satırın sonuna kadar okunur — operatörle başlayan devam satırları dahildir — ve expression dili ile değerlendirilir; karşılaştırma ve mantıksal operatörler dahil. Blok açamazsın; çok adımlı bir predicate için pure fn kullan:
pure fn isLarge(line: LineItem) -> bool {
if line.amount > 100 {
return true
}
return false
}
large = filter line in lines => isLarge(line)Zincirleme
active = filter item in items => item.active names = map item in active => upper(item.name) count = length(names)
Her adım kendi satırında ayrı bir değişkene atanır; zincir sözdizimi (.filter().map()) yoktur.
Bu üçü koleksiyon statement yüzeyinin tamamıdır; burası dilin bittiği yerdir, koleksiyonların bittiği yer değil. Sıralama, gruplama, chunk'lama, dilimleme, tekilleştirme ve toplama fonksiyondur — std/collections içinde 25 tane, konvansiyon gereği col olarak import edilir; indeksleme bölümündeki col.append'in ardındaki col da odur. Yani "bunu nasıl sıralarım, nasıl gruplarım" sorusunun cevabı eksik bir statement değil; bir sonraki satırda adlandırılmış bir yerel değişken ve bir col.* çağrısıdır.
pure fn ve io fn
Üst düzey fonksiyonlar deterministik yardımcılardır. Schema where kurallarında, @valid içinde ve agent method'larında çağrılabilirler.
pure fn totalAmount(items: LineItem[]) -> float {
total = reduce item in items with 0 => acc + item.amount
return total
}
schema Invoice {
items: LineItem[1..100]
total_amount: float
where totalAmount(items) == total_amount
}| Kural | pure fn | io fn |
|---|---|---|
ask / tool / parallel / dependency çağrısı | ✗ yasak | ✗ yasak |
std/io çağrısı | ✗ yasak (parse zamanında reddedilir) | ✓ serbest |
where içinde kullanım | ✓ | ✗ (where is deterministic and cannot use IO) |
@valid içinde kullanım | ✓ | ✗ |
return zorunluluğu | ✓ en az bir return | ✓ |
| while limiti | 1000 | 1000 |
| Özyineleme | ✗ (Pure fn cycle found) | ✗ |
Parametreler ve doğrulama
- Parametreler tipli olmak zorundadır ve çağrı anında doğrulanır.
- Opsiyonel parametre:
x?: string. Verilmezse değerinullolur. - Dönüş değeri de bildirilen tipe göre doğrulanır.
- Parametrelere field annotation'ları eklenebilir ve bunlar çağrı anında gerçekten uygulanır:
pure fn f(code: string @match("^[A-Z]+$"), score: float @range(0, 10)) -> bool { … }. Aynı kuralio fn, agent method vetoolparametreleri için de geçerlidir. Bir annotation burada bilinçli olarak yok sayılır:@severitybir kuralı yalnızca değerin sonuç olarak denetlendiği yerde yumuşatır. Parametre bir girdidir, dolayısıyla kural sert kalır — şerhli değer geçemez.
export ve gizlilik
// helpers.luma
export pure fn normalize(x: string) -> string {
return lower(trim(x))
}
pure fn internal(x: string) -> string { // no export → cannot be called from outside the module
return upper(x)
}Import ve modüller
import "std/math" // unnamed: abs(...), sum(...) directly import math from "std/math" // namespaced: math.abs(...) import "./helpers.luma" // relative module inside the project
| Kural | Davranış |
|---|---|
| Modül içeriği | Yalnızca schema ve pure fn/io fn. Agent, tool, context, input veya operation içeren dosya import edilemez. |
std/ çözümlemesi | luma/std/<name>.luma dosyasına eşlenir. |
| Relative çözümleme | Import eden dosyaya göre; uzantı yoksa .luma eklenir. Proje kökünün dışına çıkamaz. |
| Proje kökü | Varsayılan olarak entry dosyanın kendi dizini. Entry'nin yanındaki bir luma.config.json'a { "project_root": ".." } koyarak başka bir yere sabitlenir — engine/ + paket/ kardeş dizin düzeni böyle mümkün olur. Sabitlenen kökün dışına çıkmak yine adıyla reddedilir: hapis taşınır, kalkmaz. std/io'nun kökü ayrı bir sözleşmedir ve etkilenmez. |
| Cache | Aynı dosya bir kez ayrıştırılır ve paylaşılır. |
| Cycle | Reddedilir: Import cycle found: a -> b -> a. |
| İsim çakışması | Farklı iki modül aynı schema adını bildirirse Import schema conflict: Name ile reddedilir. Aynı modülün iki yoldan gelmesi çakışma değildir — aşağıdaki elmas notuna bak. |
export olmayan fonksiyon | Modül-özel kalır; dışarıdan çağrılırsa A private pure fn cannot be called from outside. |
Agent method gövdesinde x = name.function(...) biçimindeki bir satır hem modül çağrısı hem dependency agent çağrısı olabilir. Parser bunları, dosyanın gerçekten import ettiği namespace'lere bakarak ayırır; alias'a istediğin adı verebilirsin:
import mat from "std/math"
schema S { d: float }
agent A {
fn f() -> S {
d = mat.abs(0 - 7) // ✓ works — 'mat' is an imported namespace
return { d: d }
}
}Ne import edilmiş bir namespace ne de tanımlı bir dependency olan bir ad hâlâ net biçimde bildirilir: 'nosuch' is not defined as a dependency. Aynı ayrım pure fn gövdesinde de geçerlidir.
Standard library ve pure fn çağrıları ifade yazılabilen her yere yazılabilir, return dahil. uses ile bildirilmiş bir dependency üzerinden çağrı yazılamaz: yalnızca atama konumunda çözülür.
r = reviewer.review(doc) // böyle
return { amount: r.amount }
return reviewer.review(doc) // böyle değil
return { amount: reviewer.review(doc).amount } // bu da değilReddedilen iki biçim de program başlamadan, aynı mesajla düşer:
Error: Dependency call 'a.f' must be its own step — write 'result = a.f(...)' first, then use 'result'; a dependency method may reach a model, so it cannot run inside an expression.
Sebep, std/http köprüsünün ödediği bedelin aynısı: ifade değerlendirici uçtan uca senkron, agent method ise değil — gövdesi bir ask içerebilir ve modele gider. Senkron bir değerlendirici bunu bekleyemez; dolayısıyla dependency çağrısı, ifadenin ürettiği bir değer değil, runtime'ın sıraya koyduğu bir adımdır. Bu bir sözleşmedir, kaldırılmayı bekleyen bir kısıt değil.
Bir schema where ve @valid kurallarını — bir pure fn ise gövdesinin tamamını — kendisini bildiren modülün alias'larıyla çözer, entry dosyanın ne import ettiğiyle değil; ve bu import zinciri ne kadar derin olursa olsun geçerlidir. Böylece bir modül kendi std bağımlılıklarını sahiplenebilir, onu sonunda tüketen programın bunları bilmesi gerekmez; bir paketi onu yükleyen engine'den ayrılabilir tutan şey de budur.
export edilen her şey, aradan kaç modül geçerse geçsin, onu sonunda tüketen programa ulaşır:
kural.luma (export pure fn kontrol) ──> paket.luma ──> programın
✓ şemalar buraya ulaşır
✓ kontrol() dev0.7.0'a kadar ikisi simetrik değildi — bir fonksiyon yalnızca onu doğrudan import eden modüle görünüyordu, yani araya konan bir paketleyici modül onu gizliyordu. O asimetri kalktı: katmanlı bir paket kurallarını bir modüle, tiplerini başkasına koyup ikisini tek bir girişten yeniden ihraç edebilir.
İki şey değişmedi. export edilmemiş bir fn, zincir nasıl kurulursa kurulsun modül-özel kalır. Ve gerçekten çakışan bir ad — aynı fonksiyon adını export eden iki farklı modül — hâlâ adıyla reddedilir: Import pure fn conflict: kontrol. Aynı modülün iki yoldan gelmesi çakışma değildir, yani elmas import sorun çıkarmaz.
Error: Unknown function 'yardimci': yardimci(1)
Hiçbir şeye çözülmeyen bir çağrının verdiği şey budur — çağrıya benzeyen bir değişken hakkında değil, fonksiyon hakkında konuşan bir mesaj.
Paylaşılan yapı taşları kendi modülünde durur; bu da onları kullanan iki üyenin aynı dosyayı import etmesi demektir — ve birleştiren program o modülü iki kez alır:
belge.luma ─┬─> fatura.luma ─┐
└─> pos_fisi.luma ─┴─> ortak.luma (Taraf)İkinci geliş no-op'tur, çünkü bir schema kendisini bildiren modülün kimliğini taşır. Yalnızca farklı iki modülün aynı adı bildirmesi çakışmadır, ki kontrol bunun için var. Bu olmadan tip-başına-modül düzeni, üyeler bir şey paylaştığı anda çöker — paylaşmak da zaten paylaşılan modülün var olma sebebidir.
Zorunluluk değil, konvansiyon: standard library sayfası std modüllerini kanonik alias'larıyla import eder (import math from "std/math", import str from "std/string", import col from "std/collections", import num from "std/numeric"). Standard library sayfasındaki tüm örnekler bu alias'ları kullanır.
Statik analiz
Program çalışmaya başlamadan önce parser aşağıdaki kontrolleri yapar. Bunların çoğu hata, bir tanesi uyarıdır.
| Kontrol | Sonuç | Örnek mesaj |
|---|---|---|
| Ulaşılmaz kod | hata | A.f: unreachable code found. |
Loop dışında break/continue | hata | A.f: break can only be used inside a loop. |
Sonsuz while true | hata | A.f: infinite while loop without break. |
| Bilinmeyen schema alanı | hata | A.f: schema field 'x.yok' was not found. |
| Method gövdesinde alan satırı | hata | Schema field line in A.f (line 7): 'kod: string' — field declarations belong in a schema block. |
| Kapanmamış çok satırlı literal | hata | Unterminated multi-line literal: 'x = { a: 1' — continuation lines must start with ','. |
| Çıplak karşılaştırma | uyarı | Warning: result of 'xs[0] == 1' is ignored; a bare comparison has no effect. |
| Agent dependency cycle | hata | Agent dependency cycle found: A -> B -> A |
| Pure fn cycle | hata | Pure fn cycle found: f -> g -> f |
| Schema composition cycle | hata | Schema composition cycle found: … |
| Import cycle | hata | Import cycle found: … |
ask ile return'ün birlikte kullanımı | hata | Agent method f cannot use ask and return together. |
| Method dönüş tipi ≠ terminal ask schema'sı | hata | fn f return schema differs from the ask schema. |
Bütün olmayan match | hata | Match for tagged union 'Document' must handle member 'Receipt'. |
match içinde default | hata | Match does not allow a default branch. |
| Her dalın karşılamadığı union dönüşü | hata | Switch in method f requires a default branch for tagged union 'Document'. |
| İzin verilmeyen tool'un direct çağrısı | hata | Agent 'A' does not allow tool 'x' to be called directly. |
| Kullanılmayan değişken | uyarı | Warning: variable 'x' is unused in A.f. |
Bunlara ek olarak model, run_policy, tool, agent ve schema referansları çözümlenir: tanımsız bir model, tool veya agent adı programın hiç başlamamasına yol açar.