Add host prelude embedding API
This commit is contained in:
parent
6316939438
commit
e23b31da46
38
crates/decodal-core/examples/host_prelude.rs
Normal file
38
crates/decodal-core/examples/host_prelude.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
use decodal_core::{Data, EmptyLoader, Engine, HostValue};
|
||||||
|
|
||||||
|
fn main() -> decodal_core::Result<()> {
|
||||||
|
let mut engine = Engine::new(EmptyLoader);
|
||||||
|
|
||||||
|
engine.bind_global(
|
||||||
|
"Service",
|
||||||
|
HostValue::object([
|
||||||
|
("name", HostValue::string_type()),
|
||||||
|
("port", HostValue::int_type().gt(443).default_int(8443)?),
|
||||||
|
("enabled", HostValue::bool_type().default_bool(true)?),
|
||||||
|
]),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let module = engine.add_root_source(
|
||||||
|
"embedded-main",
|
||||||
|
"embedded-main",
|
||||||
|
r#"
|
||||||
|
Service & {
|
||||||
|
name = "api";
|
||||||
|
port = 9443;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let value = engine.eval_module(module)?;
|
||||||
|
let data = engine.materialize(&value)?;
|
||||||
|
|
||||||
|
if let Data::Object(fields) = data {
|
||||||
|
assert_eq!(fields[0].value, Data::String(String::from("api")));
|
||||||
|
assert_eq!(fields[1].value, Data::Int(9443));
|
||||||
|
assert_eq!(fields[2].value, Data::Bool(true));
|
||||||
|
} else {
|
||||||
|
panic!("expected object");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
164
crates/decodal-core/src/embedding.rs
Normal file
164
crates/decodal-core/src/embedding.rs
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
use alloc::{boxed::Box, string::String, vec::Vec};
|
||||||
|
|
||||||
|
use crate::runtime::{Constraint, LiteralValue, PrimitiveType};
|
||||||
|
use crate::{CompareOp, Diagnostic, DiagnosticKind, Result, Span};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum HostValue {
|
||||||
|
String(String),
|
||||||
|
Int(i64),
|
||||||
|
Float(f64),
|
||||||
|
Bool(bool),
|
||||||
|
Array(Vec<HostValue>),
|
||||||
|
Object(Vec<HostField>),
|
||||||
|
Abstract {
|
||||||
|
constraints: Vec<Constraint>,
|
||||||
|
default: Option<Box<HostValue>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct HostField {
|
||||||
|
pub name: String,
|
||||||
|
pub value: HostValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HostValue {
|
||||||
|
pub fn string(value: impl Into<String>) -> Self {
|
||||||
|
Self::String(value.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn int(value: i64) -> Self {
|
||||||
|
Self::Int(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn float(value: f64) -> Self {
|
||||||
|
Self::Float(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bool(value: bool) -> Self {
|
||||||
|
Self::Bool(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn array<I>(items: I) -> Self
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = HostValue>,
|
||||||
|
{
|
||||||
|
Self::Array(items.into_iter().collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn object<I, N>(fields: I) -> Self
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = (N, HostValue)>,
|
||||||
|
N: Into<String>,
|
||||||
|
{
|
||||||
|
Self::Object(
|
||||||
|
fields
|
||||||
|
.into_iter()
|
||||||
|
.map(|(name, value)| HostField {
|
||||||
|
name: name.into(),
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn string_type() -> Self {
|
||||||
|
Self::abstract_with_constraint(Constraint::Type(PrimitiveType::String))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn int_type() -> Self {
|
||||||
|
Self::abstract_with_constraint(Constraint::Type(PrimitiveType::Int))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn float_type() -> Self {
|
||||||
|
Self::abstract_with_constraint(Constraint::Type(PrimitiveType::Float))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bool_type() -> Self {
|
||||||
|
Self::abstract_with_constraint(Constraint::Type(PrimitiveType::Bool))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn builtin_predicate(name: impl Into<String>) -> Self {
|
||||||
|
Self::abstract_with_constraint(Constraint::BuiltinPredicate(name.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn abstract_with_constraint(constraint: Constraint) -> Self {
|
||||||
|
Self::Abstract {
|
||||||
|
constraints: alloc::vec![constraint],
|
||||||
|
default: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_constraint(mut self, constraint: Constraint) -> Self {
|
||||||
|
match &mut self {
|
||||||
|
Self::Abstract { constraints, .. } => constraints.push(constraint),
|
||||||
|
_ => {
|
||||||
|
self = Self::Abstract {
|
||||||
|
constraints: alloc::vec![constraint],
|
||||||
|
default: Some(Box::new(self)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gt(self, value: i64) -> Self {
|
||||||
|
self.with_constraint(Constraint::Compare(CompareOp::Gt, LiteralValue::Int(value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gte(self, value: i64) -> Self {
|
||||||
|
self.with_constraint(Constraint::Compare(
|
||||||
|
CompareOp::Gte,
|
||||||
|
LiteralValue::Int(value),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lt(self, value: i64) -> Self {
|
||||||
|
self.with_constraint(Constraint::Compare(CompareOp::Lt, LiteralValue::Int(value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lte(self, value: i64) -> Self {
|
||||||
|
self.with_constraint(Constraint::Compare(
|
||||||
|
CompareOp::Lte,
|
||||||
|
LiteralValue::Int(value),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default(self, value: HostValue) -> Result<Self> {
|
||||||
|
match self {
|
||||||
|
Self::Abstract {
|
||||||
|
constraints,
|
||||||
|
default: None,
|
||||||
|
} => Ok(Self::Abstract {
|
||||||
|
constraints,
|
||||||
|
default: Some(Box::new(value)),
|
||||||
|
}),
|
||||||
|
Self::Abstract { .. } => Err(Diagnostic::new(
|
||||||
|
DiagnosticKind::DefaultConflict,
|
||||||
|
Span::default(),
|
||||||
|
"host value already has a default",
|
||||||
|
)),
|
||||||
|
concrete => Ok(Self::Abstract {
|
||||||
|
constraints: Vec::new(),
|
||||||
|
default: Some(Box::new(concrete)),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_string(self, value: impl Into<String>) -> Result<Self> {
|
||||||
|
self.default(Self::string(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_int(self, value: i64) -> Result<Self> {
|
||||||
|
self.default(Self::int(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_float(self, value: f64) -> Result<Self> {
|
||||||
|
self.default(Self::float(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_bool(self, value: bool) -> Result<Self> {
|
||||||
|
self.default(Self::bool(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ use crate::{
|
||||||
ExprId, SourceForm, SourceId, Span,
|
ExprId, SourceForm, SourceId, Span,
|
||||||
ast::{Ast, BinaryOp, CompareOp, Expr, Field, Literal},
|
ast::{Ast, BinaryOp, CompareOp, Expr, Field, Literal},
|
||||||
diagnostic::{Diagnostic, DiagnosticKind, Result},
|
diagnostic::{Diagnostic, DiagnosticKind, Result},
|
||||||
|
embedding::HostValue,
|
||||||
module::{EmptyLoader, LoadedSource, Module, SourceLoader},
|
module::{EmptyLoader, LoadedSource, Module, SourceLoader},
|
||||||
parse_source_with_source_id,
|
parse_source_with_source_id,
|
||||||
runtime::{
|
runtime::{
|
||||||
|
|
@ -15,6 +16,7 @@ use crate::{
|
||||||
|
|
||||||
pub struct Engine<L = EmptyLoader> {
|
pub struct Engine<L = EmptyLoader> {
|
||||||
loader: L,
|
loader: L,
|
||||||
|
prelude_env: EnvId,
|
||||||
modules: Vec<Module>,
|
modules: Vec<Module>,
|
||||||
thunks: Vec<Thunk>,
|
thunks: Vec<Thunk>,
|
||||||
envs: Vec<Env>,
|
envs: Vec<Env>,
|
||||||
|
|
@ -39,12 +41,29 @@ impl<L: SourceLoader> Engine<L> {
|
||||||
pub fn new(loader: L) -> Self {
|
pub fn new(loader: L) -> Self {
|
||||||
Self {
|
Self {
|
||||||
loader,
|
loader,
|
||||||
|
prelude_env: EnvId(0),
|
||||||
modules: Vec::new(),
|
modules: Vec::new(),
|
||||||
thunks: Vec::new(),
|
thunks: Vec::new(),
|
||||||
envs: Vec::new(),
|
envs: vec![Env {
|
||||||
|
parent: None,
|
||||||
|
bindings: Vec::new(),
|
||||||
|
}],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bind_global(&mut self, name: impl Into<String>, value: HostValue) -> Result<ThunkId> {
|
||||||
|
let value = self.internalize_host_value(value)?;
|
||||||
|
let thunk = self.add_value_thunk(value);
|
||||||
|
self.bind(self.prelude_env, name.into(), thunk);
|
||||||
|
Ok(thunk)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bind_global_runtime(&mut self, name: impl Into<String>, value: RuntimeValue) -> ThunkId {
|
||||||
|
let thunk = self.add_value_thunk(value);
|
||||||
|
self.bind(self.prelude_env, name.into(), thunk);
|
||||||
|
thunk
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_root_source(
|
pub fn add_root_source(
|
||||||
&mut self,
|
&mut self,
|
||||||
key: impl Into<String>,
|
key: impl Into<String>,
|
||||||
|
|
@ -136,7 +155,7 @@ impl<L: SourceLoader> Engine<L> {
|
||||||
source_form: SourceForm,
|
source_form: SourceForm,
|
||||||
) -> ModuleId {
|
) -> ModuleId {
|
||||||
let module = ModuleId(self.modules.len() as u32);
|
let module = ModuleId(self.modules.len() as u32);
|
||||||
let root_env = self.new_env(None);
|
let root_env = self.new_env(Some(self.prelude_env));
|
||||||
let root_thunk = if source_form == SourceForm::Fields {
|
let root_thunk = if source_form == SourceForm::Fields {
|
||||||
if let Expr::Object(fields) = ast.get(root).expr.clone() {
|
if let Expr::Object(fields) = ast.get(root).expr.clone() {
|
||||||
let object = self
|
let object = self
|
||||||
|
|
@ -699,6 +718,61 @@ impl<L: SourceLoader> Engine<L> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn internalize_host_value(&mut self, value: HostValue) -> Result<RuntimeValue> {
|
||||||
|
match value {
|
||||||
|
HostValue::String(value) => Ok(RuntimeValue::Concrete(ConcreteValue::String(value))),
|
||||||
|
HostValue::Int(value) => Ok(RuntimeValue::Concrete(ConcreteValue::Int(value))),
|
||||||
|
HostValue::Float(value) => Ok(RuntimeValue::Concrete(ConcreteValue::Float(value))),
|
||||||
|
HostValue::Bool(value) => Ok(RuntimeValue::Concrete(ConcreteValue::Bool(value))),
|
||||||
|
HostValue::Array(items) => {
|
||||||
|
let mut thunks = Vec::new();
|
||||||
|
for item in items {
|
||||||
|
let value = self.internalize_host_value(item)?;
|
||||||
|
thunks.push(self.add_value_thunk(value));
|
||||||
|
}
|
||||||
|
Ok(RuntimeValue::Concrete(ConcreteValue::Array(thunks)))
|
||||||
|
}
|
||||||
|
HostValue::Object(fields) => {
|
||||||
|
let mut object = ObjectValue { fields: Vec::new() };
|
||||||
|
for field in fields {
|
||||||
|
if object
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.any(|existing| existing.name == field.name)
|
||||||
|
{
|
||||||
|
return Err(Diagnostic::new(
|
||||||
|
DiagnosticKind::Conflict,
|
||||||
|
Span::default(),
|
||||||
|
format!("duplicate host object field `{}`", field.name),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let value = self.internalize_host_value(field.value)?;
|
||||||
|
let value = self.add_value_thunk(value);
|
||||||
|
object.fields.push(ObjectField {
|
||||||
|
name: field.name,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(RuntimeValue::Concrete(ConcreteValue::Object(object)))
|
||||||
|
}
|
||||||
|
HostValue::Abstract {
|
||||||
|
constraints,
|
||||||
|
default,
|
||||||
|
} => {
|
||||||
|
let default = if let Some(default) = default {
|
||||||
|
let value = self.internalize_host_value(*default)?;
|
||||||
|
Some(self.add_value_thunk(value))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Ok(RuntimeValue::Abstract(AbstractValue {
|
||||||
|
constraints,
|
||||||
|
default,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn force(&mut self, id: ThunkId) -> Result<RuntimeValue> {
|
fn force(&mut self, id: ThunkId) -> Result<RuntimeValue> {
|
||||||
let index = id.0 as usize;
|
let index = id.0 as usize;
|
||||||
match self.thunks[index].state.clone() {
|
match self.thunks[index].state.clone() {
|
||||||
|
|
@ -1045,4 +1119,51 @@ mod tests {
|
||||||
assert_eq!(fields[1].name, "result");
|
assert_eq!(fields[1].name, "result");
|
||||||
assert!(matches!(fields[1].value, Data::Object(_)));
|
assert!(matches!(fields[1].value, Data::Object(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn host_prelude_provides_abstract_object() {
|
||||||
|
let mut engine = Engine::new(EmptyLoader);
|
||||||
|
engine
|
||||||
|
.bind_global(
|
||||||
|
"Service",
|
||||||
|
HostValue::object([
|
||||||
|
("name", HostValue::string_type()),
|
||||||
|
(
|
||||||
|
"port",
|
||||||
|
HostValue::int_type().gt(443).default_int(8443).unwrap(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"enabled",
|
||||||
|
HostValue::bool_type().default_bool(true).unwrap(),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let module = engine
|
||||||
|
.add_root_source(
|
||||||
|
"main",
|
||||||
|
"main",
|
||||||
|
r#"Service & { name = "api"; port = 9443; }"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let value = engine.eval_module(module).unwrap();
|
||||||
|
let data = engine.materialize(&value).unwrap();
|
||||||
|
let Data::Object(fields) = data else { panic!() };
|
||||||
|
assert_eq!(fields[0].value, Data::String(String::from("api")));
|
||||||
|
assert_eq!(fields[1].value, Data::Int(9443));
|
||||||
|
assert_eq!(fields[2].value, Data::Bool(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_binding_shadows_host_prelude() {
|
||||||
|
let mut engine = Engine::new(EmptyLoader);
|
||||||
|
engine.bind_global("Service", HostValue::int(1)).unwrap();
|
||||||
|
let module = engine
|
||||||
|
.add_root_source("main", "main", r#"Service = "local"; result = Service;"#)
|
||||||
|
.unwrap();
|
||||||
|
let value = engine.eval_module(module).unwrap();
|
||||||
|
let data = engine.materialize(&value).unwrap();
|
||||||
|
let Data::Object(fields) = data else { panic!() };
|
||||||
|
assert_eq!(fields[1].value, Data::String(String::from("local")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ extern crate alloc;
|
||||||
|
|
||||||
pub mod ast;
|
pub mod ast;
|
||||||
pub mod diagnostic;
|
pub mod diagnostic;
|
||||||
|
pub mod embedding;
|
||||||
pub mod eval;
|
pub mod eval;
|
||||||
pub mod lexer;
|
pub mod lexer;
|
||||||
pub mod module;
|
pub mod module;
|
||||||
|
|
@ -13,11 +14,12 @@ pub mod span;
|
||||||
|
|
||||||
pub use ast::{Ast, BinaryOp, CompareOp, Expr, ExprId, Field, Literal, Param};
|
pub use ast::{Ast, BinaryOp, CompareOp, Expr, ExprId, Field, Literal, Param};
|
||||||
pub use diagnostic::{Diagnostic, DiagnosticKind, Result};
|
pub use diagnostic::{Diagnostic, DiagnosticKind, Result};
|
||||||
|
pub use embedding::{HostField, HostValue};
|
||||||
pub use eval::Engine;
|
pub use eval::Engine;
|
||||||
pub use lexer::{Lexer, Token, TokenKind};
|
pub use lexer::{Lexer, Token, TokenKind};
|
||||||
pub use module::{EmptyLoader, LoadedSource, Module, SourceLoader};
|
pub use module::{EmptyLoader, LoadedSource, Module, SourceLoader};
|
||||||
pub use parser::{ParseOutput, Parser, SourceForm, parse_source, parse_source_with_source_id};
|
pub use parser::{ParseOutput, Parser, SourceForm, parse_source, parse_source_with_source_id};
|
||||||
pub use runtime::{Data, ExprRef, ModuleId, RuntimeValue};
|
pub use runtime::{Constraint, Data, ExprRef, LiteralValue, ModuleId, PrimitiveType, RuntimeValue};
|
||||||
pub use span::{SourceId, Span};
|
pub use span::{SourceId, Span};
|
||||||
|
|
||||||
pub fn version() -> &'static str {
|
pub fn version() -> &'static str {
|
||||||
|
|
|
||||||
97
doc/manual/souce/design/embedding-api.md
Normal file
97
doc/manual/souce/design/embedding-api.md
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
# Embedding API
|
||||||
|
|
||||||
|
Decodal core can be embedded without giving the core crate access to a filesystem.
|
||||||
|
The host supplies imported sources through `SourceLoader` and may also provide global bindings through the host prelude API.
|
||||||
|
|
||||||
|
## Host prelude
|
||||||
|
|
||||||
|
`Engine` owns a prelude environment.
|
||||||
|
Bindings in this environment are visible from every module loaded by the engine.
|
||||||
|
|
||||||
|
```text
|
||||||
|
prelude env
|
||||||
|
↓
|
||||||
|
module root env
|
||||||
|
↓
|
||||||
|
let / function env
|
||||||
|
```
|
||||||
|
|
||||||
|
Module top-level bindings shadow prelude bindings.
|
||||||
|
Primitive type names such as `String`, `Int`, `Float`, and `Bool` are handled before environment lookup, so they are reserved and cannot be shadowed by host bindings.
|
||||||
|
|
||||||
|
## Global bindings
|
||||||
|
|
||||||
|
The host can bind values before adding or evaluating user sources.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use decodal_core::{EmptyLoader, Engine, HostValue};
|
||||||
|
|
||||||
|
let mut engine = Engine::new(EmptyLoader);
|
||||||
|
|
||||||
|
engine.bind_global(
|
||||||
|
"Service",
|
||||||
|
HostValue::object([
|
||||||
|
("name", HostValue::string_type()),
|
||||||
|
("port", HostValue::int_type().gt(443).default_int(8443)?),
|
||||||
|
("enabled", HostValue::bool_type().default_bool(true)?),
|
||||||
|
]),
|
||||||
|
)?;
|
||||||
|
```
|
||||||
|
|
||||||
|
A user source can then refer to `Service` without importing it.
|
||||||
|
|
||||||
|
```dcdl
|
||||||
|
Service & {
|
||||||
|
name = "api";
|
||||||
|
port = 9443;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## HostValue
|
||||||
|
|
||||||
|
`HostValue` is the public builder-facing value representation for embedding.
|
||||||
|
It keeps host code from constructing internal `ThunkId` or `ObjectValue` values directly.
|
||||||
|
|
||||||
|
```text
|
||||||
|
HostValue =
|
||||||
|
String
|
||||||
|
Int
|
||||||
|
Float
|
||||||
|
Bool
|
||||||
|
Array(Vec<HostValue>)
|
||||||
|
Object(Vec<HostField>)
|
||||||
|
Abstract { constraints, default }
|
||||||
|
```
|
||||||
|
|
||||||
|
When a host value is bound, the engine internalizes it into `RuntimeValue` and allocates value thunks for object fields, array items, and defaults.
|
||||||
|
|
||||||
|
## Abstract host objects
|
||||||
|
|
||||||
|
A host-provided schema object is represented as a concrete object structure whose fields may contain abstract values.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
HostValue::object([
|
||||||
|
("name", HostValue::string_type()),
|
||||||
|
("port", HostValue::int_type().gt(443).default_int(8443)?),
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
Conceptually this becomes:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Concrete(Object {
|
||||||
|
name -> Thunk(Abstract { constraints: [String], default: none })
|
||||||
|
port -> Thunk(Abstract { constraints: [Int, > 443], default: 8443 })
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
This matches the runtime model used for Decodal source-defined schema objects.
|
||||||
|
|
||||||
|
## SourceLoader and prelude together
|
||||||
|
|
||||||
|
`SourceLoader` and host prelude bindings are independent mechanisms.
|
||||||
|
|
||||||
|
- Use `SourceLoader` when user sources should explicitly import host-provided modules.
|
||||||
|
- Use prelude bindings when host-provided schemas or constants should be globally available.
|
||||||
|
|
||||||
|
Both mechanisms share the same runtime evaluator, thunk model, and materialization rules.
|
||||||
|
|
@ -15,3 +15,4 @@ bytecode VM や JIT ではなく、AST を demand-driven に評価すること
|
||||||
3. [Thunk and Lazy Evaluation](./thunk-and-lazy-evaluation.md)
|
3. [Thunk and Lazy Evaluation](./thunk-and-lazy-evaluation.md)
|
||||||
4. [Composition and Materialization](./composition-and-materialization.md)
|
4. [Composition and Materialization](./composition-and-materialization.md)
|
||||||
5. [Diagnostics and Fallback](./diagnostics-and-fallback.md)
|
5. [Diagnostics and Fallback](./diagnostics-and-fallback.md)
|
||||||
|
6. [Embedding API](./embedding-api.md)
|
||||||
|
|
|
||||||
|
|
@ -41,4 +41,5 @@ Decodal は Deferred Constraint Data Language、略称 DCDL のプロジェク
|
||||||
3. [Thunk and Lazy Evaluation](./design/thunk-and-lazy-evaluation.md)
|
3. [Thunk and Lazy Evaluation](./design/thunk-and-lazy-evaluation.md)
|
||||||
4. [Composition and Materialization](./design/composition-and-materialization.md)
|
4. [Composition and Materialization](./design/composition-and-materialization.md)
|
||||||
5. [Diagnostics and Fallback](./design/diagnostics-and-fallback.md)
|
5. [Diagnostics and Fallback](./design/diagnostics-and-fallback.md)
|
||||||
|
6. [Embedding API](./design/embedding-api.md)
|
||||||
4. [Open Issues](./open-issues.md)
|
4. [Open Issues](./open-issues.md)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user