Skip to main content

sim_kernel/
encode.rs

1//! The encode contract: turning objects back into forms at a known position.
2//!
3//! The kernel defines encode options, output positions, and the object-encoding
4//! contract; concrete codecs that render forms live in library crates.
5
6use crate::{
7    capability::CapabilityName,
8    env::Cx,
9    error::Result,
10    expr::Expr,
11    id::{CodecId, Symbol},
12    object::ShapeRef,
13    value::Value,
14};
15
16/// The output position an encoder is targeting.
17///
18/// Encoders render differently depending on where the form will land; the
19/// kernel defines the positions, concrete codecs honor them.
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum EncodePosition {
22    /// Output will be evaluated.
23    Eval,
24    /// Output sits inside a quotation.
25    Quote,
26    /// Output is plain data.
27    Data,
28    /// Output is a pattern.
29    Pattern,
30}
31
32/// How a constructed object is surfaced when encoded.
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub enum ConstructorSurface {
35    /// A bare constructor call.
36    BareCall,
37    /// A read-time construct form.
38    ReadConstruct,
39    /// Tagged data.
40    TaggedData,
41}
42
43/// Whether read-time construct forms may be emitted.
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub enum ReadConstructEncodePolicy {
46    /// Forbid read-construct output.
47    Forbid,
48    /// Allow read-construct output.
49    Allow,
50}
51
52/// Whether read-eval output may be emitted.
53#[derive(Clone, Debug, PartialEq, Eq)]
54pub enum ReadEvalEncodePolicy {
55    /// Forbid read-eval output.
56    Forbid,
57    /// Allow only explicitly declared read-eval output.
58    ///
59    /// The shape and capability fields describe the expected admitted result and
60    /// maximum powers for the eval body. Codecs must not treat this as a broad
61    /// permission to emit arbitrary read-eval syntax.
62    AllowExplicit {
63        /// Shape symbol the evaluated result must satisfy.
64        expected_shape: Symbol,
65        /// Maximum capabilities the emitted eval body may request.
66        allowed_capabilities: Vec<CapabilityName>,
67    },
68}
69
70/// Whether output is canonicalized or preserves the original input form.
71#[derive(Clone, Copy, Debug, PartialEq, Eq)]
72pub enum CanonicalPolicy {
73    /// Emit a canonical form.
74    Canonical,
75    /// Preserve the input form as read.
76    PreserveInput,
77}
78
79/// The full set of options controlling an encode pass.
80#[derive(Clone, Debug, PartialEq, Eq)]
81pub struct EncodeOptions {
82    /// The target output position.
83    pub position: EncodePosition,
84    /// The canonicalization policy.
85    pub canonical: CanonicalPolicy,
86    /// Whether to preserve lossless source origin.
87    pub lossless_origin: bool,
88    /// The read-construct emission policy.
89    pub read_construct: ReadConstructEncodePolicy,
90    /// The read-eval emission policy.
91    pub read_eval: ReadEvalEncodePolicy,
92}
93
94impl Default for EncodeOptions {
95    fn default() -> Self {
96        Self {
97            position: EncodePosition::Data,
98            canonical: CanonicalPolicy::Canonical,
99            lossless_origin: false,
100            read_construct: ReadConstructEncodePolicy::Allow,
101            read_eval: ReadEvalEncodePolicy::Forbid,
102        }
103    }
104}
105
106/// The mutable context passed to an encoder while it renders a form.
107pub struct WriteCx<'a> {
108    /// The runtime context.
109    pub cx: &'a mut Cx,
110    /// The codec performing the encode.
111    pub codec: CodecId,
112    /// The active encode options.
113    pub options: EncodeOptions,
114}
115
116impl<'a> WriteCx<'a> {
117    /// Reborrows this context with the encode position overridden.
118    pub fn with_position<'b>(&'b mut self, position: EncodePosition) -> WriteCx<'b> {
119        let mut options = self.options.clone();
120        options.position = position;
121        WriteCx {
122            cx: &mut *self.cx,
123            codec: self.codec,
124            options,
125        }
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::{EncodeOptions, ReadEvalEncodePolicy};
132    use crate::{CapabilityName, Symbol, read_eval_capability};
133
134    #[test]
135    fn default_options_forbid_read_eval_output() {
136        assert_eq!(
137            EncodeOptions::default().read_eval,
138            ReadEvalEncodePolicy::Forbid
139        );
140    }
141
142    #[test]
143    fn explicit_read_eval_policy_carries_shape_and_caps() {
144        let policy = ReadEvalEncodePolicy::AllowExplicit {
145            expected_shape: Symbol::qualified("core", "Number"),
146            allowed_capabilities: vec![read_eval_capability(), CapabilityName::new("secret/env")],
147        };
148
149        let ReadEvalEncodePolicy::AllowExplicit {
150            expected_shape,
151            allowed_capabilities,
152        } = policy
153        else {
154            panic!("expected explicit read-eval policy");
155        };
156        assert_eq!(expected_shape, Symbol::qualified("core", "Number"));
157        assert_eq!(
158            allowed_capabilities,
159            vec![read_eval_capability(), CapabilityName::new("secret/env")]
160        );
161    }
162}
163
164/// How an object presents itself to a codec for encoding.
165///
166/// The kernel defines these encoding shapes; a codec turns the chosen shape
167/// into concrete output.
168#[derive(Clone, Debug, PartialEq, Eq)]
169pub enum ObjectEncoding {
170    /// Encode as a constructor call of `class` with argument expressions.
171    Constructor {
172        /// The constructing class.
173        class: Symbol,
174        /// The constructor argument expressions.
175        args: Vec<Expr>,
176    },
177    /// Encode as tagged data with named fields.
178    TaggedData {
179        /// The data tag.
180        tag: Symbol,
181        /// The named field expressions.
182        fields: Vec<(Symbol, Expr)>,
183    },
184    /// Encode as an opaque reference identified by a stable id.
185    Opaque {
186        /// The object's class.
187        class: Symbol,
188        /// A stable, codec-renderable identifier for the object.
189        stable_id: String,
190    },
191}
192
193/// Contract: an object that can describe its own encoding.
194///
195/// The kernel defines this trait; objects implement it to choose how codecs
196/// render them back into forms.
197pub trait ObjectEncode: Send + Sync {
198    /// Returns the [`ObjectEncoding`] this object should be rendered as.
199    fn object_encoding(&self, cx: &mut Cx) -> Result<ObjectEncoding>;
200}
201
202/// Contract: a read-time constructor that builds an object from read args.
203///
204/// The kernel defines the contract; libraries register concrete constructors
205/// that the reader invokes to build values from forms.
206pub trait ReadConstructor: Send + Sync {
207    /// The symbol naming this constructor.
208    fn symbol(&self) -> Symbol;
209    /// The shape the constructor's arguments must match.
210    fn args_shape(&self, cx: &mut Cx) -> Result<ShapeRef>;
211    /// Constructs the value from matched arguments at read time.
212    fn construct_read(&self, cx: &mut Cx, args: Vec<Value>) -> Result<Value>;
213}