Files
Decodal/doc/manual/source/jp/embedding.md
T

6.3 KiB

Embedding

Decodal runtime は filesystem、network、environment variable を直接読み込まない。 ホストは global bindings と import の解決方法を注入し、評価結果を Data または application type として受け取る。

Rust runtime

EngineImportLoader と global bindings を持つ。 外部 resource を必要としない source には EmptyLoader を使える。

use decodal::{EmptyLoader, Engine, Value};

let mut engine = Engine::new(EmptyLoader);
engine.bind_global(
    "Service",
    Value::object([
        ("name", Value::string_type()),
        ("port", Value::int_type().gt(443).default_int(8443)?),
        ("enabled", Value::bool_type().default_bool(true)?),
    ]),
)?;

let module = engine.add_root_source(
    "service.dcdl",
    "service.dcdl",
    r#"{ name = "api"; port = 9443; } as Service"#,
)?;
let value = engine.eval_module(module)?;
let data = engine.materialize(&value)?;

Value は concrete value と host-defined range の両方を表す公開型である。 主な constructor は次の通りである。

  • Value::stringintfloatboolarrayobject: concrete value。
  • Value::string_typeint_typefloat_typebool_type: primitive range。
  • Value::unknown: 最上位 range Unknown
  • Value::array_of: 全要素へ同じ range を適用する array range。
  • Value::map_of: 全 field value へ同じ range を適用する associative-array range。
  • Value::object_with_rest: named fields と残りの field range を持つ object。

Typed Rust integration

derive feature の Decodal derive は、Rust struct から DecodalSchemaDecodalDecode を生成する。

use decodal::{Decodal, DecodalDecode, DecodalSchema};

#[derive(Decodal)]
struct Service {
    name: String,

    #[decodal(gt = 443, default = 8443)]
    port: i64,

    #[decodal(rename = "feature.enable", default = true)]
    feature_enabled: bool,
}

DecodalSchema::decodal_schema()Engine::bind_global に渡せる Value を返す。 materialize 済みの DataDecodalDecode::decodal_decode で Rust type に変換できる。

追加 field を受け取る struct では、map field に #[decodal(rest)] を付ける。

use std::collections::BTreeMap;
use decodal::{Data, Decodal};

#[derive(Decodal)]
struct OpenConfig {
    enabled: bool,

    #[decodal(rest)]
    extra: BTreeMap<String, Data>,
}

BTreeMap<String, Data>...UnknownBTreeMap<String, String>...String に対応する。 named fields は rest map から除外される。 #[decodal(rest)] は struct ごとに1つだけ指定でき、renamedefault、field constraints とは併用できない。

Imports

ImportLoader::loadLoadedImport::Source または LoadedImport::Value を返す。 ホストは specifier の解決、filesystem や他の storage からの読み込み、content type の判定をすべて管理する。

LoadedImport::Source は DCDL source を返す。 LoadedImport::Value は Markdown、JSON、TOML などをホスト独自の規則で構造化して返す用途に使える。

import "./post.md"
  -> host reads and parses Markdown
  -> LoadedImport::Value {
       key: "content/post.md",
       value: { frontmatter: {...}, body: "..." }
     }
  -> normal Decodal validation and materialization

stable key は import の同一性と diagnostic の provenance に使われる。 構造化した値が Decodal constraint に違反した場合、diagnostic は import key と logical value path、および違反した DCDL constraint の source span を示す。 外部形式そのものの parse error と位置情報は loader が報告する。

Shared host environment

HostEnvironment は loader の作成と global binding の設定を1つにまとめる。 同じ environment を runtime、language service、LSP へ渡すことで、実行時と編集時の評価規則を一致させられる。

use decodal::{Engine, HostEnvironment};

struct AppEnvironment;

impl HostEnvironment for AppEnvironment {
    type Loader = ContentLoader;

    fn create_loader(&self) -> Self::Loader {
        ContentLoader::new()
    }

    fn configure_engine(
        &self,
        engine: &mut Engine<Self::Loader>,
    ) -> decodal::Result<()> {
        engine.bind_global("Site", site_schema())?;
        Ok(())
    }
}

transport 非依存の tooling は decodal_language_service::LanguageService::new(&environment) で構築する。 LSP integration では decodal_lsp::LspEnvironment を実装し、environment factory から server を起動する。

use decodal_lsp::{LspEnvironment, run_stdio};

impl LspEnvironment for AppEnvironment {}

run_stdio(|initialize| {
    let _ = initialize;
    Ok(AppEnvironment)
})?;

factory は server capability の応答前に client の InitializeParams を一度受け取る。 LspEnvironment の document lifecycle hooks を使うと、未保存の DCDL、Markdown、その他の resource を host-owned overlay に反映できる。

JavaScript and browser environments

decodal-wasmDecodalLanguageService は filesystem や仮想 project を仮定しない。 JavaScript 側が globalsloadImportcompleteImport を提供する。

import init, { DecodalLanguageService } from 'decodal-wasm';

await init();

const files = {
  'schema.dcdl': 'Server = { port = Int; };',
};

const service = new DecodalLanguageService({
  globals: {
    App: {
      enabled: { $decodal: 'Bool', default: true },
    },
  },
  loadImport(_currentKey, specifier) {
    const key = specifier.replace(/^\.\//, '');
    return { kind: 'source', key, name: key, source: files[key] };
  },
  completeImport() {
    return ['./schema.dcdl'];
  },
});

const result = service.evaluate('main.dcdl', 'main.dcdl', 'App');

JavaScript の primitive、array、plain object は concrete value になる。 range は $decodal descriptor で指定する。

  • { $decodal: 'String' | 'Int' | 'Float' | 'Bool' }
  • { $decodal: 'Unknown' }
  • { $decodal: 'Array', item }
  • { $decodal: 'Map', value }
  • { $decodal: 'Object', fields, rest }
  • { $decodal: 'Range', constraints, default }

import callbacks は同期 API である。 network resource などの非同期入力は、評価の前に preload または cache しておく。