1use std::{collections::BTreeMap, sync::Arc};
8
9use crate::{
10 claim::{Claim, ClaimPattern},
11 datum_store::DatumStore,
12 env::Cx,
13 error::Result,
14 expr::Expr,
15 force_list_to_vec,
16 handle_store::HandleStore,
17 id::{CORE_CARD_CLASS_ID, Symbol},
18 object::{ClassRef, Object},
19 ref_id::{ContentId, Coordinate, HandleId, Ref},
20 ref_resolver::{RefResolver, TemporaryRefResolver, value_from_datum},
21 value::Value,
22};
23
24#[derive(Clone)]
34pub struct Card {
35 subject: Ref,
36 entries: Vec<(Symbol, Value)>,
37}
38
39impl Card {
40 pub fn new(subject: Ref, entries: Vec<(Symbol, Value)>) -> Self {
42 Self { subject, entries }
43 }
44
45 pub fn subject(&self) -> &Ref {
47 &self.subject
48 }
49
50 pub fn entries(&self) -> &[(Symbol, Value)] {
52 &self.entries
53 }
54}
55
56impl Object for Card {
57 fn display(&self, _cx: &mut Cx) -> Result<String> {
58 Ok(format!("#<card {:?}>", self.subject))
59 }
60
61 fn as_any(&self) -> &dyn std::any::Any {
62 self
63 }
64}
65
66impl crate::ObjectCompat for Card {
67 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
68 let symbol = Symbol::qualified("core", "Card");
69 if let Some(value) = cx.registry().class_by_symbol(&symbol) {
70 return Ok(value.clone());
71 }
72 cx.factory().class_stub(CORE_CARD_CLASS_ID, symbol)
73 }
74 fn as_table(&self, cx: &mut Cx) -> Result<Value> {
75 cx.factory().table(self.entries.clone())
76 }
77 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
78 self.as_table(cx)?.object().as_expr(cx)
79 }
80}
81
82pub fn card_subject_predicate() -> Symbol {
84 core_symbol("subject")
85}
86
87pub fn card_kind_predicate() -> Symbol {
89 core_symbol("kind")
90}
91
92pub fn card_help_predicate() -> Symbol {
94 core_symbol("help")
95}
96
97pub fn card_args_predicate() -> Symbol {
99 core_symbol("args")
100}
101
102pub fn card_result_predicate() -> Symbol {
104 core_symbol("result")
105}
106
107pub fn card_tests_predicate() -> Symbol {
109 core_symbol("tests")
110}
111
112pub fn card_ops_predicate() -> Symbol {
114 core_symbol("ops")
115}
116
117pub fn card_requires_predicate() -> Symbol {
119 core_symbol("requires")
120}
121
122pub fn card_see_also_predicate() -> Symbol {
124 core_symbol("see-also")
125}
126
127pub fn card_shape_known_predicate() -> Symbol {
129 core_symbol("shape-known")
130}
131
132pub fn card_fixed_predicates() -> Vec<Symbol> {
134 vec![
135 card_subject_predicate(),
136 card_kind_predicate(),
137 card_help_predicate(),
138 card_args_predicate(),
139 card_result_predicate(),
140 card_tests_predicate(),
141 card_ops_predicate(),
142 card_requires_predicate(),
143 card_see_also_predicate(),
144 card_shape_known_predicate(),
145 ]
146}
147
148pub fn minimal_card(cx: &mut Cx, subject: Ref) -> Result<Value> {
150 let entries = minimal_entries(cx, &subject)?;
151 cx.factory().opaque(Arc::new(Card::new(subject, entries)))
152}
153
154pub fn card_for_ref(cx: &mut Cx, subject: Ref) -> Result<Value> {
156 card_from_parts(cx, subject, None, None)
157}
158
159pub fn card_for_value(cx: &mut Cx, value: Value) -> Result<Value> {
161 let mut resolver = TemporaryRefResolver::new();
162 let subject = resolver.ref_for_value(cx, &value)?;
163 card_from_parts(cx, subject, Some(value), None)
164}
165
166pub fn card_for_ref_with_fallback(
169 cx: &mut Cx,
170 subject: Ref,
171 fallback: Option<Value>,
172 default_kind: Option<Symbol>,
173) -> Result<Value> {
174 card_from_parts(cx, subject, fallback, default_kind)
175}
176
177fn card_from_parts(
178 cx: &mut Cx,
179 subject: Ref,
180 fallback: Option<Value>,
181 default_kind: Option<Symbol>,
182) -> Result<Value> {
183 let fallback = fallback_entries(cx, fallback.as_ref())?;
184 insert_compatibility_claims(cx, &subject, &fallback, default_kind.as_ref())?;
185 let entries = card_entries(cx, &subject, fallback, default_kind)?;
186 cx.factory().opaque(Arc::new(Card::new(subject, entries)))
187}
188
189fn card_entries(
190 cx: &mut Cx,
191 subject: &Ref,
192 fallback: BTreeMap<Symbol, Value>,
193 default_kind: Option<Symbol>,
194) -> Result<Vec<(Symbol, Value)>> {
195 let args = claim_scalar(cx, subject, card_args_predicate())?
196 .or_else(|| fallback.get(&field_symbol("args")).cloned());
197 let result = claim_scalar(cx, subject, card_result_predicate())?
198 .or_else(|| fallback.get(&field_symbol("result")).cloned());
199 let shape_known = claim_scalar(cx, subject, card_shape_known_predicate())?
200 .or_else(|| fallback.get(&field_symbol("shape-known")).cloned());
201
202 let kind = match claim_scalar(cx, subject, card_kind_predicate())?
203 .or_else(|| fallback.get(&field_symbol("kind")).cloned())
204 {
205 Some(value) => value,
206 None => match default_kind {
207 Some(kind) => cx.factory().symbol(kind)?,
208 None => core_symbol_value(cx, "unknown")?,
209 },
210 };
211 let help = claim_scalar(cx, subject, card_help_predicate())?
212 .or_else(|| fallback.get(&field_symbol("help")).cloned())
213 .or_else(|| fallback.get(&field_symbol("purpose")).cloned())
214 .unwrap_or(cx.factory().string(String::new())?);
215 let tests = claim_list(cx, subject, card_tests_predicate())?
216 .or_else(|| fallback.get(&field_symbol("tests")).cloned())
217 .unwrap_or(empty_list(cx)?);
218 let ops = claim_list(cx, subject, card_ops_predicate())?
219 .or_else(|| fallback.get(&field_symbol("ops")).cloned())
220 .unwrap_or(empty_list(cx)?);
221 let requires = claim_list(cx, subject, card_requires_predicate())?
222 .or_else(|| fallback.get(&field_symbol("requires")).cloned())
223 .unwrap_or(empty_list(cx)?);
224 let see_also = claim_list(cx, subject, card_see_also_predicate())?
225 .or_else(|| fallback.get(&field_symbol("see-also")).cloned())
226 .unwrap_or(empty_list(cx)?);
227
228 let mut entries = vec![
229 (field_symbol("subject"), ref_value(cx, subject)?),
230 (field_symbol("kind"), kind),
231 (field_symbol("help"), help),
232 (
233 field_symbol("args"),
234 args.clone().unwrap_or(core_symbol_value(cx, "Any")?),
235 ),
236 (
237 field_symbol("result"),
238 result.clone().unwrap_or(core_symbol_value(cx, "Any")?),
239 ),
240 (field_symbol("tests"), tests),
241 (field_symbol("ops"), ops),
242 (field_symbol("requires"), requires),
243 (field_symbol("see-also"), see_also),
244 (
245 field_symbol("shape-known"),
246 shape_known.unwrap_or(cx.factory().bool(args.is_some() || result.is_some())?),
247 ),
248 ];
249
250 if let Ref::Symbol(symbol) = subject
251 && !fallback.contains_key(&field_symbol("symbol"))
252 {
253 entries.push((
254 field_symbol("symbol"),
255 cx.factory().string(symbol.to_string())?,
256 ));
257 }
258
259 for (key, value) in fallback {
260 if !entries.iter().any(|(existing, _)| existing == &key) {
261 entries.push((key, value));
262 }
263 }
264 Ok(entries)
265}
266
267fn minimal_entries(cx: &mut Cx, subject: &Ref) -> Result<Vec<(Symbol, Value)>> {
268 Ok(vec![
269 (field_symbol("subject"), ref_value(cx, subject)?),
270 (field_symbol("kind"), core_symbol_value(cx, "unknown")?),
271 (field_symbol("help"), cx.factory().string(String::new())?),
272 (field_symbol("args"), core_symbol_value(cx, "Any")?),
273 (field_symbol("result"), core_symbol_value(cx, "Any")?),
274 (field_symbol("tests"), empty_list(cx)?),
275 (field_symbol("ops"), empty_list(cx)?),
276 (field_symbol("requires"), empty_list(cx)?),
277 (field_symbol("see-also"), empty_list(cx)?),
278 (field_symbol("shape-known"), cx.factory().bool(false)?),
279 ])
280}
281
282fn insert_compatibility_claims(
283 cx: &mut Cx,
284 subject: &Ref,
285 fallback: &BTreeMap<Symbol, Value>,
286 default_kind: Option<&Symbol>,
287) -> Result<()> {
288 let kind = fallback.get(&field_symbol("kind"));
289 if let Some(value) = kind {
290 insert_value_claim_if_missing(cx, subject, card_kind_predicate(), value)?;
291 } else if let Some(kind) = default_kind {
292 insert_ref_claim_if_missing(
293 cx,
294 subject,
295 card_kind_predicate(),
296 Ref::Symbol(kind.clone()),
297 )?;
298 }
299
300 if let Some(value) = fallback
301 .get(&field_symbol("help"))
302 .or_else(|| fallback.get(&field_symbol("purpose")))
303 {
304 insert_value_claim_if_missing(cx, subject, card_help_predicate(), value)?;
305 }
306 for (field, predicate) in [
307 ("args", card_args_predicate()),
308 ("result", card_result_predicate()),
309 ("shape-known", card_shape_known_predicate()),
310 ] {
311 if let Some(value) = fallback.get(&field_symbol(field)) {
312 insert_value_claim_if_missing(cx, subject, predicate, value)?;
313 }
314 }
315 for (field, predicate) in [
316 ("tests", card_tests_predicate()),
317 ("ops", card_ops_predicate()),
318 ("requires", card_requires_predicate()),
319 ("see-also", card_see_also_predicate()),
320 ] {
321 if let Some(value) = fallback.get(&field_symbol(field)) {
322 insert_list_claims_if_missing(cx, subject, predicate, value)?;
323 }
324 }
325 Ok(())
326}
327
328fn insert_value_claim_if_missing(
329 cx: &mut Cx,
330 subject: &Ref,
331 predicate: Symbol,
332 value: &Value,
333) -> Result<()> {
334 let Some(object) = stable_ref_for_value(cx, value)? else {
335 return Ok(());
336 };
337 insert_ref_claim_if_missing(cx, subject, predicate, object)
338}
339
340fn insert_list_claims_if_missing(
341 cx: &mut Cx,
342 subject: &Ref,
343 predicate: Symbol,
344 value: &Value,
345) -> Result<()> {
346 if !claims_for(cx, subject, predicate.clone())?.is_empty() {
347 return Ok(());
348 }
349 let Some(list) = value.object().as_list() else {
350 return insert_value_claim_if_missing(cx, subject, predicate, value);
351 };
352 for item in force_list_to_vec(cx, list, "card compatibility claims")? {
353 let Some(object) = stable_ref_for_value(cx, &item)? else {
354 continue;
355 };
356 cx.insert_fact(Claim::public(subject.clone(), predicate.clone(), object))?;
357 }
358 Ok(())
359}
360
361fn insert_ref_claim_if_missing(
362 cx: &mut Cx,
363 subject: &Ref,
364 predicate: Symbol,
365 object: Ref,
366) -> Result<()> {
367 if claims_for(cx, subject, predicate.clone())?.is_empty() {
368 cx.insert_fact(Claim::public(subject.clone(), predicate, object))?;
369 }
370 Ok(())
371}
372
373fn stable_ref_for_value(cx: &mut Cx, value: &Value) -> Result<Option<Ref>> {
374 if let Expr::Symbol(symbol) = value.object().as_expr(cx)? {
375 return Ok(Some(Ref::Symbol(symbol)));
376 }
377 let mut resolver = TemporaryRefResolver::new();
378 match resolver.ref_for_value(cx, value)? {
379 Ref::Handle(_) => Ok(None),
380 reference => Ok(Some(reference)),
381 }
382}
383
384fn claim_scalar(cx: &mut Cx, subject: &Ref, predicate: Symbol) -> Result<Option<Value>> {
385 let claims = claims_for(cx, subject, predicate)?;
386 claims
387 .first()
388 .map(|reference| claim_object_value(cx, reference))
389 .transpose()
390}
391
392fn claim_list(cx: &mut Cx, subject: &Ref, predicate: Symbol) -> Result<Option<Value>> {
393 let claims = claims_for(cx, subject, predicate)?;
394 if claims.is_empty() {
395 return Ok(None);
396 }
397 let values = claims
398 .iter()
399 .map(|reference| claim_object_value(cx, reference))
400 .collect::<Result<Vec<_>>>()?;
401 cx.factory().list(values).map(Some)
402}
403
404fn claims_for(cx: &mut Cx, subject: &Ref, predicate: Symbol) -> Result<Vec<Ref>> {
405 let claims = cx.query_facts(ClaimPattern {
406 subject: Some(subject.clone()),
407 predicate: Some(predicate),
408 object: None,
409 include_revoked: false,
410 })?;
411 Ok(claims.into_iter().map(|claim| claim.object).collect())
412}
413
414fn claim_object_value(cx: &mut Cx, reference: &Ref) -> Result<Value> {
415 match reference {
416 Ref::Symbol(symbol) => cx.factory().symbol(symbol.clone()),
417 Ref::Content(id) => {
418 let datum = cx.datum_store().get(id)?.cloned();
419 match datum {
420 Some(datum) => value_from_datum(cx, datum),
421 None => ref_value(cx, reference),
422 }
423 }
424 Ref::Handle(handle) => match cx.handles().get(handle).cloned() {
425 Some(value) => Ok(value),
426 None => ref_value(cx, reference),
427 },
428 Ref::Coord(_) => ref_value(cx, reference),
429 }
430}
431
432fn fallback_entries(cx: &mut Cx, value: Option<&Value>) -> Result<BTreeMap<Symbol, Value>> {
433 let Some(value) = value else {
434 return Ok(BTreeMap::new());
435 };
436
437 let entries = if let Some(table) = value.object().as_table_impl() {
438 table.entries(cx)?
439 } else {
440 let table = value.object().as_table(cx)?;
441 match table.object().as_table_impl() {
442 Some(table) => table.entries(cx)?,
443 None => Vec::new(),
444 }
445 };
446
447 Ok(entries.into_iter().collect())
448}
449
450pub fn ref_value(cx: &mut Cx, reference: &Ref) -> Result<Value> {
454 match reference {
455 Ref::Symbol(symbol) => cx.factory().symbol(symbol.clone()),
456 Ref::Content(id) => cx.factory().expr(content_ref_expr(id)),
457 Ref::Handle(handle) => cx.factory().expr(handle_ref_expr(*handle)),
458 Ref::Coord(coordinate) => cx.factory().expr(coord_ref_expr(coordinate)),
459 }
460}
461
462fn content_ref_expr(id: &ContentId) -> Expr {
463 ref_expr(vec![
464 (
465 Expr::Symbol(field_symbol("kind")),
466 Expr::Symbol(core_symbol("content")),
467 ),
468 (
469 Expr::Symbol(field_symbol("algorithm")),
470 Expr::Symbol(id.algorithm.clone()),
471 ),
472 (
473 Expr::Symbol(field_symbol("bytes")),
474 Expr::Bytes(id.bytes.to_vec()),
475 ),
476 ])
477}
478
479fn handle_ref_expr(handle: HandleId) -> Expr {
480 ref_expr(vec![
481 (
482 Expr::Symbol(field_symbol("kind")),
483 Expr::Symbol(core_symbol("handle")),
484 ),
485 (
486 Expr::Symbol(field_symbol("id")),
487 Expr::Bytes(handle.0.to_be_bytes().to_vec()),
488 ),
489 ])
490}
491
492fn coord_ref_expr(coordinate: &Coordinate) -> Expr {
493 ref_expr(vec![
494 (
495 Expr::Symbol(field_symbol("kind")),
496 Expr::Symbol(core_symbol("coord")),
497 ),
498 (
499 Expr::Symbol(field_symbol("space")),
500 Expr::Symbol(coordinate.space.clone()),
501 ),
502 (
503 Expr::Symbol(field_symbol("ordinal")),
504 content_ref_expr(&coordinate.ordinal),
505 ),
506 ])
507}
508
509fn ref_expr(entries: Vec<(Expr, Expr)>) -> Expr {
510 Expr::Extension {
511 tag: core_symbol("ref"),
512 payload: Box::new(Expr::Map(entries)),
513 }
514}
515
516fn empty_list(cx: &mut Cx) -> Result<Value> {
517 cx.factory().list(Vec::new())
518}
519
520fn core_symbol_value(cx: &mut Cx, name: &'static str) -> Result<Value> {
521 cx.factory().symbol(core_symbol(name))
522}
523
524fn field_symbol(name: &'static str) -> Symbol {
525 Symbol::new(name)
526}
527
528fn core_symbol(name: &'static str) -> Symbol {
529 Symbol::qualified("core", name)
530}
531
532#[cfg(test)]
533#[path = "card/tests.rs"]
534mod tests;