194 lines
6.0 KiB
Markdown
194 lines
6.0 KiB
Markdown
# Embedding
|
|
|
|
The Decodal runtime does not read filesystems, networks, or environment variables directly.
|
|
The host injects global bindings and import resolution, then receives the evaluation result as `Data` or an application type.
|
|
|
|
## Rust runtime
|
|
|
|
`Engine` owns an `ImportLoader` and global bindings.
|
|
Use `EmptyLoader` for source that does not require external resources.
|
|
|
|
```rust
|
|
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` is the public type for both concrete values and host-defined ranges.
|
|
Its main constructors are:
|
|
|
|
- `Value::string`, `int`, `float`, `bool`, `array`, and `object`: concrete values.
|
|
- `Value::string_type`, `int_type`, `float_type`, and `bool_type`: primitive ranges.
|
|
- `Value::unknown`: the top range, `Unknown`.
|
|
- `Value::array_of`: an array range that applies one range to every element.
|
|
- `Value::map_of`: an associative-array range that applies one range to every field value.
|
|
- `Value::object_with_rest`: an object with named fields and a range for remaining fields.
|
|
|
|
## Typed Rust integration
|
|
|
|
The `Decodal` derive from the `derive` feature generates `DecodalSchema` and `DecodalDecode` implementations from a Rust struct.
|
|
|
|
```rust
|
|
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()` returns a `Value` suitable for `Engine::bind_global`.
|
|
Materialized `Data` can be converted to a Rust type with `DecodalDecode::decodal_decode`.
|
|
|
|
For a struct that accepts additional fields, mark a map field with `#[decodal(rest)]`.
|
|
|
|
```rust
|
|
use std::collections::BTreeMap;
|
|
use decodal::{Data, Decodal};
|
|
|
|
#[derive(Decodal)]
|
|
struct OpenConfig {
|
|
enabled: bool,
|
|
|
|
#[decodal(rest)]
|
|
extra: BTreeMap<String, Data>,
|
|
}
|
|
```
|
|
|
|
`BTreeMap<String, Data>` corresponds to `...Unknown`, while `BTreeMap<String, String>` corresponds to `...String`.
|
|
Named fields are excluded from the rest map.
|
|
Only one `#[decodal(rest)]` field is allowed per struct, and it cannot be combined with `rename`, `default`, or field constraints.
|
|
|
|
## Imports
|
|
|
|
`ImportLoader::load` returns either `LoadedImport::Source` or `LoadedImport::Value`.
|
|
The host controls specifier resolution, reading from the filesystem or other storage, and content-type detection.
|
|
|
|
`LoadedImport::Source` supplies DCDL source.
|
|
`LoadedImport::Value` is intended for structured host-defined representations of formats such as Markdown, JSON, and TOML.
|
|
|
|
```text
|
|
import "./post.md"
|
|
-> host reads and parses Markdown
|
|
-> LoadedImport::Value {
|
|
key: "content/post.md",
|
|
value: { frontmatter: {...}, body: "..." }
|
|
}
|
|
-> normal Decodal validation and materialization
|
|
```
|
|
|
|
A stable `key` identifies an import and supplies diagnostic provenance.
|
|
If a structured value violates a Decodal constraint, the diagnostic identifies the import key, logical value path, and source span of the violated DCDL constraint.
|
|
The loader reports parse errors and positions in the external format itself.
|
|
|
|
## Shared host environment
|
|
|
|
`HostEnvironment` combines loader construction and global-binding setup.
|
|
Passing the same environment to the runtime, language service, and LSP keeps runtime and editor evaluation rules aligned.
|
|
|
|
```rust
|
|
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(())
|
|
}
|
|
}
|
|
```
|
|
|
|
Construct transport-independent tooling with `decodal_language_service::LanguageService::new(&environment)`.
|
|
For LSP integration, implement `decodal_lsp::LspEnvironment` and start the server from an environment factory.
|
|
|
|
```rust
|
|
use decodal_lsp::{LspEnvironment, run_stdio};
|
|
|
|
impl LspEnvironment for AppEnvironment {}
|
|
|
|
run_stdio(|initialize| {
|
|
let _ = initialize;
|
|
Ok(AppEnvironment)
|
|
})?;
|
|
```
|
|
|
|
The factory receives the client's `InitializeParams` once, before the server responds with its capabilities.
|
|
Use the `LspEnvironment` document lifecycle hooks to reflect unsaved DCDL, Markdown, and other resources in a host-owned overlay.
|
|
|
|
## JavaScript and browser environments
|
|
|
|
The `DecodalLanguageService` in `decodal-wasm` does not assume a filesystem or virtual project.
|
|
JavaScript supplies `globals`, `loadImport`, and `completeImport`.
|
|
|
|
```js
|
|
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 primitives, arrays, and plain objects become concrete values.
|
|
Use `$decodal` descriptors for ranges:
|
|
|
|
- `{ $decodal: 'String' | 'Int' | 'Float' | 'Bool' }`
|
|
- `{ $decodal: 'Unknown' }`
|
|
- `{ $decodal: 'Array', item }`
|
|
- `{ $decodal: 'Map', value }`
|
|
- `{ $decodal: 'Object', fields, rest }`
|
|
- `{ $decodal: 'Range', constraints, default }`
|
|
|
|
Import callbacks are synchronous.
|
|
Preload or cache asynchronous inputs such as network resources before evaluation.
|