1use crate::{
7 card::{card_help_predicate, card_kind_predicate, card_ops_predicate},
8 claim::{Claim, ClaimPattern},
9 datum::Datum,
10 env::Cx,
11 error::Result,
12 id::Symbol,
13 ref_id::{ContentId, Coordinate, Ref},
14 term::OpKey,
15};
16
17pub fn rank_rank_op_key() -> OpKey {
19 rank_op_key("rank")
20}
21
22pub fn rank_unrank_op_key() -> OpKey {
24 rank_op_key("unrank")
25}
26
27pub fn rank_neighbors_op_key() -> OpKey {
29 rank_op_key("neighbors")
30}
31
32pub fn rank_order_next_op_key() -> OpKey {
34 rank_op_key("order-next")
35}
36
37pub fn rank_space_kind() -> Symbol {
39 rank_symbol("space")
40}
41
42pub fn rank_coordinate_kind() -> Symbol {
44 rank_symbol("coordinate")
45}
46
47pub fn rank_space_predicate() -> Symbol {
49 rank_symbol("space")
50}
51
52pub fn rank_ordinal_predicate() -> Symbol {
54 rank_symbol("ordinal")
55}
56
57pub fn rank_coordinate(space: Symbol, ordinal: ContentId) -> Ref {
83 Ref::Coord(Coordinate { space, ordinal })
84}
85
86pub fn publish_rank_space_claims(cx: &mut Cx, space: Symbol, help: Option<&str>) -> Result<()> {
89 let subject = Ref::Symbol(space);
90 insert_once(
91 cx,
92 subject.clone(),
93 card_kind_predicate(),
94 Ref::Symbol(rank_space_kind()),
95 )?;
96 for op in [
97 rank_rank_op_key(),
98 rank_unrank_op_key(),
99 rank_neighbors_op_key(),
100 rank_order_next_op_key(),
101 ] {
102 insert_once(
103 cx,
104 subject.clone(),
105 card_ops_predicate(),
106 Ref::Symbol(op_symbol(&op)),
107 )?;
108 }
109 if let Some(help) = help {
110 let help_ref = Claim::intern_object(cx.datum_store_mut(), Datum::String(help.to_owned()))?;
111 insert_once(cx, subject, card_help_predicate(), help_ref)?;
112 }
113 Ok(())
114}
115
116pub fn publish_coordinate_claims(cx: &mut Cx, coordinate: Coordinate) -> Result<()> {
119 let subject = Ref::Coord(coordinate.clone());
120 insert_once(
121 cx,
122 subject.clone(),
123 card_kind_predicate(),
124 Ref::Symbol(rank_coordinate_kind()),
125 )?;
126 insert_once(
127 cx,
128 subject.clone(),
129 rank_space_predicate(),
130 Ref::Symbol(coordinate.space),
131 )?;
132 insert_once(
133 cx,
134 subject,
135 rank_ordinal_predicate(),
136 Ref::Content(coordinate.ordinal),
137 )
138}
139
140fn insert_once(cx: &mut Cx, subject: Ref, predicate: Symbol, object: Ref) -> Result<()> {
141 let exists = !cx
142 .query_facts(ClaimPattern::exact(
143 subject.clone(),
144 predicate.clone(),
145 object.clone(),
146 ))?
147 .is_empty();
148 if !exists {
149 cx.insert_fact(Claim::public(subject, predicate, object))?;
150 }
151 Ok(())
152}
153
154fn rank_op_key(name: &str) -> OpKey {
155 OpKey::new(Symbol::new("rank"), Symbol::new(name), 1)
156}
157
158fn op_symbol(op: &OpKey) -> Symbol {
159 Symbol::qualified(
160 op.namespace.to_string(),
161 format!("{}.v{}", op.name, op.version),
162 )
163}
164
165fn rank_symbol(name: &str) -> Symbol {
166 Symbol::qualified("rank", name)
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172 use crate::{
173 DefaultFactory, Expr, NoopEvalPolicy, card::card_for_ref, datum_store::DatumStore,
174 };
175 use std::sync::Arc;
176
177 #[test]
178 fn rank_space_and_coordinate_claims_are_publishable_without_accessor() {
179 let mut cx = Cx::new(
180 Arc::new(NoopEvalPolicy),
181 Arc::new(DefaultFactory),
182 crate::HandleSeed::new(7),
183 );
184 let space = Symbol::qualified("rank", "expr-small");
185 publish_rank_space_claims(&mut cx, space.clone(), Some("small expression rank")).unwrap();
186
187 let ordinal = cx
188 .datum_store_mut()
189 .intern(Datum::String("first".to_owned()))
190 .unwrap();
191 let coordinate = Coordinate {
192 space: space.clone(),
193 ordinal: ordinal.clone(),
194 };
195 publish_coordinate_claims(&mut cx, coordinate.clone()).unwrap();
196
197 assert_has_claim(
198 &cx,
199 Ref::Symbol(space),
200 card_kind_predicate(),
201 Ref::Symbol(rank_space_kind()),
202 );
203 assert_has_claim(
204 &cx,
205 Ref::Coord(coordinate),
206 rank_ordinal_predicate(),
207 Ref::Content(ordinal),
208 );
209 }
210
211 #[test]
212 fn rank_space_and_coordinate_claims_project_to_cards() {
213 let mut cx = Cx::new(
214 Arc::new(NoopEvalPolicy),
215 Arc::new(DefaultFactory),
216 crate::HandleSeed::new(7),
217 );
218 let space = Symbol::qualified("rank", "expr-small");
219 publish_rank_space_claims(&mut cx, space.clone(), Some("small expression rank")).unwrap();
220
221 let ordinal = cx
222 .datum_store_mut()
223 .intern(Datum::String("first".to_owned()))
224 .unwrap();
225 let coordinate = Coordinate {
226 space: space.clone(),
227 ordinal,
228 };
229 publish_coordinate_claims(&mut cx, coordinate.clone()).unwrap();
230
231 let space_card = card_expr(&mut cx, Ref::Symbol(space));
232 assert_eq!(
233 table_value(&space_card, "kind"),
234 Some(&Expr::Symbol(rank_space_kind()))
235 );
236 assert_eq!(
237 table_value(&space_card, "help"),
238 Some(&Expr::String("small expression rank".to_owned()))
239 );
240 assert_list_contains_symbol(
241 table_value(&space_card, "ops").expect("rank ops"),
242 Symbol::qualified("rank", "rank.v1"),
243 );
244 assert_list_contains_symbol(
245 table_value(&space_card, "ops").expect("rank ops"),
246 Symbol::qualified("rank", "unrank.v1"),
247 );
248
249 let coordinate_card = card_expr(&mut cx, Ref::Coord(coordinate));
250 assert_eq!(
251 table_value(&coordinate_card, "kind"),
252 Some(&Expr::Symbol(rank_coordinate_kind()))
253 );
254 }
255
256 fn assert_has_claim(cx: &Cx, subject: Ref, predicate: Symbol, object: Ref) {
257 let claims = cx
258 .query_facts(ClaimPattern::exact(subject, predicate, object))
259 .unwrap();
260 assert_eq!(claims.len(), 1);
261 }
262
263 fn card_expr(cx: &mut Cx, subject: Ref) -> Expr {
264 card_for_ref(cx, subject)
265 .unwrap()
266 .object()
267 .as_expr(cx)
268 .unwrap()
269 }
270
271 fn table_value<'a>(expr: &'a Expr, key: &str) -> Option<&'a Expr> {
272 let Expr::Map(entries) = expr else {
273 return None;
274 };
275 entries.iter().find_map(|(entry_key, entry_value)| {
276 let Expr::Symbol(entry_key) = entry_key else {
277 return None;
278 };
279 (entry_key == &Symbol::new(key)).then_some(entry_value)
280 })
281 }
282
283 fn assert_list_contains_symbol(expr: &Expr, expected: Symbol) {
284 assert!(matches!(expr, Expr::List(_)), "expected list");
285 let Expr::List(items) = expr else {
286 return;
287 };
288 assert!(
289 items
290 .iter()
291 .any(|item| item == &Expr::Symbol(expected.clone())),
292 "expected list to contain {expected}"
293 );
294 }
295}