1use sim_kernel::{
34 AbiVersion, Cx, Dependency, Export, Expr, Lib, LibId, LibManifest, LibTarget, Linker, LoadCx,
35 Result, Symbol, Value, Version,
36};
37
38pub enum SurfaceField {
40 Symbol(Symbol),
42 Str(String),
44 Symbols(Vec<Symbol>),
46 Strs(Vec<String>),
48 Bool(bool),
50 U64(u64),
52 Expr(Expr),
54}
55
56pub struct SurfaceValueSpec {
58 pub symbol: Symbol,
60 pub fields: Vec<(Symbol, SurfaceField)>,
62}
63
64pub struct SurfacePackSpec {
66 pub lib_id: Symbol,
68 pub values: Vec<SurfaceValueSpec>,
70}
71
72pub struct SurfacePackLib {
74 pub spec: SurfacePackSpec,
76}
77
78impl Lib for SurfacePackLib {
79 fn manifest(&self) -> LibManifest {
80 LibManifest {
81 id: self.spec.lib_id.clone(),
82 version: Version(env!("CARGO_PKG_VERSION").to_owned()),
83 abi: AbiVersion { major: 0, minor: 1 },
84 target: LibTarget::HostRegistered,
85 requires: Vec::<Dependency>::new(),
86 capabilities: Vec::new(),
87 exports: self
88 .spec
89 .values
90 .iter()
91 .map(|value| Export::Value {
92 symbol: value.symbol.clone(),
93 })
94 .collect(),
95 }
96 }
97
98 fn load(&self, cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
99 for value in &self.spec.values {
100 let card = build_card(cx, &value.fields)?;
101 linker.value(value.symbol.clone(), card)?;
102 }
103 Ok(())
104 }
105}
106
107fn build_card(cx: &mut LoadCx, fields: &[(Symbol, SurfaceField)]) -> Result<Value> {
108 let mut entries = Vec::with_capacity(fields.len());
109 for (key, field) in fields {
110 let value = match field {
111 SurfaceField::Symbol(symbol) => cx.factory().symbol(symbol.clone())?,
112 SurfaceField::Str(text) => cx.factory().string(text.clone())?,
113 SurfaceField::Bool(flag) => cx.factory().bool(*flag)?,
114 SurfaceField::Symbols(symbols) => {
115 let items = symbols
116 .iter()
117 .map(|symbol| cx.factory().symbol(symbol.clone()))
118 .collect::<Result<Vec<_>>>()?;
119 cx.factory().list(items)?
120 }
121 SurfaceField::Strs(texts) => {
122 let items = texts
123 .iter()
124 .map(|text| cx.factory().string(text.clone()))
125 .collect::<Result<Vec<_>>>()?;
126 cx.factory().list(items)?
127 }
128 other => cx.factory().expr(field_to_expr(other))?,
129 };
130 entries.push((key.clone(), value));
131 }
132 cx.factory().table(entries)
133}
134
135pub fn card_expr(spec: &SurfaceValueSpec) -> Expr {
139 Expr::Map(
140 spec.fields
141 .iter()
142 .map(|(key, field)| (Expr::Symbol(key.clone()), field_to_expr(field)))
143 .collect(),
144 )
145}
146
147fn field_to_expr(field: &SurfaceField) -> Expr {
148 match field {
149 SurfaceField::Symbol(symbol) => Expr::Symbol(symbol.clone()),
150 SurfaceField::Str(text) => Expr::String(text.clone()),
151 SurfaceField::Bool(flag) => Expr::Bool(*flag),
152 SurfaceField::U64(number) => sim_value::build::uint(*number),
153 SurfaceField::Symbols(symbols) => Expr::List(
154 symbols
155 .iter()
156 .map(|symbol| Expr::Symbol(symbol.clone()))
157 .collect(),
158 ),
159 SurfaceField::Strs(texts) => Expr::List(
160 texts
161 .iter()
162 .map(|text| Expr::String(text.clone()))
163 .collect(),
164 ),
165 SurfaceField::Expr(expr) => expr.clone(),
166 }
167}
168
169pub fn install_once(cx: &mut Cx, lib: &impl Lib) -> Result<bool> {
173 install_once_id(cx, lib).map(|id| id.is_some())
174}
175
176pub fn install_once_id(cx: &mut Cx, lib: &impl Lib) -> Result<Option<LibId>> {
178 let id = lib.manifest().id.clone();
179 if cx.registry().lib(&id).is_some() {
180 return Ok(None);
181 }
182 cx.load_lib(lib).map(Some)
183}
184
185pub fn installed_lib_id(cx: &Cx, lib: &impl Lib) -> Option<LibId> {
187 let id = lib.manifest().id;
188 cx.registry().lib(&id).map(|loaded| loaded.id)
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 fn pack() -> SurfacePackLib {
196 SurfacePackLib {
197 spec: SurfacePackSpec {
198 lib_id: Symbol::new("demo-pack"),
199 values: vec![
200 SurfaceValueSpec {
201 symbol: Symbol::qualified("demo", "Alpha"),
202 fields: vec![
203 (
204 Symbol::new("symbol"),
205 SurfaceField::Symbol(Symbol::qualified("demo", "Alpha")),
206 ),
207 (Symbol::new("layer"), SurfaceField::Str("demo".to_owned())),
208 (Symbol::new("lossless"), SurfaceField::Bool(true)),
209 (Symbol::new("rank"), SurfaceField::U64(3)),
210 (
211 Symbol::new("tags"),
212 SurfaceField::Strs(vec!["a".to_owned(), "b".to_owned()]),
213 ),
214 ],
215 },
216 SurfaceValueSpec {
217 symbol: Symbol::qualified("demo", "Beta"),
218 fields: vec![(
219 Symbol::new("symbol"),
220 SurfaceField::Symbol(Symbol::qualified("demo", "Beta")),
221 )],
222 },
223 ],
224 },
225 }
226 }
227
228 #[test]
229 fn manifest_exports_one_value_per_card() {
230 let manifest = pack().manifest();
231 assert_eq!(manifest.id, Symbol::new("demo-pack"));
232 let symbols: Vec<String> = manifest
233 .exports
234 .iter()
235 .filter_map(|export| match export {
236 Export::Value { symbol } => Some(symbol.to_string()),
237 _ => None,
238 })
239 .collect();
240 assert_eq!(
241 symbols,
242 vec!["demo/Alpha".to_owned(), "demo/Beta".to_owned()]
243 );
244 }
245
246 #[test]
247 fn card_expr_renders_every_field_kind() {
248 let spec = &pack().spec.values[0];
249 let Expr::Map(entries) = card_expr(spec) else {
250 panic!("card_expr must build a map");
251 };
252 assert_eq!(entries.len(), 5);
253 assert_eq!(
254 entries[0],
255 (
256 Expr::Symbol(Symbol::new("symbol")),
257 Expr::Symbol(Symbol::qualified("demo", "Alpha"))
258 )
259 );
260 assert_eq!(
261 entries[1],
262 (
263 Expr::Symbol(Symbol::new("layer")),
264 Expr::String("demo".to_owned())
265 )
266 );
267 assert_eq!(
268 entries[2],
269 (Expr::Symbol(Symbol::new("lossless")), Expr::Bool(true))
270 );
271 assert_eq!(
272 entries[3],
273 (Expr::Symbol(Symbol::new("rank")), sim_value::build::uint(3))
274 );
275 assert_eq!(
276 entries[4].1,
277 Expr::List(vec![
278 Expr::String("a".to_owned()),
279 Expr::String("b".to_owned())
280 ])
281 );
282 }
283
284 #[test]
285 fn install_once_is_idempotent_and_loads_every_field_kind() {
286 let mut cx = sim_test_support::core_cx();
287 assert!(
288 install_once(&mut cx, &pack()).unwrap(),
289 "first install loads"
290 );
291 assert!(
292 !install_once(&mut cx, &pack()).unwrap(),
293 "second install is a no-op"
294 );
295 }
296}