Luma public docs
English

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.

DeklarasyonBiçimAmaç
importimport name from "std/math"Pure modül yükler.
inputinput topic: stringCLI girdisi bildirir (string veya file).
contextcontext b = file("docs/b.md")Dosya içeriğini kaynak kimliğiyle bağlar.
schemaschema Name { … }Veri sözleşmesi tanımlar. → Schema
schema … = A + Bschema Admin = User + AuditSchema composition.
modelmodel Primary { … }Model/provider profili. → Model
run_policyrun_policy R { … }Çok modelli çalıştırma stratejisi.
tooltool name(p: t) -> Out { … }Tool contract'ı. → Tool
pure fn / io fnpure fn f(x: int) -> int { … }Deterministik fonksiyon.
agentagent Name { fn m() -> S { … } }Method taşıyan birim. → Agent
datasourcedatasource Name { … }Veritabanı bağlantı contract'ı. → Veri
queryquery Name(p: t) -> Row[] { … }Datasource üzerinde tipli okuma. → Veri
commandcommand Name(p: t) -> int { … }Tipli yazma; side_effects bildirmek zorundadır. → Veri

Operation'lar (üst düzey, sırayla çalışır)

luma
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üzey yalnızca deklarasyon ve operation alı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

Satır başına bir statement — ama statement sürdürülebilir

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.

✓ Doğru
schema Result {
  count: int
  total: float
}

pure fn f() -> Result {
  a = 1
  b = 2
  return { count: a, total: b }
}
✗ Yanlış
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:

luma
schema Answer {
  summary: string
    @desc("Yalnızca verilen belgeye dayalı özet")
    @length(20, 400)
  confidence: float [0..1]
}
where ve when operatörden bölünür, keyword'den değil

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ü.

Bu hata sınıfının bir aracı var: --fmt

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: type olur: iki noktadan önce boşluk yok, sonra tam bir boşluk. Sütun hizalaması korunmaz — score : float da model: Primary da 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.
lumaönce — girinti, alan aralığı ve devam satırları hep birden bozuk
// 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()
lumasonra — luma grader.luma --fmt
// 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

luma
input topic: string
input document: file
TürNasıl verilirProgramda
string--topic "teslimat süresi" veya --topic=teslimatDeğer doğrudan değişken olur.
file--document docs/contract.mdDosya 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

luma
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}" -> Answer

context, 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

luma
// 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

luma
number   = 42
text     = "Luma"
flag     = true
empty    = null
list     = [1, 2, 3]
object   = { name: "Ayşe", active: true }
field    = object.name

Tipli 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.

luma
request: CalcInput = { a: 4, b: 6, operation: "add" }
result: CalcResult = tool calculate(request)
items: LineItem[] = [{ amount: 10 }, { amount: 5 }]
Tip söz dizimi burada sınırlıdır

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}:

luma
ask "Şu konuyu özetle: {topic}\n\nBelge:\n{document}" -> Summary
ask "Önceki bulgu: {analysis.finding} (güven {analysis.confidence})" -> Report
  • Değer string ise doğrudan yazılır; değilse JSON.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 ise Field '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 etmek Agent context access was denied: x hatası 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

ÖncelikOperatörNotlar
1 (en yüksek)( )Gruplama.
2fn(...) · .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:

luma
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.

İki şekil dışarıda kalıyor

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.

Karşılaştırma her expression bağlamında geçerlidir

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.

luma
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.

Null DEĞERİ ile ilgilidir, eksik anahtarla değil

?? 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ı.

Astral karakterler — emoji, CJK uzantıları — her metin konumunda çalışır

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.

! önceliği ve boolean zorunluluğu

! 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:

✓ Basit koşul: doğrudan yaz
positive = filter item in items => item.amount > 0

large = filter item in items => item.amount > 100
  && item.active
✗ Karmaşık koşul: fonksiyona taşı
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ÖrnekNot
Sayı42 · 3.14 · -7Tü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.
Booltrue · false
Nullnull
Array[1, 2, 3]Elemanlar expression olabilir.
Object{ name: "Ayşe", n: 1 }Anahtar tırnaklı da yazılabilir: { "name": "Ada" }.
Schema referansıInvoiceBir schema adı expression'da geçtiğinde std/schema fonksiyonlarına verilebilecek referansa dönüşür.

String birleştirme

luma
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.

Standard library'nin host implementasyonları da öneksiz erişilebilir

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.

length(value: string | any[]) → int

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])   // 3
contains(value: string | any[], search: any) → bool

String 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")  // true
upper(value: string) → string · lower(value: string) → string

Büyük/küçük harfe çevirir.

trim(value: string) → string

Yalnızca baştaki ve sondaki boşlukları siler. Ortadaki boşluklara dokunmaz.

trim("  Ayşe  Yılmaz  ")   // "Ayşe  Yılmaz"
startsWith(value: string, prefix: string) → bool · endsWith(value: string, suffix: string) → bool

Önek / sonek kontrolü.

replace(value: string, search: string, replacement: string) → string

Tüm eşleşmeleri değiştirir (regex değil, düz metin).

replace("a-b-c", "-", "/")   // "a/b/c"
join(values: string[], separator: string) → string

String array'i birleştirir. Array elemanlarının tümü string olmalıdır.

unique(values: any[]) → any[]

Tekrarları atar. Karşılaştırma JSON.stringify üzerinden yapılır, yani nesneler de derin karşılaştırılır.

flatten(values: any[]) → any[]

İç içe array'leri tamamen düzleştirir (sonsuz derinlik).

flatten([["a"], ["b", ["c"]]])   // ["a", "b", "c"]

Kontrol akışı

if / else

luma
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

luma
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çimiAnlamı
case 1 · case "metin"Değer eşitliği (===).
case > 10Operatö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.

luma
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çimKural
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ükHer üyenin kendi case'i olmalıdır; tekrarlanan case reddedilir: Match case 'Invoice' is duplicated.
default yokYakalayı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:

luma
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.

luma
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.

luma
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.

switch üretir, match tüketir

İ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

luma
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ıtDeğer
for kaynağıArray olmalıdır; değilse the for source must be an array hatası.
Agent while limitiVarsayılan 100 yineleme; loop_limit ile değiştirilir. Aşılırsa Agent loop limit exceeded.
pure fn while limitiSabit 1000 yineleme.
break / continueYalnı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

luma
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:

jsonhata değişkeninin içeriği
{ "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:

luma
import agent from "std/agent"

} catch error {
  code = agent.errorCode(error)
  if agent.isRetryable(error) {
    ...
  }
}
return ve akış sinyalleri

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:

luma
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:

DurumDavranış
Aralık dışı okuma (xs[5], uzunluk 2)xs[5] is out of bounds (length 2).
Aralık dışı yazmaxs[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 indeksNe geldiğini söyleyen adlandırılmış tip hatası.
Container olmayanı indekslemek<path> cannot be indexed; only a list or a map supports [] access.
Sessiz yutmanın kapanışı

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.

luma
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çimSonuç
x = filter i in COLLECTION => EXPREXPR truthy olan elemanların array'i. i her yinelemede geçerli elemandır.
x = map i in COLLECTION => EXPRHer eleman için EXPR sonucundan oluşan array.
x = reduce i in COLLECTION with INITIAL => EXPRBirikimli 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.

Gövde tek bir expression olmalı

=> 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

luma
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.

luma
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
}
Kuralpure fnio 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 limiti10001000
Ö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ğeri null olur.
  • 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ı kural io fn, agent method ve tool parametreleri için de geçerlidir. Bir annotation burada bilinçli olarak yok sayılır: @severity bir 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

luma
// 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

luma
import "std/math"              // unnamed: abs(...), sum(...) directly
import math from "std/math"    // namespaced: math.abs(...)
import "./helpers.luma"        // relative module inside the project
KuralDavranış
Modül içeriğiYalnızca schema ve pure fn/io fn. Agent, tool, context, input veya operation içeren dosya import edilemez.
std/ çözümlemesiluma/std/<name>.luma dosyasına eşlenir.
Relative çözümlemeImport 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.
CacheAynı dosya bir kez ayrıştırılır ve paylaşılır.
CycleReddedilir: 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 fonksiyonModül-özel kalır; dışarıdan çağrılırsa A private pure fn cannot be called from outside.
Her namespace alias'ı çalışır

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.

Dependency çağrısı bir adımdır, ifade değil

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ğil

Reddedilen 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.

Şema da export edilen fn de zincirin tamamını kat eder

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() de

v0.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.

Elmas import sorun değil

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.

KontrolSonuçÖrnek mesaj
Ulaşılmaz kodhataA.f: unreachable code found.
Loop dışında break/continuehataA.f: break can only be used inside a loop.
Sonsuz while truehataA.f: infinite while loop without break.
Bilinmeyen schema alanıhataA.f: schema field 'x.yok' was not found.
Method gövdesinde alan satırıhataSchema field line in A.f (line 7): 'kod: string' — field declarations belong in a schema block.
Kapanmamış çok satırlı literalhataUnterminated multi-line literal: 'x = { a: 1' — continuation lines must start with ','.
Çıplak karşılaştırmauyarıWarning: result of 'xs[0] == 1' is ignored; a bare comparison has no effect.
Agent dependency cyclehataAgent dependency cycle found: A -> B -> A
Pure fn cyclehataPure fn cycle found: f -> g -> f
Schema composition cyclehataSchema composition cycle found: …
Import cyclehataImport cycle found: …
ask ile return'ün birlikte kullanımıhataAgent method f cannot use ask and return together.
Method dönüş tipi ≠ terminal ask schema'sıhatafn f return schema differs from the ask schema.
Bütün olmayan matchhataMatch for tagged union 'Document' must handle member 'Receipt'.
match içinde defaulthataMatch does not allow a default branch.
Her dalın karşılamadığı union dönüşühataSwitch in method f requires a default branch for tagged union 'Document'.
İzin verilmeyen tool'un direct çağrısıhataAgent 'A' does not allow tool 'x' to be called directly.
Kullanılmayan değişkenuyarı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.