Add bilingual manual and localized docs routes
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user