Files
dexpr/docs/editor.md
2026-04-07 15:16:19 +03:00

9.2 KiB
Raw Permalink Blame History

Editor Modülü (codemirror-lang-dexpr)

Konum: editor/

CodeMirror 6 için dexpr dil desteği kütüphanesi. Syntax highlighting, tip-bazlı autocomplete ve error-tolerant parsing sağlar.


Mimari

editor/
  src/
    dexpr.grammar     ← Lezer gramer dosyası (kaynak)
    parser.js         ← Lezer tarafından üretilen parser (generated)
    parser.terms.js   ← Token tanımları (generated)
    tokens.ts         ← External tokenizer (else if → tek token)
    language.ts       ← LRLanguage tanımı + syntax highlighting tag'leri
    highlight.ts      ← Varsayılan renk teması
    completions.ts    ← Autocomplete: tip çıkarımı + metadata bazlı öneriler
    index.ts          ← Ana export: dexpr() fonksiyonu
  demo.ts             ← Test sayfası kaynak kodu
  demo.html           ← Test sayfası
  dexpr.grammar       ← (src altındaki asıl dosya)

İki Parser Stratejisi

Taraf Parser Amaç
Rust (execution) PEG (peg crate) AST üretimi → derleme → VM çalıştırma
Editor (web) Lezer Syntax highlighting, error recovery, autocomplete

Dil küçük olduğundan (7 keyword, ~20 operatör) iki gramer dosyasını sync tutmak kolaydır.


Lezer Grammar

Dosya: src/dexpr.grammar

Parse Kuralları

Kural ıklama
Program statement* — üst düzey kural
IfStatement if expr then stmts (else if expr then stmts)* (else stmts)? end
Assignment VariableName AssignOp expression veya VariableName (.PropertyName)+ AssignOp expression
PropertyAccess expression.PropertyName — nesne özellik erişimi
ExprStatement Bağımsız ifade
BinaryExpression İkili operatörler, öncelik sırasıyla
UnaryExpression Tekli - ve !
MethodCall expression.PropertyName(args)
FunctionCall VariableName(args)
ParenExpression (expression)

Operatör Önceliği (düşükten yükseğe)

  1. || — mantıksal OR
  2. && — mantıksal AND
  3. ==, !=, <, <=, >, >=, in — karşılaştırma
  4. +, - — toplama/çıkarma
  5. *, /, % — çarpma/bölme/mod
  6. ** — üs alma (sağdan birleşimli)
  7. -, ! — tekli operatörler (prefix)
  8. .method() — metod çağrısı
  9. name() — fonksiyon çağrısı

External Tokenizer

Dosya: src/tokens.ts

else if iki ayrı keyword olarak yazılır ama Lezer'da tek token olarak tanınır (elseIf). External tokenizer else + boşluk + if dizisini tespit edip tek token üretir. Bu sayede else if zinciri ile nested if arasındaki belirsizlik (ambiguity) ortadan kalkar.

Keyword Yönetimi

Keyword'ler @extend ile tanımlanır — identifier token'ından türetilir ama her zaman keyword olarak parse edilir. dexpr'de keyword'ler reserved'dır (if, then, else, end, in, true, false).

Error Recovery

Lezer GLR parser kullanır. Bozuk/yarım kod yazılırken:

  • Parse edilebilen kısımlar doğru tree node'ları üretir
  • Hatalı kısımlar (error) node'ları ile sarılır
  • Editör yarım kodda bile syntax highlighting ve autocomplete sunabilir

Syntax Highlighting

Dosya: src/language.ts (tag eşleştirme) + src/highlight.ts (renk teması)

Token → Tag Eşleştirmesi

Token Lezer Tag Varsayılan Renk
if, then, else, end, in, elseIf keyword #7c3aed (mor)
true, false bool #d97706 (turuncu)
"string", 'string' string #059669 (yeşil)
42, 3.14 number #2563eb (mavi)
// comment, /* comment */ lineComment / blockComment #9ca3af (gri, italic)
+, -, *, /, %, !, ||, && operator #dc2626 (kırmızı)
==, !=, <, <=, >, >=, =, ** compareOperator #dc2626 (kırmızı)
myVar variableName #1f2937 (koyu)
.method propertyName #0891b2 (cyan)
functionName() function(variableName) #9333ea (mor)

Host uygulama highlighting: false vererek varsayılan temayı devre dışı bırakıp kendi temasını kullanabilir.


Autocomplete

Dosya: src/completions.ts

Veri Kaynağı

Autocomplete verileri DexprLanguageInfo arayüzü ile sağlanır. Bu veri Rust tarafında LanguageInfo::to_json() ile üretilir. Detaylar: Language Info Modülü

interface DexprLanguageInfo {
  functions: FunctionInfo[];
  methods: Partial<Record<DexprType, MethodInfo[]>>;
  variables?: VariableInfo[];
}

Tip Çıkarımı (Type Inference)

Completions modülü Lezer syntax tree'sini kullanarak değişken tiplerini çıkarır:

  1. Config'den gelen tipler: variables dizisindeki her değişkenin tipi bilinir
  2. Assignment analizi: x = "hello"x: String, y = 42y: Number
  3. Method dönüş tipi: z = name.split(",")z: StringList (split dönüş tipi bilinir)
  4. Binary expression: a = x + y → string varsa String, number varsa Number

Bu analiz her autocomplete tetiklendiğinde Lezer tree üzerinde yapılır. Bozuk kodda bile parse edilmiş assignment'lar doğru tip bilgisi verir.

Tetikleme Kuralları

Durum Davranış
identifier yazılırken Keyword, fonksiyon, değişken önerileri
. yazıldığında Dot öncesi ifadenin tipine göre metod önerileri
. + tip bilinmiyor Tüm metodlar (fallback)
. + Number tipi Öneri yok (ondalık yazımıyla karışmaz)
String / comment içinde Öneri yok
Ctrl+Space Explicit tetikleme

Metod Önerileri (tipe göre)

Dot Öncesi Gösterilen Metodlar
"hello". String metodları
category. (config'de String) String metodları
x. (assignment'tan String çıkarıldı) String metodları
items. (config'de StringList) StringList metodları
scores. (config'de NumberList) NumberList metodları
obj. (config'de Object) Object field'ları + Object metodları
kalemler. (config'de List) Element field'ları (property projection) + List metodları
kalemler.tutar. (List projection → NumberList) NumberList metodları (sum, avg, min, max...)
result. (tip bilinmiyor) Tüm metodlar
42. Öneri yok

Kurulum ve Build

cd editor

# Bağımlılıkları kur
bun install

# Lezer parser'ı grammar'dan üret
npx lezer-generator src/dexpr.grammar -o src/parser.js

# Kütüphaneyi build et
bun run build       # → dist/index.js, dist/index.cjs, dist/index.d.ts

# Demo'yu build et (test için)
bun run demo        # → dist/demo.global.js

# Demo'yu çalıştır
bunx serve . -p 3456   # → http://localhost:3456/demo.html

Grammar Değişikliği Yapıldığında

  1. src/dexpr.grammar dosyasını düzenle
  2. npx lezer-generator src/dexpr.grammar -o src/parser.js çalıştır
  3. bun run build ile yeniden derle

Host Uygulama Entegrasyonu

1. Rust Tarafı: Metadata Üretimi

use dexpr::language_info::LanguageInfo;

let mut info = LanguageInfo::builtin();

// VM'de register edilen her fonksiyon için:
info.add_function("getRate", "(code: String) -> Number", Some("Kur bilgisi"));

// VM'de register edilen her metod için:
info.add_method("String", "toTitleCase", "() -> String", None);

// VM'de set_global ile verilen her değişken için:
info.add_variable("price", "Number", None);
info.add_variable("category", "String", None);

let json = info.to_json();

2. Frontend Tarafı: Editör Oluşturma

import { EditorView, basicSetup } from "codemirror";
import { EditorState } from "@codemirror/state";
import { dexpr } from "codemirror-lang-dexpr";

// Rust'tan gelen JSON
const languageInfo = JSON.parse(jsonFromRust);

new EditorView({
  state: EditorState.create({
    doc: "",
    extensions: [basicSetup, dexpr(languageInfo)],
  }),
  parent: document.getElementById("editor")!,
});

3. Dinamik Güncelleme

Eğer host uygulama çalışma sırasında yeni fonksiyon/değişken eklerse, editörü yeni languageInfo ile yeniden oluşturmak gerekir. CodeMirror'un EditorView.dispatch ile extension'ları güncellemek mümkündür ama en basit yol editörü yeniden oluşturmaktır.


Export'lar

Ana Export

Export Tip ıklama
dexpr(config) Extension All-in-one: language + autocomplete + highlighting

Granüler Export'lar

Export ıklama
dexprLanguage Sadece LRLanguage tanımı
dexprCompletion(info) Sadece autocomplete extension'ı
dexprHighlighting() Sadece varsayılan renk teması
dexprHighlightStyle HighlightStyle nesnesi (özelleştirme için)
KEYWORDS Keyword completion listesi

Tip Export'ları

Tip ıklama
DexprLanguageInfo Metadata arayüzü (JSON yapısı)
DexprType "String" | "Number" | "Boolean" | "NumberList" | "StringList" | "Object" | "List"
FunctionInfo Fonksiyon metadata'sı
MethodInfo Metod metadata'sı
VariableInfo Değişken metadata'sı