mirror of
https://github.com/duhanbalci/dexpr.git
synced 2026-07-02 00:29:15 +00:00
initial commit
This commit is contained in:
294
docs/embedding.md
Normal file
294
docs/embedding.md
Normal file
@@ -0,0 +1,294 @@
|
||||
# dexpr Embedding Guide
|
||||
|
||||
dexpr'i başka projelere embed etmenin iki yolu var:
|
||||
|
||||
1. **Rust backend + Web frontend** — Rust tarafında çalıştırma, frontend'de editör
|
||||
2. **Tamamen tarayıcıda (WASM)** — Hem çalıştırma hem editör tarayıcıda
|
||||
|
||||
---
|
||||
|
||||
## Yol 1: Rust Backend + Web Frontend
|
||||
|
||||
Tipik senaryo: kullanıcı editörde formül yazar, backend derler ve çalıştırır.
|
||||
|
||||
### Rust tarafı
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
[dependencies]
|
||||
dexpr = { path = "../dexpr" } # veya git/crates.io
|
||||
rust_decimal_macros = "1.40"
|
||||
indexmap = "2"
|
||||
smol_str = "0.3"
|
||||
```
|
||||
|
||||
```rust
|
||||
use dexpr::{ast::value::Value, compiler::Compiler, vm::VM, language_info::LanguageInfo};
|
||||
|
||||
// ── 1. Verileri hazırla ──
|
||||
// JSON'dan (API, DB, config, vb.)
|
||||
let customer = Value::from_json(r#"{
|
||||
"name": "Alice",
|
||||
"email": "alice@test.com",
|
||||
"age": 30,
|
||||
"active": true,
|
||||
"tags": ["premium", "tr"]
|
||||
}"#).unwrap();
|
||||
|
||||
let order = Value::from_json(r#"{
|
||||
"amount": 1500,
|
||||
"currency": "TRY",
|
||||
"items": ["laptop", "mouse"]
|
||||
}"#).unwrap();
|
||||
|
||||
// ── 2. Editör metadata'sını üret (sayfa yüklenirken, 1 kez) ──
|
||||
let mut info = LanguageInfo::builtin();
|
||||
|
||||
// Değişkenler — tip ve field bilgisi Value'dan otomatik çıkar
|
||||
info.add_value("customer", &customer, Some("Müşteri bilgisi".into()));
|
||||
info.add_value("order", &order, Some("Sipariş bilgisi".into()));
|
||||
info.add_value("discount", &Value::Number(rust_decimal_macros::dec!(10)), None);
|
||||
|
||||
// Host fonksiyonları
|
||||
info.add_function("getRate", "(code: String) -> Number", Some("Döviz kuru"));
|
||||
info.add_function("fetchPrice", "(sku: String) -> Number", None);
|
||||
|
||||
let editor_json = info.to_json();
|
||||
// → bu JSON'ı bir endpoint ile frontend'e gönder
|
||||
|
||||
// ── 3. Kullanıcının yazdığı kodu çalıştır ──
|
||||
fn execute_dexpr(
|
||||
source: &str,
|
||||
globals: &[(&str, Value)],
|
||||
) -> Result<Value, String> {
|
||||
let mut compiler = Compiler::new();
|
||||
let (bytecode, debug_info) = compiler
|
||||
.compile_from_source(source)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let mut vm = VM::new(&bytecode);
|
||||
vm.set_debug_info(&debug_info);
|
||||
|
||||
for (name, value) in globals {
|
||||
vm.set_global(name, value.clone());
|
||||
}
|
||||
|
||||
// Host fonksiyonları kaydet
|
||||
vm.register_function("getRate", |args| {
|
||||
// args[0]: currency code
|
||||
Ok(Value::Number(rust_decimal_macros::dec!(34.5)))
|
||||
});
|
||||
|
||||
vm.execute().map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
// Kullanıcının formülü:
|
||||
let result = execute_dexpr(
|
||||
"order.amount * (1 - discount / 100)",
|
||||
&[
|
||||
("customer", customer),
|
||||
("order", order),
|
||||
("discount", Value::Number(rust_decimal_macros::dec!(10))),
|
||||
],
|
||||
);
|
||||
// → Ok(Number(1350))
|
||||
```
|
||||
|
||||
### Frontend tarafı
|
||||
|
||||
```bash
|
||||
npm install codemirror codemirror-lang-dexpr
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { EditorView, basicSetup } from "codemirror";
|
||||
import { EditorState } from "@codemirror/state";
|
||||
import { dexpr } from "codemirror-lang-dexpr";
|
||||
|
||||
// ── 1. Backend'den metadata al ──
|
||||
const langInfo = await fetch("/api/editor-metadata").then(r => r.json());
|
||||
|
||||
// ── 2. Editörü oluştur ──
|
||||
const editor = new EditorView({
|
||||
state: EditorState.create({
|
||||
doc: "",
|
||||
extensions: [basicSetup, dexpr(langInfo)],
|
||||
}),
|
||||
parent: document.getElementById("editor")!,
|
||||
});
|
||||
|
||||
// ── 3. Çalıştır ──
|
||||
async function run() {
|
||||
const code = editor.state.doc.toString();
|
||||
const res = await fetch("/api/execute", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ code }),
|
||||
});
|
||||
const { result, error } = await res.json();
|
||||
document.getElementById("output")!.textContent =
|
||||
error ?? JSON.stringify(result);
|
||||
}
|
||||
```
|
||||
|
||||
### Akış
|
||||
|
||||
```
|
||||
Frontend Backend (Rust)
|
||||
─────── ──────────────
|
||||
GET /editor-metadata ──────────► LanguageInfo::to_json()
|
||||
◄─────────────────────────────── JSON (functions, methods, variables+fields)
|
||||
|
||||
dexpr(langInfo)
|
||||
customer. → [name, email, age, ...]
|
||||
customer.name. → [upper, lower, trim, ...]
|
||||
|
||||
POST /execute { code } ────────► Compiler::compile_from_source()
|
||||
VM::new() + set_global() + execute()
|
||||
◄─────────────────────────────── { result: 1350 }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Yol 2: Tamamen Tarayıcıda (WASM)
|
||||
|
||||
Backend'e gerek yok. Parser, compiler, VM hepsi tarayıcıda çalışır.
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
cd wasm
|
||||
wasm-pack build --target web --release
|
||||
# → wasm/pkg/ altında JS + WASM + TypeScript types üretilir
|
||||
```
|
||||
|
||||
`pkg/` içeriği:
|
||||
- `dexpr_wasm_bg.wasm` (~370KB) — WASM binary
|
||||
- `dexpr_wasm.js` — JS glue code (auto-init)
|
||||
- `dexpr_wasm.d.ts` — TypeScript type definitions
|
||||
|
||||
### Kullanım
|
||||
|
||||
```html
|
||||
<div id="editor"></div>
|
||||
<button onclick="run()">Çalıştır</button>
|
||||
<pre id="output"></pre>
|
||||
|
||||
<script type="module">
|
||||
import init, { DexprEngine } from "./pkg/dexpr_wasm.js";
|
||||
import { EditorView, basicSetup } from "codemirror";
|
||||
import { EditorState } from "@codemirror/state";
|
||||
import { dexpr } from "codemirror-lang-dexpr";
|
||||
|
||||
// ── 1. WASM'ı başlat ──
|
||||
await init();
|
||||
const engine = new DexprEngine();
|
||||
|
||||
// ── 2. Global'leri JSON string olarak ver ──
|
||||
engine.setGlobal("customer", JSON.stringify({
|
||||
name: "Alice",
|
||||
email: "alice@test.com",
|
||||
age: 30,
|
||||
active: true,
|
||||
tags: ["premium", "tr"],
|
||||
}));
|
||||
|
||||
engine.setGlobal("order", JSON.stringify({
|
||||
amount: 1500,
|
||||
currency: "TRY",
|
||||
items: ["laptop", "mouse"],
|
||||
}));
|
||||
|
||||
engine.setGlobal("discount", "10");
|
||||
|
||||
// ── 3. Host fonksiyon kaydet ──
|
||||
engine.registerFunction("getRate", (argsJson) => {
|
||||
const args = JSON.parse(argsJson);
|
||||
const code = args[0]; // currency code string
|
||||
const rates = { USD: 34.5, EUR: 37.2 };
|
||||
return JSON.stringify(rates[code] ?? 0);
|
||||
});
|
||||
engine.addFunctionInfo("getRate", "(code: String) -> Number", "Döviz kuru");
|
||||
|
||||
// ── 4. Editör metadata'sını engine'den al ──
|
||||
// setGlobal çağrıları otomatik olarak field tiplerini algılar
|
||||
const langInfo = JSON.parse(engine.languageInfo());
|
||||
|
||||
// ── 5. CodeMirror editörünü oluştur ──
|
||||
const editor = new EditorView({
|
||||
state: EditorState.create({
|
||||
doc: `// customer ve order WASM engine'e JSON olarak verildi
|
||||
name = customer.name.upper()
|
||||
total = order.amount * (1 - discount / 100)
|
||||
log(name, total)
|
||||
total`,
|
||||
extensions: [basicSetup, dexpr(langInfo)],
|
||||
}),
|
||||
parent: document.getElementById("editor"),
|
||||
});
|
||||
|
||||
// ── 6. Çalıştır butonu ──
|
||||
window.run = function() {
|
||||
try {
|
||||
const code = editor.state.doc.toString();
|
||||
const resultJson = engine.execute(code);
|
||||
const result = JSON.parse(resultJson);
|
||||
document.getElementById("output").textContent = JSON.stringify(result, null, 2);
|
||||
|
||||
// Global'ler güncellendi mi kontrol et
|
||||
const customer = JSON.parse(engine.getGlobal("customer"));
|
||||
console.log("customer after:", customer);
|
||||
} catch (e) {
|
||||
document.getElementById("output").textContent = "Error: " + e.message;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
### API Referansı
|
||||
|
||||
```typescript
|
||||
class DexprEngine {
|
||||
constructor()
|
||||
|
||||
// Global değişken: JSON string olarak ver/al
|
||||
setGlobal(name: string, json: string): void
|
||||
getGlobal(name: string): string | undefined
|
||||
|
||||
// Çalıştır: kaynak kodu → sonuç JSON string
|
||||
execute(source: string): string
|
||||
|
||||
// Host fonksiyonu kaydet (args ve return JSON string olarak)
|
||||
registerFunction(name: string, fn: (argsJson: string) => string): void
|
||||
|
||||
// Editör metadata (otomatik güncellenir her setGlobal'de)
|
||||
languageInfo(): string
|
||||
|
||||
// Editör autocomplete için fonksiyon bilgisi
|
||||
addFunctionInfo(name: string, signature: string, doc?: string): void
|
||||
}
|
||||
```
|
||||
|
||||
### Dikkat Edilmesi Gerekenler
|
||||
|
||||
- **JSON string convention**: `setGlobal` ve `execute` JSON *string* alır/verir — `JSON.stringify()` ve `JSON.parse()` kullan
|
||||
- **Global sync**: `execute()` sonrası property assignment'lar (`customer.city = "Ankara"`) global'lere yansır — `getGlobal()` ile güncel halini al
|
||||
- **Host fonksiyonlar**: Args ve return JSON string — `(argsJson: string) => string`
|
||||
- **Editör metadata**: `setGlobal()` her çağrıldığında `languageInfo()` otomatik güncellenir (field tipleri Value'dan çıkarılır)
|
||||
- **WASM boyutu**: Release build ~370KB (gzip ile ~120KB)
|
||||
|
||||
---
|
||||
|
||||
## Karşılaştırma
|
||||
|
||||
| | Rust Backend | WASM |
|
||||
|---|---|---|
|
||||
| **Çalıştırma** | Server'da | Tarayıcıda |
|
||||
| **Performans** | Native hız | ~2-3x native |
|
||||
| **Güvenlik** | Server-side validation | Client-side (güvenilir ortam) |
|
||||
| **Latency** | Network roundtrip | Anında |
|
||||
| **Deployment** | Backend deploy | Static dosya |
|
||||
| **Host fonksiyonlar** | Rust closure'ları | JS callback (JSON bridge) |
|
||||
| **DB/API erişimi** | Doğrudan | Fetch ile |
|
||||
|
||||
**Öneri**: Güvenlik kritikse (fiyat hesaplama, kurallar) → Rust backend. İnteraktif preview/sandbox → WASM.
|
||||
Reference in New Issue
Block a user