Files
dexpr/docs/embedding.md
2026-04-05 16:08:59 +03:00

8.7 KiB
Raw Permalink Blame History

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ı

# Cargo.toml
[dependencies]
dexpr = { path = "../dexpr" }  # veya git/crates.io
rust_decimal_macros = "1.40"
indexmap = "2"
smol_str = "0.3"
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ı

npm install codemirror codemirror-lang-dexpr
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

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

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

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.