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}