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