1use std::collections::BTreeMap;
8use std::fmt::Write as _;
9
10use anyhow::{Context as _, anyhow, bail};
11use conjure_cp::Model;
12use conjure_cp::ast::{
13 AbstractLiteral, DeclarationPtr, DomainPtr, Field, GroundDomain, Literal, Moo, Name, Range,
14};
15use serde_json::{Map, Number, Value as JsonValue};
16
17use crate::utils::json::sort_json_object;
18
19pub fn solutions_to_simplified_json(
21 solutions: &[BTreeMap<Name, Literal>],
22) -> anyhow::Result<JsonValue> {
23 let mut sorted = solutions.to_vec();
24 sorted.sort_by(solution_key_cmp);
25 let mut items = Vec::with_capacity(sorted.len());
26 for solution in &sorted {
27 items.push(solution_to_simplified_json(solution)?);
28 }
29 Ok(JsonValue::Array(items))
30}
31
32pub fn solutions_to_simplified_json_string(
34 solutions: &[BTreeMap<Name, Literal>],
35) -> anyhow::Result<String> {
36 let value = solutions_to_simplified_json(solutions)?;
37 Ok(format!("{}\n", serde_json::to_string_pretty(&value)?))
38}
39
40pub fn solutions_from_simplified_json(
42 value: &JsonValue,
43 domains: &BTreeMap<Name, DomainPtr>,
44) -> anyhow::Result<Vec<BTreeMap<Name, Literal>>> {
45 match value {
46 JsonValue::Array(items) => items
47 .iter()
48 .map(|item| solution_from_simplified_json(item, domains))
49 .collect(),
50 JsonValue::Object(_) => Ok(vec![solution_from_simplified_json(value, domains)?]),
51 _ => bail!("expected a JSON array or object of solutions"),
52 }
53}
54
55pub fn solutions_from_simplified_json_str(
57 text: &str,
58 domains: &BTreeMap<Name, DomainPtr>,
59) -> anyhow::Result<Vec<BTreeMap<Name, Literal>>> {
60 let value: JsonValue = serde_json::from_str(text).context("parsing solutions JSON")?;
61 solutions_from_simplified_json(&value, domains)
62}
63
64pub fn solution_to_simplified_json(
66 solution: &BTreeMap<Name, Literal>,
67) -> anyhow::Result<JsonValue> {
68 let mut object = Map::new();
69 for (name, value) in solution {
70 object.insert(name.to_string(), literal_to_simplified_json(value)?);
71 }
72 Ok(sort_json_object(&JsonValue::Object(object), false))
73}
74
75pub fn solution_from_simplified_json(
77 value: &JsonValue,
78 domains: &BTreeMap<Name, DomainPtr>,
79) -> anyhow::Result<BTreeMap<Name, Literal>> {
80 let object = value
81 .as_object()
82 .ok_or_else(|| anyhow!("each solution must be a JSON object"))?;
83 let mut solution = BTreeMap::new();
84 for (name, json_value) in object {
85 let name = Name::user(name.as_str());
86 let domain = domains.get(&name);
87 let literal = literal_from_simplified_json(json_value, domain)
88 .with_context(|| format!("parsing value for `{name}`"))?;
89 solution.insert(name, literal);
90 }
91 Ok(solution)
92}
93
94pub fn params_to_simplified_json(params: &BTreeMap<Name, Literal>) -> anyhow::Result<JsonValue> {
96 solution_to_simplified_json(params)
97}
98
99pub fn params_from_simplified_json(
101 value: &JsonValue,
102 given_domains: &BTreeMap<Name, DomainPtr>,
103) -> anyhow::Result<BTreeMap<Name, Literal>> {
104 solution_from_simplified_json(value, given_domains)
105}
106
107pub fn params_from_simplified_json_str(
109 text: &str,
110 given_domains: &BTreeMap<Name, DomainPtr>,
111) -> anyhow::Result<BTreeMap<Name, Literal>> {
112 let value: JsonValue = serde_json::from_str(text).context("parsing parameter JSON")?;
113 params_from_simplified_json(&value, given_domains)
114}
115
116pub fn literal_to_simplified_json(literal: &Literal) -> anyhow::Result<JsonValue> {
118 match literal {
119 Literal::Bool(b) => Ok(JsonValue::Bool(*b)),
120 Literal::Int(i) => Ok(JsonValue::Number(Number::from(*i))),
121 Literal::AbstractLiteral(abs) => abstract_literal_to_simplified_json(abs),
122 }
123}
124
125fn abstract_literal_to_simplified_json(
126 abs: &AbstractLiteral<Literal>,
127) -> anyhow::Result<JsonValue> {
128 match abs {
129 AbstractLiteral::Set(elems) | AbstractLiteral::MSet(elems) => elems_as_json_array(elems),
130 AbstractLiteral::Tuple(elems) | AbstractLiteral::Sequence(elems) => {
131 elems_as_json_array(elems)
132 }
133 AbstractLiteral::Record(fields) => {
134 let mut object = Map::new();
135 for field in fields {
136 object.insert(
137 field.name.to_string(),
138 literal_to_simplified_json(&field.value)?,
139 );
140 }
141 Ok(JsonValue::Object(object))
142 }
143 AbstractLiteral::Variant(field) => {
144 let mut object = Map::new();
145 object.insert(
146 field.name.to_string(),
147 literal_to_simplified_json(&field.value)?,
148 );
149 Ok(JsonValue::Object(object))
150 }
151 AbstractLiteral::Matrix(elems, index_domain) => {
152 matrix_to_simplified_json(elems, index_domain.as_ref())
153 }
154 AbstractLiteral::Function(pairs) => function_to_simplified_json(pairs),
155 AbstractLiteral::Relation(rows) => {
156 let mut items = Vec::with_capacity(rows.len());
157 for row in rows {
158 items.push(elems_as_json_array(row)?);
159 }
160 Ok(JsonValue::Array(items))
161 }
162 AbstractLiteral::Partition(parts) => {
163 let mut items = Vec::with_capacity(parts.len());
164 for part in parts {
165 items.push(elems_as_json_array(part)?);
166 }
167 Ok(JsonValue::Array(items))
168 }
169 AbstractLiteral::Permutation(cycles) => {
170 let mut items = Vec::with_capacity(cycles.len());
171 for cycle in cycles {
172 items.push(elems_as_json_array(cycle)?);
173 }
174 Ok(JsonValue::Array(items))
175 }
176 }
177}
178
179fn matrix_to_simplified_json(
180 elems: &[Literal],
181 index_domain: &GroundDomain,
182) -> anyhow::Result<JsonValue> {
183 let GroundDomain::Int(_) = index_domain else {
184 return elems_as_json_array(elems);
185 };
186
187 let indices: Vec<Literal> = match index_domain.values() {
188 Ok(iter) => iter.collect(),
189 Err(_) => return elems_as_json_array(elems),
190 };
191
192 if indices.len() != elems.len() {
193 return elems_as_json_array(elems);
194 }
195
196 dictionary_from_pairs(indices.into_iter().zip(elems.iter().cloned()))
197}
198
199fn function_to_simplified_json(pairs: &[(Literal, Literal)]) -> anyhow::Result<JsonValue> {
200 dictionary_from_pairs(pairs.iter().cloned())
201}
202
203fn dictionary_from_pairs(
204 pairs: impl IntoIterator<Item = (Literal, Literal)>,
205) -> anyhow::Result<JsonValue> {
206 let mut object_entries = Vec::new();
207 let mut array_entries = Vec::new();
208 let mut all_keys_ok = true;
209
210 for (key, value) in pairs {
211 let key_json = literal_to_simplified_json(&key)?;
212 let value_json = literal_to_simplified_json(&value)?;
213 array_entries.push(JsonValue::Array(vec![key_json.clone(), value_json.clone()]));
214 match &key_json {
215 JsonValue::Bool(_) | JsonValue::Number(_) | JsonValue::String(_) => {
216 object_entries.push((json_key_string(&key_json)?, value_json));
217 }
218 _ => {
219 all_keys_ok = false;
220 }
221 }
222 }
223
224 if all_keys_ok {
225 Ok(JsonValue::Object(object_entries.into_iter().collect()))
226 } else {
227 Ok(JsonValue::Array(array_entries))
228 }
229}
230
231fn elems_as_json_array(elems: &[Literal]) -> anyhow::Result<JsonValue> {
232 let mut items = Vec::with_capacity(elems.len());
233 for elem in elems {
234 items.push(literal_to_simplified_json(elem)?);
235 }
236 Ok(JsonValue::Array(items))
237}
238
239fn json_key_string(value: &JsonValue) -> anyhow::Result<String> {
240 match value {
241 JsonValue::Bool(b) => Ok(b.to_string()),
242 JsonValue::Number(n) => Ok(n.to_string()),
243 JsonValue::String(s) => Ok(s.clone()),
244 _ => bail!("JSON object keys must be bool, number, or string"),
245 }
246}
247
248pub fn literal_from_simplified_json(
250 value: &JsonValue,
251 domain: Option<&DomainPtr>,
252) -> anyhow::Result<Literal> {
253 if let Some(domain) = domain {
254 if let Some(ground) = domain.as_ground() {
255 return literal_from_simplified_json_with_ground(value, ground);
256 }
257 if let Ok(ground) = domain.resolve() {
260 return literal_from_simplified_json_with_ground(value, ground.as_ref());
261 }
262 }
263 literal_from_simplified_json_unguided(value)
264}
265
266fn literal_from_simplified_json_with_ground(
267 value: &JsonValue,
268 domain: &GroundDomain,
269) -> anyhow::Result<Literal> {
270 match domain {
271 GroundDomain::Empty(_) => bail!("cannot parse a value for an empty domain"),
272 GroundDomain::Bool => match value {
273 JsonValue::Bool(b) => Ok(Literal::Bool(*b)),
274 JsonValue::Number(n) => match n.as_i64() {
275 Some(1) => Ok(Literal::Bool(true)),
276 Some(0) => Ok(Literal::Bool(false)),
277 _ => bail!("expected a boolean"),
278 },
279 _ => bail!("expected a boolean"),
280 },
281 GroundDomain::Int(_) => Ok(Literal::Int(json_to_i32(value)?)),
282 GroundDomain::Tuple(inners) => {
283 let JsonValue::Array(items) = value else {
284 bail!("expected a JSON array for a tuple");
285 };
286 if items.len() != inners.len() {
287 bail!(
288 "tuple arity mismatch: expected {}, got {}",
289 inners.len(),
290 items.len()
291 );
292 }
293 let mut elems = Vec::with_capacity(items.len());
294 for (item, inner) in items.iter().zip(inners) {
295 elems.push(literal_from_simplified_json_with_ground(item, inner)?);
296 }
297 Ok(Literal::AbstractLiteral(AbstractLiteral::Tuple(elems)))
298 }
299 GroundDomain::Record(fields) => {
300 let JsonValue::Object(object) = value else {
301 bail!("expected a JSON object for a record");
302 };
303 let mut entries = Vec::with_capacity(fields.len());
304 for field in fields {
305 let key = field.name.to_string();
306 let Some(item) = object.get(&key) else {
307 bail!("missing record field `{key}`");
308 };
309 entries.push(Field {
310 name: field.name.clone(),
311 value: literal_from_simplified_json_with_ground(item, field.value.as_ref())?,
312 });
313 }
314 Ok(Literal::AbstractLiteral(AbstractLiteral::Record(entries)))
315 }
316 GroundDomain::Variant(fields) => {
317 let JsonValue::Object(object) = value else {
318 bail!("expected a JSON object for a variant");
319 };
320 if object.len() != 1 {
321 bail!(
322 "variant value must contain exactly one alternative, got {}",
323 object.len()
324 );
325 }
326 let (key, item) = object.iter().next().expect("object has one entry");
327 let field = fields
328 .iter()
329 .find(|field| field.name.to_string() == *key)
330 .ok_or_else(|| anyhow!("unknown variant alternative `{key}`"))?;
331 Ok(Literal::AbstractLiteral(AbstractLiteral::Variant(
332 Moo::new(Field {
333 name: field.name.clone(),
334 value: literal_from_simplified_json_with_ground(item, field.value.as_ref())?,
335 }),
336 )))
337 }
338 GroundDomain::Matrix(inner, index_domains) => {
339 matrix_from_simplified_json(value, inner.as_ref(), index_domains)
340 }
341 GroundDomain::Sequence(_, inner) => match value {
342 JsonValue::Array(items) => {
343 let mut elems = Vec::with_capacity(items.len());
344 for item in items {
345 elems.push(literal_from_simplified_json_with_ground(item, inner)?);
346 }
347 Ok(Literal::AbstractLiteral(AbstractLiteral::Sequence(elems)))
348 }
349 JsonValue::Object(object) => sequence_from_object(object, inner),
350 _ => bail!("expected a JSON array for a sequence"),
351 },
352 GroundDomain::Set(_, inner) => {
353 let JsonValue::Array(items) = value else {
354 bail!("expected a JSON array for a set");
355 };
356 let mut elems = Vec::with_capacity(items.len());
357 for item in items {
358 elems.push(literal_from_simplified_json_with_ground(item, inner)?);
359 }
360 Ok(Literal::AbstractLiteral(AbstractLiteral::Set(elems)))
361 }
362 GroundDomain::MSet(_, inner) => {
363 let JsonValue::Array(items) = value else {
364 bail!("expected a JSON array for a multiset");
365 };
366 let mut elems = Vec::with_capacity(items.len());
367 for item in items {
368 elems.push(literal_from_simplified_json_with_ground(item, inner)?);
369 }
370 Ok(Literal::AbstractLiteral(AbstractLiteral::MSet(elems)))
371 }
372 GroundDomain::Function(_, from, to) => {
373 function_from_simplified_json(value, from.as_ref(), to.as_ref())
374 }
375 GroundDomain::Relation(_, inner_doms) => relation_from_simplified_json(value, inner_doms),
376 GroundDomain::Partition(_, inner) => partition_from_simplified_json(value, inner),
377 GroundDomain::Permutation(_, inner) => permutation_from_simplified_json(value, inner),
378 }
379}
380
381fn partition_from_simplified_json(
382 value: &JsonValue,
383 inner: &GroundDomain,
384) -> anyhow::Result<Literal> {
385 let JsonValue::Array(items) = value else {
386 bail!("expected a JSON array for a partition");
387 };
388 let mut parts = Vec::with_capacity(items.len());
389 for item in items {
390 let JsonValue::Array(elems) = item else {
391 bail!("expected a JSON array for a partition part");
392 };
393 let mut part = Vec::with_capacity(elems.len());
394 for elem in elems {
395 part.push(literal_from_simplified_json_with_ground(elem, inner)?);
396 }
397 parts.push(part);
398 }
399 Ok(Literal::AbstractLiteral(AbstractLiteral::Partition(parts)))
400}
401
402fn permutation_from_simplified_json(
403 value: &JsonValue,
404 inner: &GroundDomain,
405) -> anyhow::Result<Literal> {
406 let JsonValue::Array(items) = value else {
407 bail!("expected a JSON array for a permutation");
408 };
409 let mut cycles = Vec::with_capacity(items.len());
410 for item in items {
411 let JsonValue::Array(elems) = item else {
412 bail!("expected a JSON array for a permutation cycle");
413 };
414 let mut cycle = Vec::with_capacity(elems.len());
415 for elem in elems {
416 cycle.push(literal_from_simplified_json_with_ground(elem, inner)?);
417 }
418 cycles.push(cycle);
419 }
420 Ok(Literal::AbstractLiteral(AbstractLiteral::Permutation(
421 cycles,
422 )))
423}
424
425fn relation_from_simplified_json(
426 value: &JsonValue,
427 inner_doms: &[Moo<GroundDomain>],
428) -> anyhow::Result<Literal> {
429 let JsonValue::Array(items) = value else {
430 bail!("expected a JSON array for a relation");
431 };
432 let mut tuples = Vec::with_capacity(items.len());
433 for item in items {
434 let JsonValue::Array(fields) = item else {
435 bail!("expected a JSON array for a relation tuple");
436 };
437 if fields.len() != inner_doms.len() {
438 bail!(
439 "relation tuple arity mismatch: expected {}, got {}",
440 inner_doms.len(),
441 fields.len()
442 );
443 }
444 let mut tuple = Vec::with_capacity(fields.len());
445 for (field, dom) in fields.iter().zip(inner_doms) {
446 tuple.push(literal_from_simplified_json_with_ground(field, dom)?);
447 }
448 tuples.push(tuple);
449 }
450 Ok(Literal::AbstractLiteral(AbstractLiteral::Relation(tuples)))
451}
452
453fn matrix_from_simplified_json(
454 value: &JsonValue,
455 inner: &GroundDomain,
456 index_domains: &[Moo<GroundDomain>],
457) -> anyhow::Result<Literal> {
458 let (first_index, rest) = index_domains
459 .split_first()
460 .ok_or_else(|| anyhow!("matrix domain has no index domains"))?;
461
462 let elem_domain: GroundDomain = if rest.is_empty() {
463 inner.clone()
464 } else {
465 GroundDomain::Matrix(Moo::new(inner.clone()), rest.to_vec())
466 };
467
468 match value {
469 JsonValue::Object(object) => {
470 let mut pairs = Vec::with_capacity(object.len());
471 for (key, item) in object {
472 let index = index_key_to_literal(key, first_index)?;
473 let elem = literal_from_simplified_json_with_ground(item, &elem_domain)?;
474 pairs.push((index, elem));
475 }
476 pairs.sort_by(|a, b| a.0.essence_cmp(&b.0));
477 let keys: Vec<i32> = object
478 .keys()
479 .map(|k| k.parse::<i32>())
480 .collect::<Result<Vec<_>, _>>()
481 .unwrap_or_default();
482 let elems: Vec<Literal> = pairs.into_iter().map(|(_, v)| v).collect();
483 let index_domain = if let Ok(expected) = first_index.values() {
484 let expected: Vec<_> = expected.collect();
485 if expected.len() == elems.len() {
486 first_index.as_ref().clone()
487 } else {
488 infer_int_index_domain(&keys)
489 }
490 } else {
491 infer_int_index_domain(&keys)
492 };
493 Ok(Literal::AbstractLiteral(AbstractLiteral::Matrix(
494 elems,
495 index_domain.into(),
496 )))
497 }
498 JsonValue::Array(items) => {
499 let mut elems = Vec::with_capacity(items.len());
500 for item in items {
501 elems.push(literal_from_simplified_json_with_ground(
502 item,
503 &elem_domain,
504 )?);
505 }
506 let n = i32::try_from(elems.len()).context("matrix too large")?;
507 let index_domain = GroundDomain::Int(vec![Range::Bounded(1, n)]);
508 Ok(Literal::AbstractLiteral(AbstractLiteral::Matrix(
509 elems,
510 index_domain.into(),
511 )))
512 }
513 _ => bail!("expected a JSON object or array for a matrix"),
514 }
515}
516
517fn infer_int_index_domain(keys: &[i32]) -> GroundDomain {
518 if keys.is_empty() {
519 return GroundDomain::Int(vec![]);
520 }
521 let mut ints = keys.to_vec();
522 ints.sort_unstable();
523 let min = ints[0];
524 let max = *ints.last().expect("non-empty");
525 if max - min + 1 == ints.len() as i32 {
526 GroundDomain::Int(vec![Range::Bounded(min, max)])
527 } else {
528 GroundDomain::Int(ints.into_iter().map(Range::Single).collect())
529 }
530}
531
532fn index_key_to_literal(key: &str, index_domain: &GroundDomain) -> anyhow::Result<Literal> {
533 match index_domain {
534 GroundDomain::Bool => match key {
535 "false" => Ok(Literal::Bool(false)),
536 "true" => Ok(Literal::Bool(true)),
537 _ => bail!("expected boolean matrix index key"),
538 },
539 _ => Ok(Literal::Int(key.parse::<i32>().with_context(|| {
540 format!("expected integer matrix index key, got `{key}`")
541 })?)),
542 }
543}
544
545fn sequence_from_object(
546 object: &Map<String, JsonValue>,
547 inner: &GroundDomain,
548) -> anyhow::Result<Literal> {
549 let mut pairs = Vec::with_capacity(object.len());
550 for (key, item) in object {
551 let index: i32 = key
552 .parse()
553 .with_context(|| format!("sequence index `{key}` is not an integer"))?;
554 pairs.push((
555 index,
556 literal_from_simplified_json_with_ground(item, inner)?,
557 ));
558 }
559 pairs.sort_by_key(|(i, _)| *i);
560 Ok(Literal::AbstractLiteral(AbstractLiteral::Sequence(
561 pairs.into_iter().map(|(_, v)| v).collect(),
562 )))
563}
564
565fn function_from_simplified_json(
566 value: &JsonValue,
567 from: &GroundDomain,
568 to: &GroundDomain,
569) -> anyhow::Result<Literal> {
570 match value {
571 JsonValue::Object(object) => {
572 let mut pairs = Vec::with_capacity(object.len());
573 for (key, item) in object {
574 let domain_key = index_key_to_literal(key, from)?;
575 let mapped = literal_from_simplified_json_with_ground(item, to)?;
576 pairs.push((domain_key, mapped));
577 }
578 Ok(Literal::AbstractLiteral(AbstractLiteral::Function(pairs)))
579 }
580 JsonValue::Array(items) => {
581 let mut pairs = Vec::with_capacity(items.len());
582 for item in items {
583 let JsonValue::Array(pair) = item else {
584 bail!("function array entries must be [from, to] pairs");
585 };
586 if pair.len() != 2 {
587 bail!("function array entries must be [from, to] pairs");
588 }
589 pairs.push((
590 literal_from_simplified_json_with_ground(&pair[0], from)?,
591 literal_from_simplified_json_with_ground(&pair[1], to)?,
592 ));
593 }
594 Ok(Literal::AbstractLiteral(AbstractLiteral::Function(pairs)))
595 }
596 _ => bail!("expected a JSON object or array for a function"),
597 }
598}
599
600fn literal_from_simplified_json_unguided(value: &JsonValue) -> anyhow::Result<Literal> {
601 match value {
602 JsonValue::Bool(b) => Ok(Literal::Bool(*b)),
603 JsonValue::Number(_) | JsonValue::String(_) => Ok(Literal::Int(json_to_i32(value)?)),
604 JsonValue::Array(items) => {
605 let mut elems = Vec::with_capacity(items.len());
606 for item in items {
607 elems.push(literal_from_simplified_json_unguided(item)?);
608 }
609 Ok(Literal::AbstractLiteral(AbstractLiteral::Set(elems)))
610 }
611 JsonValue::Object(object) => {
612 let all_int_keys = object.keys().all(|key| key.parse::<i32>().is_ok());
613 if all_int_keys {
614 let mut pairs = Vec::with_capacity(object.len());
615 for (key, item) in object {
616 let index: i32 = key.parse().with_context(|| {
617 format!("unguided object key `{key}` is not an integer")
618 })?;
619 pairs.push((index, literal_from_simplified_json_unguided(item)?));
620 }
621 pairs.sort_by_key(|(i, _)| *i);
622 let keys: Vec<i32> = pairs.iter().map(|(i, _)| *i).collect();
623 let elems: Vec<_> = pairs.into_iter().map(|(_, v)| v).collect();
624 return Ok(Literal::AbstractLiteral(AbstractLiteral::Matrix(
625 elems,
626 infer_int_index_domain(&keys).into(),
627 )));
628 }
629
630 let mut entries = Vec::with_capacity(object.len());
632 for (key, item) in object {
633 entries.push(Field {
634 name: Name::user(key.as_str()),
635 value: literal_from_simplified_json_unguided(item)?,
636 });
637 }
638 entries.sort_by(|a, b| a.name.cmp(&b.name));
639 Ok(Literal::AbstractLiteral(AbstractLiteral::Record(entries)))
640 }
641 JsonValue::Null => bail!("null is not a valid Essence literal"),
642 }
643}
644
645fn json_to_i32(value: &JsonValue) -> anyhow::Result<i32> {
646 match value {
647 JsonValue::Number(n) => n
648 .as_i64()
649 .and_then(|i| i32::try_from(i).ok())
650 .ok_or_else(|| anyhow!("expected a 32-bit integer")),
651 JsonValue::String(s) => s
652 .parse::<i32>()
653 .with_context(|| format!("expected an integer string, got `{s}`")),
654 _ => bail!("expected an integer"),
655 }
656}
657
658fn solution_key_cmp(
659 lhs: &BTreeMap<Name, Literal>,
660 rhs: &BTreeMap<Name, Literal>,
661) -> std::cmp::Ordering {
662 lhs.iter()
663 .zip(rhs)
664 .find_map(|((lhs_name, lhs_value), (rhs_name, rhs_value))| {
665 let ordering = lhs_name.cmp(rhs_name);
666 (ordering != std::cmp::Ordering::Equal)
667 .then_some(ordering)
668 .or_else(|| {
669 let ordering = lhs_value.essence_cmp(rhs_value);
670 (ordering != std::cmp::Ordering::Equal).then_some(ordering)
671 })
672 })
673 .unwrap_or_else(|| lhs.len().cmp(&rhs.len()))
674}
675
676pub fn domains_from_model(model: &Model) -> BTreeMap<Name, DomainPtr> {
678 let mut domains = BTreeMap::new();
679 for (name, decl) in model.symbols().clone().into_iter() {
680 if let Some(domain) = decl.domain() {
681 domains.insert(name, domain);
682 }
683 }
684 domains
685}
686
687pub fn param_model_from_assignments(
689 params: BTreeMap<Name, Literal>,
690 given_domains: &BTreeMap<Name, DomainPtr>,
691 context: std::sync::Arc<std::sync::RwLock<conjure_cp::context::Context<'static>>>,
692) -> Model {
693 let mut model = Model::new(context);
694 for (name, literal) in params {
695 let decl = match given_domains.get(&name) {
696 Some(domain) => {
697 DeclarationPtr::new_value_letting_with_domain(name, literal.into(), domain.clone())
698 }
699 None => DeclarationPtr::new_value_letting(name, literal.into()),
700 };
701 model.symbols_mut().update_insert(decl);
702 }
703 model
704}
705
706pub fn canonical_simplified_json_string(value: &JsonValue) -> anyhow::Result<String> {
708 let sorted = sort_json_object(value, true);
709 Ok(format!("{}\n", serde_json::to_string_pretty(&sorted)?))
710}
711
712pub fn write_simplified_json(value: &JsonValue, out: &mut String) -> anyhow::Result<()> {
714 write!(out, "{}", serde_json::to_string_pretty(value)?)?;
715 out.push('\n');
716 Ok(())
717}
718
719#[cfg(test)]
720mod tests {
721 use super::*;
722 use conjure_cp::ast::{Domain, SetAttr};
723
724 fn int_dom(lo: i32, hi: i32) -> DomainPtr {
725 Domain::int(vec![Range::Bounded(lo, hi)])
726 }
727
728 #[test]
729 fn round_trips_scalars_and_set() {
730 let mut domains = BTreeMap::new();
731 domains.insert(Name::user("x"), int_dom(1, 3));
732 domains.insert(
733 Name::user("s"),
734 Domain::set(SetAttr::<i32>::default(), int_dom(1, 3)),
735 );
736
737 let json = serde_json::json!([{"s": [1, 3], "x": 2}]);
738 let solutions = solutions_from_simplified_json(&json, &domains).unwrap();
739 assert_eq!(solutions.len(), 1);
740 assert_eq!(solutions[0].get(&Name::user("x")), Some(&Literal::Int(2)));
741 let Literal::AbstractLiteral(AbstractLiteral::Set(elems)) =
742 solutions[0].get(&Name::user("s")).unwrap()
743 else {
744 panic!("expected set");
745 };
746 assert_eq!(elems, &vec![Literal::Int(1), Literal::Int(3)]);
747
748 let rendered = solutions_to_simplified_json(&solutions).unwrap();
749 let again = solutions_from_simplified_json(&rendered, &domains).unwrap();
750 assert_eq!(again, solutions);
751 }
752
753 #[test]
754 fn round_trips_int_indexed_matrix() {
755 let matrix_dom = Domain::matrix(int_dom(1, 3), vec![int_dom(1, 2)]);
756 let mut domains = BTreeMap::new();
757 domains.insert(Name::user("m"), matrix_dom);
758
759 let json = serde_json::json!([{"m": {"1": 1, "2": 2}}]);
760 let solutions = solutions_from_simplified_json(&json, &domains).unwrap();
761 let rendered = solutions_to_simplified_json(&solutions).unwrap();
762 let again = solutions_from_simplified_json(&rendered, &domains).unwrap();
763 assert_eq!(again, solutions);
764 }
765
766 #[test]
767 fn guided_variant_object_parses_as_variant() {
768 let mut domains = BTreeMap::new();
769 domains.insert(
770 Name::user("x"),
771 Domain::variant(vec![
772 Field {
773 name: Name::user("flag"),
774 value: Domain::bool(),
775 },
776 Field {
777 name: Name::user("value"),
778 value: int_dom(1, 3),
779 },
780 ]),
781 );
782
783 let json = serde_json::json!([{"x": {"value": 2}}]);
784 let solutions = solutions_from_simplified_json(&json, &domains).unwrap();
785 let expected = Literal::AbstractLiteral(AbstractLiteral::Variant(Moo::new(Field {
786 name: Name::user("value"),
787 value: Literal::Int(2),
788 })));
789 assert_eq!(solutions[0].get(&Name::user("x")), Some(&expected));
790
791 let rendered = solutions_to_simplified_json(&solutions).unwrap();
792 assert_eq!(
793 solutions_from_simplified_json(&rendered, &domains).unwrap(),
794 solutions
795 );
796 }
797
798 #[test]
799 fn unguided_object_with_named_fields_parses_as_record() {
800 let json = serde_json::json!({"a": true, "b": 3});
801 let literal = literal_from_simplified_json(&json, None).unwrap();
802 let Literal::AbstractLiteral(AbstractLiteral::Record(fields)) = literal else {
803 panic!("expected record, got {literal:?}");
804 };
805 assert_eq!(fields.len(), 2);
806 assert_eq!(fields[0].name, Name::user("a"));
807 assert_eq!(fields[0].value, Literal::Bool(true));
808 assert_eq!(fields[1].name, Name::user("b"));
809 assert_eq!(fields[1].value, Literal::Int(3));
810 }
811}