1
//! Conjure's simplified JSON format for parameters and solutions.
2
//!
3
//! Matches Conjure `--output-format=json`: integers and booleans are JSON scalars, sets and
4
//! tuples are arrays, records are objects, and int-indexed matrices are objects whose keys are
5
//! the printed index values. Non-int-indexed matrices encode as arrays.
6

            
7
use std::collections::BTreeMap;
8
use std::fmt::Write as _;
9

            
10
use anyhow::{Context as _, anyhow, bail};
11
use conjure_cp::Model;
12
use conjure_cp::ast::{
13
    AbstractLiteral, DeclarationPtr, DomainPtr, Field, GroundDomain, Literal, Moo, Name, Range,
14
};
15
use serde_json::{Map, Number, Value as JsonValue};
16

            
17
use crate::utils::json::sort_json_object;
18

            
19
/// Render solutions as a JSON array of assignment objects (Conjure `--solutions-in-one-file`).
20
3
pub fn solutions_to_simplified_json(
21
3
    solutions: &[BTreeMap<Name, Literal>],
22
3
) -> anyhow::Result<JsonValue> {
23
3
    let mut sorted = solutions.to_vec();
24
3
    sorted.sort_by(solution_key_cmp);
25
3
    let mut items = Vec::with_capacity(sorted.len());
26
3
    for solution in &sorted {
27
3
        items.push(solution_to_simplified_json(solution)?);
28
    }
29
3
    Ok(JsonValue::Array(items))
30
3
}
31

            
32
/// Pretty-print solutions in Conjure's simplified JSON layout.
33
pub 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

            
40
/// Parse a Conjure-style solutions JSON document (array, or a single object).
41
8
pub fn solutions_from_simplified_json(
42
8
    value: &JsonValue,
43
8
    domains: &BTreeMap<Name, DomainPtr>,
44
8
) -> anyhow::Result<Vec<BTreeMap<Name, Literal>>> {
45
8
    match value {
46
8
        JsonValue::Array(items) => items
47
8
            .iter()
48
8
            .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
8
}
54

            
55
/// Parse solutions JSON text.
56
2
pub fn solutions_from_simplified_json_str(
57
2
    text: &str,
58
2
    domains: &BTreeMap<Name, DomainPtr>,
59
2
) -> anyhow::Result<Vec<BTreeMap<Name, Literal>>> {
60
2
    let value: JsonValue = serde_json::from_str(text).context("parsing solutions JSON")?;
61
2
    solutions_from_simplified_json(&value, domains)
62
2
}
63

            
64
/// Render a single assignment as a simplified JSON object.
65
3
pub fn solution_to_simplified_json(
66
3
    solution: &BTreeMap<Name, Literal>,
67
3
) -> anyhow::Result<JsonValue> {
68
3
    let mut object = Map::new();
69
3
    for (name, value) in solution {
70
4
        object.insert(name.to_string(), literal_to_simplified_json(value)?);
71
    }
72
3
    Ok(sort_json_object(&JsonValue::Object(object), false))
73
3
}
74

            
75
/// Parse a single assignment object.
76
1176
pub fn solution_from_simplified_json(
77
1176
    value: &JsonValue,
78
1176
    domains: &BTreeMap<Name, DomainPtr>,
79
1176
) -> anyhow::Result<BTreeMap<Name, Literal>> {
80
1176
    let object = value
81
1176
        .as_object()
82
        .ok_or_else(|| anyhow!("each solution must be a JSON object"))?;
83
1176
    let mut solution = BTreeMap::new();
84
1176
    for (name, json_value) in object {
85
2346
        let name = Name::user(name.as_str());
86
2346
        let domain = domains.get(&name);
87
2346
        let literal = literal_from_simplified_json(json_value, domain)
88
            .with_context(|| format!("parsing value for `{name}`"))?;
89
2346
        solution.insert(name, literal);
90
    }
91
1176
    Ok(solution)
92
1176
}
93

            
94
/// Render a parameter file as a single simplified JSON object.
95
pub fn params_to_simplified_json(params: &BTreeMap<Name, Literal>) -> anyhow::Result<JsonValue> {
96
    solution_to_simplified_json(params)
97
}
98

            
99
/// Parse a parameter JSON object using given domains from the problem model.
100
1168
pub fn params_from_simplified_json(
101
1168
    value: &JsonValue,
102
1168
    given_domains: &BTreeMap<Name, DomainPtr>,
103
1168
) -> anyhow::Result<BTreeMap<Name, Literal>> {
104
1168
    solution_from_simplified_json(value, given_domains)
105
1168
}
106

            
107
/// Parse parameter JSON text.
108
1168
pub fn params_from_simplified_json_str(
109
1168
    text: &str,
110
1168
    given_domains: &BTreeMap<Name, DomainPtr>,
111
1168
) -> anyhow::Result<BTreeMap<Name, Literal>> {
112
1168
    let value: JsonValue = serde_json::from_str(text).context("parsing parameter JSON")?;
113
1168
    params_from_simplified_json(&value, given_domains)
114
1168
}
115

            
116
/// Encode a literal in Conjure's simplified JSON format.
117
11
pub fn literal_to_simplified_json(literal: &Literal) -> anyhow::Result<JsonValue> {
118
11
    match literal {
119
        Literal::Unnamed(_) => bail!("simplified JSON for unnamed types is not supported yet"),
120
        Literal::EnumVariant(_) => {
121
            bail!("simplified JSON for enumerated types is not supported yet")
122
        }
123
        Literal::Bool(b) => Ok(JsonValue::Bool(*b)),
124
8
        Literal::Int(i) => Ok(JsonValue::Number(Number::from(*i))),
125
3
        Literal::AbstractLiteral(abs) => abstract_literal_to_simplified_json(abs),
126
    }
127
11
}
128

            
129
3
fn abstract_literal_to_simplified_json(
130
3
    abs: &AbstractLiteral<Literal>,
131
3
) -> anyhow::Result<JsonValue> {
132
3
    match abs {
133
1
        AbstractLiteral::Set(elems) | AbstractLiteral::MSet(elems) => elems_as_json_array(elems),
134
        AbstractLiteral::Tuple(elems) | AbstractLiteral::Sequence(elems) => {
135
            elems_as_json_array(elems)
136
        }
137
        AbstractLiteral::Record(fields) => {
138
            let mut object = Map::new();
139
            for field in fields {
140
                object.insert(
141
                    field.name.to_string(),
142
                    literal_to_simplified_json(&field.value)?,
143
                );
144
            }
145
            Ok(JsonValue::Object(object))
146
        }
147
        AbstractLiteral::Variant(field) => {
148
1
            let mut object = Map::new();
149
1
            object.insert(
150
1
                field.name.to_string(),
151
1
                literal_to_simplified_json(&field.value)?,
152
            );
153
1
            Ok(JsonValue::Object(object))
154
        }
155
        AbstractLiteral::Matrix(elems, index_domain) => {
156
1
            matrix_to_simplified_json(elems, index_domain.as_ref())
157
        }
158
        AbstractLiteral::Function(pairs) => function_to_simplified_json(pairs),
159
        AbstractLiteral::Relation(rows) => {
160
            let mut items = Vec::with_capacity(rows.len());
161
            for row in rows {
162
                items.push(elems_as_json_array(row)?);
163
            }
164
            Ok(JsonValue::Array(items))
165
        }
166
        AbstractLiteral::Partition(parts) => {
167
            let mut items = Vec::with_capacity(parts.len());
168
            for part in parts {
169
                items.push(elems_as_json_array(part)?);
170
            }
171
            Ok(JsonValue::Array(items))
172
        }
173
        AbstractLiteral::Permutation(cycles) => {
174
            let mut items = Vec::with_capacity(cycles.len());
175
            for cycle in cycles {
176
                items.push(elems_as_json_array(cycle)?);
177
            }
178
            Ok(JsonValue::Array(items))
179
        }
180
    }
181
3
}
182

            
183
1
fn matrix_to_simplified_json(
184
1
    elems: &[Literal],
185
1
    index_domain: &GroundDomain,
186
1
) -> anyhow::Result<JsonValue> {
187
1
    let GroundDomain::Int(_, _) = index_domain else {
188
        return elems_as_json_array(elems);
189
    };
190

            
191
1
    let indices: Vec<Literal> = match index_domain.values() {
192
1
        Ok(iter) => iter.collect(),
193
        Err(_) => return elems_as_json_array(elems),
194
    };
195

            
196
1
    if indices.len() != elems.len() {
197
        return elems_as_json_array(elems);
198
1
    }
199

            
200
1
    dictionary_from_pairs(indices.into_iter().zip(elems.iter().cloned()))
201
1
}
202

            
203
fn function_to_simplified_json(pairs: &[(Literal, Literal)]) -> anyhow::Result<JsonValue> {
204
    dictionary_from_pairs(pairs.iter().cloned())
205
}
206

            
207
1
fn dictionary_from_pairs(
208
1
    pairs: impl IntoIterator<Item = (Literal, Literal)>,
209
1
) -> anyhow::Result<JsonValue> {
210
1
    let mut object_entries = Vec::new();
211
1
    let mut array_entries = Vec::new();
212
1
    let mut all_keys_ok = true;
213

            
214
1
    for (key, value) in pairs {
215
2
        let key_json = literal_to_simplified_json(&key)?;
216
2
        let value_json = literal_to_simplified_json(&value)?;
217
2
        array_entries.push(JsonValue::Array(vec![key_json.clone(), value_json.clone()]));
218
2
        match &key_json {
219
            JsonValue::Bool(_) | JsonValue::Number(_) | JsonValue::String(_) => {
220
2
                object_entries.push((json_key_string(&key_json)?, value_json));
221
            }
222
            _ => {
223
                all_keys_ok = false;
224
            }
225
        }
226
    }
227

            
228
1
    if all_keys_ok {
229
1
        Ok(JsonValue::Object(object_entries.into_iter().collect()))
230
    } else {
231
        Ok(JsonValue::Array(array_entries))
232
    }
233
1
}
234

            
235
1
fn elems_as_json_array(elems: &[Literal]) -> anyhow::Result<JsonValue> {
236
1
    let mut items = Vec::with_capacity(elems.len());
237
1
    for elem in elems {
238
2
        items.push(literal_to_simplified_json(elem)?);
239
    }
240
1
    Ok(JsonValue::Array(items))
241
1
}
242

            
243
2
fn json_key_string(value: &JsonValue) -> anyhow::Result<String> {
244
2
    match value {
245
        JsonValue::Bool(b) => Ok(b.to_string()),
246
2
        JsonValue::Number(n) => Ok(n.to_string()),
247
        JsonValue::String(s) => Ok(s.clone()),
248
        _ => bail!("JSON object keys must be bool, number, or string"),
249
    }
250
2
}
251

            
252
/// Decode a simplified JSON value, using `domain` to disambiguate arrays and objects.
253
2347
pub fn literal_from_simplified_json(
254
2347
    value: &JsonValue,
255
2347
    domain: Option<&DomainPtr>,
256
2347
) -> anyhow::Result<Literal> {
257
2347
    if let Some(domain) = domain {
258
2346
        if let Some(ground) = domain.as_ground() {
259
10
            return literal_from_simplified_json_with_ground(value, ground);
260
2336
        }
261
        // Domain lettings (e.g. `find x : R`) are unresolved until resolved through the symbol
262
        // table; resolve them so records/sets/etc. are not misread as matrices.
263
2336
        if let Ok(ground) = domain.resolve() {
264
2336
            return literal_from_simplified_json_with_ground(value, ground.as_ref());
265
        }
266
1
    }
267
1
    literal_from_simplified_json_unguided(value)
268
2347
}
269

            
270
12868
fn literal_from_simplified_json_with_ground(
271
12868
    value: &JsonValue,
272
12868
    domain: &GroundDomain,
273
12868
) -> anyhow::Result<Literal> {
274
12868
    match domain {
275
        GroundDomain::EnumeratedType(_, _) | GroundDomain::UnnamedType(_) => {
276
            bail!("simplified JSON for enumerated and unnamed types is not supported yet")
277
        }
278
        GroundDomain::Empty(_) => bail!("cannot parse a value for an empty domain"),
279
        GroundDomain::Bool => match value {
280
            JsonValue::Bool(b) => Ok(Literal::Bool(*b)),
281
            JsonValue::Number(n) => match n.as_i64() {
282
                Some(1) => Ok(Literal::Bool(true)),
283
                Some(0) => Ok(Literal::Bool(false)),
284
                _ => bail!("expected a boolean"),
285
            },
286
            _ => bail!("expected a boolean"),
287
        },
288
5854
        GroundDomain::Int(_, _) => Ok(Literal::Int(json_to_i32(value)?)),
289
        GroundDomain::Tuple(inners, _) => {
290
            let JsonValue::Array(items) = value else {
291
                bail!("expected a JSON array for a tuple");
292
            };
293
            if items.len() != inners.len() {
294
                bail!(
295
                    "tuple arity mismatch: expected {}, got {}",
296
                    inners.len(),
297
                    items.len()
298
                );
299
            }
300
            let mut elems = Vec::with_capacity(items.len());
301
            for (item, inner) in items.iter().zip(inners) {
302
                elems.push(literal_from_simplified_json_with_ground(item, inner)?);
303
            }
304
            Ok(Literal::AbstractLiteral(AbstractLiteral::Tuple(elems)))
305
        }
306
        GroundDomain::Record(fields, _) => {
307
            let JsonValue::Object(object) = value else {
308
                bail!("expected a JSON object for a record");
309
            };
310
            let mut entries = Vec::with_capacity(fields.len());
311
            for field in fields {
312
                let key = field.name.to_string();
313
                let Some(item) = object.get(&key) else {
314
                    bail!("missing record field `{key}`");
315
                };
316
                entries.push(Field {
317
                    name: field.name.clone(),
318
                    value: literal_from_simplified_json_with_ground(item, field.value.as_ref())?,
319
                });
320
            }
321
            Ok(Literal::AbstractLiteral(AbstractLiteral::Record(entries)))
322
        }
323
        GroundDomain::Variant(fields, _) => {
324
5842
            let JsonValue::Object(object) = value else {
325
                bail!("expected a JSON object for a variant");
326
            };
327
5842
            if object.len() != 1 {
328
                bail!(
329
                    "variant value must contain exactly one alternative, got {}",
330
                    object.len()
331
                );
332
5842
            }
333
5842
            let (key, item) = object.iter().next().expect("object has one entry");
334
5842
            let field = fields
335
5842
                .iter()
336
8472
                .find(|field| field.name.to_string() == *key)
337
                .ok_or_else(|| anyhow!("unknown variant alternative `{key}`"))?;
338
            Ok(Literal::AbstractLiteral(AbstractLiteral::Variant(
339
5842
                Moo::new(Field {
340
5842
                    name: field.name.clone(),
341
5842
                    value: literal_from_simplified_json_with_ground(item, field.value.as_ref())?,
342
                }),
343
            )))
344
        }
345
        GroundDomain::Matrix(inner, index_domains, _) => {
346
2
            matrix_from_simplified_json(value, inner.as_ref(), index_domains)
347
        }
348
        GroundDomain::Sequence(_, inner) => match value {
349
            JsonValue::Array(items) => {
350
                let mut elems = Vec::with_capacity(items.len());
351
                for item in items {
352
                    elems.push(literal_from_simplified_json_with_ground(item, inner)?);
353
                }
354
                Ok(Literal::AbstractLiteral(AbstractLiteral::Sequence(elems)))
355
            }
356
            JsonValue::Object(object) => sequence_from_object(object, inner),
357
            _ => bail!("expected a JSON array for a sequence"),
358
        },
359
        GroundDomain::Set(_, inner) => {
360
1170
            let JsonValue::Array(items) = value else {
361
                bail!("expected a JSON array for a set");
362
            };
363
1170
            let mut elems = Vec::with_capacity(items.len());
364
1170
            for item in items {
365
4676
                elems.push(literal_from_simplified_json_with_ground(item, inner)?);
366
            }
367
1170
            Ok(Literal::AbstractLiteral(AbstractLiteral::Set(elems)))
368
        }
369
        GroundDomain::MSet(_, inner) => {
370
            let JsonValue::Array(items) = value else {
371
                bail!("expected a JSON array for a multiset");
372
            };
373
            let mut elems = Vec::with_capacity(items.len());
374
            for item in items {
375
                elems.push(literal_from_simplified_json_with_ground(item, inner)?);
376
            }
377
            Ok(Literal::AbstractLiteral(AbstractLiteral::MSet(elems)))
378
        }
379
        GroundDomain::Function(_, from, to) => {
380
            function_from_simplified_json(value, from.as_ref(), to.as_ref())
381
        }
382
        GroundDomain::Relation(_, inner_doms) => relation_from_simplified_json(value, inner_doms),
383
        GroundDomain::Partition(_, inner) => partition_from_simplified_json(value, inner),
384
        GroundDomain::Permutation(_, inner) => permutation_from_simplified_json(value, inner),
385
    }
386
12868
}
387

            
388
fn partition_from_simplified_json(
389
    value: &JsonValue,
390
    inner: &GroundDomain,
391
) -> anyhow::Result<Literal> {
392
    let JsonValue::Array(items) = value else {
393
        bail!("expected a JSON array for a partition");
394
    };
395
    let mut parts = Vec::with_capacity(items.len());
396
    for item in items {
397
        let JsonValue::Array(elems) = item else {
398
            bail!("expected a JSON array for a partition part");
399
        };
400
        let mut part = Vec::with_capacity(elems.len());
401
        for elem in elems {
402
            part.push(literal_from_simplified_json_with_ground(elem, inner)?);
403
        }
404
        parts.push(part);
405
    }
406
    Ok(Literal::AbstractLiteral(AbstractLiteral::Partition(parts)))
407
}
408

            
409
fn permutation_from_simplified_json(
410
    value: &JsonValue,
411
    inner: &GroundDomain,
412
) -> anyhow::Result<Literal> {
413
    let JsonValue::Array(items) = value else {
414
        bail!("expected a JSON array for a permutation");
415
    };
416
    let mut cycles = Vec::with_capacity(items.len());
417
    for item in items {
418
        let JsonValue::Array(elems) = item else {
419
            bail!("expected a JSON array for a permutation cycle");
420
        };
421
        let mut cycle = Vec::with_capacity(elems.len());
422
        for elem in elems {
423
            cycle.push(literal_from_simplified_json_with_ground(elem, inner)?);
424
        }
425
        cycles.push(cycle);
426
    }
427
    Ok(Literal::AbstractLiteral(AbstractLiteral::Permutation(
428
        cycles,
429
    )))
430
}
431

            
432
fn relation_from_simplified_json(
433
    value: &JsonValue,
434
    inner_doms: &[Moo<GroundDomain>],
435
) -> anyhow::Result<Literal> {
436
    let JsonValue::Array(items) = value else {
437
        bail!("expected a JSON array for a relation");
438
    };
439
    let mut tuples = Vec::with_capacity(items.len());
440
    for item in items {
441
        let JsonValue::Array(fields) = item else {
442
            bail!("expected a JSON array for a relation tuple");
443
        };
444
        if fields.len() != inner_doms.len() {
445
            bail!(
446
                "relation tuple arity mismatch: expected {}, got {}",
447
                inner_doms.len(),
448
                fields.len()
449
            );
450
        }
451
        let mut tuple = Vec::with_capacity(fields.len());
452
        for (field, dom) in fields.iter().zip(inner_doms) {
453
            tuple.push(literal_from_simplified_json_with_ground(field, dom)?);
454
        }
455
        tuples.push(tuple);
456
    }
457
    Ok(Literal::AbstractLiteral(AbstractLiteral::Relation(tuples)))
458
}
459

            
460
2
fn matrix_from_simplified_json(
461
2
    value: &JsonValue,
462
2
    inner: &GroundDomain,
463
2
    index_domains: &[Moo<GroundDomain>],
464
2
) -> anyhow::Result<Literal> {
465
2
    let (first_index, rest) = index_domains
466
2
        .split_first()
467
        .ok_or_else(|| anyhow!("matrix domain has no index domains"))?;
468

            
469
2
    let elem_domain: GroundDomain = if rest.is_empty() {
470
2
        inner.clone()
471
    } else {
472
        GroundDomain::Matrix(Moo::new(inner.clone()), rest.to_vec(), None)
473
    };
474

            
475
2
    match value {
476
        JsonValue::Object(object) => {
477
2
            let mut pairs = Vec::with_capacity(object.len());
478
2
            for (key, item) in object {
479
4
                let index = index_key_to_literal(key, first_index)?;
480
4
                let elem = literal_from_simplified_json_with_ground(item, &elem_domain)?;
481
4
                pairs.push((index, elem));
482
            }
483
2
            pairs.sort_by(|a, b| a.0.essence_cmp(&b.0));
484
2
            let keys: Vec<i32> = object
485
2
                .keys()
486
4
                .map(|k| k.parse::<i32>())
487
                .collect::<Result<Vec<_>, _>>()
488
                .unwrap_or_default();
489
2
            let elems: Vec<Literal> = pairs.into_iter().map(|(_, v)| v).collect();
490
2
            let index_domain = if let Ok(expected) = first_index.values() {
491
2
                let expected: Vec<_> = expected.collect();
492
2
                if expected.len() == elems.len() {
493
2
                    first_index.as_ref().clone()
494
                } else {
495
                    infer_int_index_domain(&keys)
496
                }
497
            } else {
498
                infer_int_index_domain(&keys)
499
            };
500
2
            Ok(Literal::AbstractLiteral(AbstractLiteral::Matrix(
501
2
                elems,
502
2
                index_domain.into(),
503
2
            )))
504
        }
505
        JsonValue::Array(items) => {
506
            let mut elems = Vec::with_capacity(items.len());
507
            for item in items {
508
                elems.push(literal_from_simplified_json_with_ground(
509
                    item,
510
                    &elem_domain,
511
                )?);
512
            }
513
            let n = i32::try_from(elems.len()).context("matrix too large")?;
514
            let index_domain = GroundDomain::Int(vec![Range::Bounded(1, n)], None);
515
            Ok(Literal::AbstractLiteral(AbstractLiteral::Matrix(
516
                elems,
517
                index_domain.into(),
518
            )))
519
        }
520
        _ => bail!("expected a JSON object or array for a matrix"),
521
    }
522
2
}
523

            
524
fn infer_int_index_domain(keys: &[i32]) -> GroundDomain {
525
    if keys.is_empty() {
526
        return GroundDomain::Int(vec![], None);
527
    }
528
    let mut ints = keys.to_vec();
529
    ints.sort_unstable();
530
    let min = ints[0];
531
    let max = *ints.last().expect("non-empty");
532
    if max - min + 1 == ints.len() as i32 {
533
        GroundDomain::Int(vec![Range::Bounded(min, max)], None)
534
    } else {
535
        GroundDomain::Int(ints.into_iter().map(Range::Single).collect(), None)
536
    }
537
}
538

            
539
4
fn index_key_to_literal(key: &str, index_domain: &GroundDomain) -> anyhow::Result<Literal> {
540
4
    match index_domain {
541
        GroundDomain::Bool => match key {
542
            "false" => Ok(Literal::Bool(false)),
543
            "true" => Ok(Literal::Bool(true)),
544
            _ => bail!("expected boolean matrix index key"),
545
        },
546
4
        _ => Ok(Literal::Int(key.parse::<i32>().with_context(|| {
547
            format!("expected integer matrix index key, got `{key}`")
548
        })?)),
549
    }
550
4
}
551

            
552
fn sequence_from_object(
553
    object: &Map<String, JsonValue>,
554
    inner: &GroundDomain,
555
) -> anyhow::Result<Literal> {
556
    let mut pairs = Vec::with_capacity(object.len());
557
    for (key, item) in object {
558
        let index: i32 = key
559
            .parse()
560
            .with_context(|| format!("sequence index `{key}` is not an integer"))?;
561
        pairs.push((
562
            index,
563
            literal_from_simplified_json_with_ground(item, inner)?,
564
        ));
565
    }
566
    pairs.sort_by_key(|(i, _)| *i);
567
    Ok(Literal::AbstractLiteral(AbstractLiteral::Sequence(
568
        pairs.into_iter().map(|(_, v)| v).collect(),
569
    )))
570
}
571

            
572
fn function_from_simplified_json(
573
    value: &JsonValue,
574
    from: &GroundDomain,
575
    to: &GroundDomain,
576
) -> anyhow::Result<Literal> {
577
    match value {
578
        JsonValue::Object(object) => {
579
            let mut pairs = Vec::with_capacity(object.len());
580
            for (key, item) in object {
581
                let domain_key = index_key_to_literal(key, from)?;
582
                let mapped = literal_from_simplified_json_with_ground(item, to)?;
583
                pairs.push((domain_key, mapped));
584
            }
585
            Ok(Literal::AbstractLiteral(AbstractLiteral::Function(pairs)))
586
        }
587
        JsonValue::Array(items) => {
588
            let mut pairs = Vec::with_capacity(items.len());
589
            for item in items {
590
                let JsonValue::Array(pair) = item else {
591
                    bail!("function array entries must be [from, to] pairs");
592
                };
593
                if pair.len() != 2 {
594
                    bail!("function array entries must be [from, to] pairs");
595
                }
596
                pairs.push((
597
                    literal_from_simplified_json_with_ground(&pair[0], from)?,
598
                    literal_from_simplified_json_with_ground(&pair[1], to)?,
599
                ));
600
            }
601
            Ok(Literal::AbstractLiteral(AbstractLiteral::Function(pairs)))
602
        }
603
        _ => bail!("expected a JSON object or array for a function"),
604
    }
605
}
606

            
607
3
fn literal_from_simplified_json_unguided(value: &JsonValue) -> anyhow::Result<Literal> {
608
3
    match value {
609
1
        JsonValue::Bool(b) => Ok(Literal::Bool(*b)),
610
1
        JsonValue::Number(_) | JsonValue::String(_) => Ok(Literal::Int(json_to_i32(value)?)),
611
        JsonValue::Array(items) => {
612
            let mut elems = Vec::with_capacity(items.len());
613
            for item in items {
614
                elems.push(literal_from_simplified_json_unguided(item)?);
615
            }
616
            Ok(Literal::AbstractLiteral(AbstractLiteral::Set(elems)))
617
        }
618
        JsonValue::Object(object) => {
619
1
            let all_int_keys = object.keys().all(|key| key.parse::<i32>().is_ok());
620
1
            if all_int_keys {
621
                let mut pairs = Vec::with_capacity(object.len());
622
                for (key, item) in object {
623
                    let index: i32 = key.parse().with_context(|| {
624
                        format!("unguided object key `{key}` is not an integer")
625
                    })?;
626
                    pairs.push((index, literal_from_simplified_json_unguided(item)?));
627
                }
628
                pairs.sort_by_key(|(i, _)| *i);
629
                let keys: Vec<i32> = pairs.iter().map(|(i, _)| *i).collect();
630
                let elems: Vec<_> = pairs.into_iter().map(|(_, v)| v).collect();
631
                return Ok(Literal::AbstractLiteral(AbstractLiteral::Matrix(
632
                    elems,
633
                    infer_int_index_domain(&keys).into(),
634
                )));
635
1
            }
636

            
637
            // Non-integer keys: treat as a record (Conjure simplified JSON).
638
1
            let mut entries = Vec::with_capacity(object.len());
639
1
            for (key, item) in object {
640
2
                entries.push(Field {
641
2
                    name: Name::user(key.as_str()),
642
2
                    value: literal_from_simplified_json_unguided(item)?,
643
                });
644
            }
645
1
            entries.sort_by(|a, b| a.name.cmp(&b.name));
646
1
            Ok(Literal::AbstractLiteral(AbstractLiteral::Record(entries)))
647
        }
648
        JsonValue::Null => bail!("null is not a valid Essence literal"),
649
    }
650
3
}
651

            
652
5855
fn json_to_i32(value: &JsonValue) -> anyhow::Result<i32> {
653
5855
    match value {
654
5855
        JsonValue::Number(n) => n
655
5855
            .as_i64()
656
5855
            .and_then(|i| i32::try_from(i).ok())
657
            .ok_or_else(|| anyhow!("expected a 32-bit integer")),
658
        JsonValue::String(s) => s
659
            .parse::<i32>()
660
            .with_context(|| format!("expected an integer string, got `{s}`")),
661
        _ => bail!("expected an integer"),
662
    }
663
5855
}
664

            
665
fn solution_key_cmp(
666
    lhs: &BTreeMap<Name, Literal>,
667
    rhs: &BTreeMap<Name, Literal>,
668
) -> std::cmp::Ordering {
669
    lhs.iter()
670
        .zip(rhs)
671
        .find_map(|((lhs_name, lhs_value), (rhs_name, rhs_value))| {
672
            let ordering = lhs_name.cmp(rhs_name);
673
            (ordering != std::cmp::Ordering::Equal)
674
                .then_some(ordering)
675
                .or_else(|| {
676
                    let ordering = lhs_value.essence_cmp(rhs_value);
677
                    (ordering != std::cmp::Ordering::Equal).then_some(ordering)
678
                })
679
        })
680
        .unwrap_or_else(|| lhs.len().cmp(&rhs.len()))
681
}
682

            
683
/// Collect find/given domains from a model for JSON parsing guidance.
684
1274
pub fn domains_from_model(model: &Model) -> BTreeMap<Name, DomainPtr> {
685
1274
    let mut domains = BTreeMap::new();
686
1274
    for (name, decl) in model.symbols().clone().into_iter() {
687
15368
        if let Some(domain) = decl.domain() {
688
15368
            domains.insert(name, domain);
689
        }
690
    }
691
1274
    domains
692
1274
}
693

            
694
/// Build a parameter model of value-lettings from simplified JSON assignments.
695
1168
pub fn param_model_from_assignments(
696
1168
    params: BTreeMap<Name, Literal>,
697
1168
    given_domains: &BTreeMap<Name, DomainPtr>,
698
1168
    context: std::sync::Arc<std::sync::RwLock<conjure_cp::context::Context<'static>>>,
699
1168
) -> Model {
700
1168
    let mut model = Model::new(context);
701
1168
    for (name, literal) in params {
702
2336
        let decl = match given_domains.get(&name) {
703
            Some(domain) => {
704
2336
                DeclarationPtr::new_value_letting_with_domain(name, literal.into(), domain.clone())
705
            }
706
            None => DeclarationPtr::new_value_letting(name, literal.into()),
707
        };
708
2336
        model.symbols_mut().update_insert(decl);
709
    }
710
1168
    model
711
1168
}
712

            
713
/// Render pretty JSON with stable key order for golden comparisons.
714
pub fn canonical_simplified_json_string(value: &JsonValue) -> anyhow::Result<String> {
715
    let sorted = sort_json_object(value, true);
716
    Ok(format!("{}\n", serde_json::to_string_pretty(&sorted)?))
717
}
718

            
719
/// Append pretty JSON to a string buffer.
720
pub fn write_simplified_json(value: &JsonValue, out: &mut String) -> anyhow::Result<()> {
721
    write!(out, "{}", serde_json::to_string_pretty(value)?)?;
722
    out.push('\n');
723
    Ok(())
724
}
725

            
726
#[cfg(test)]
727
mod tests {
728
    use super::*;
729
    use conjure_cp::ast::{Domain, SetAttr};
730

            
731
5
    fn int_dom(lo: i32, hi: i32) -> DomainPtr {
732
5
        Domain::int(vec![Range::Bounded(lo, hi)])
733
5
    }
734

            
735
    #[test]
736
1
    fn round_trips_scalars_and_set() {
737
1
        let mut domains = BTreeMap::new();
738
1
        domains.insert(Name::user("x"), int_dom(1, 3));
739
1
        domains.insert(
740
1
            Name::user("s"),
741
1
            Domain::set(SetAttr::<i32>::default(), int_dom(1, 3)),
742
1
        );
743

            
744
1
        let json = serde_json::json!([{"s": [1, 3], "x": 2}]);
745
1
        let solutions = solutions_from_simplified_json(&json, &domains).unwrap();
746
1
        assert_eq!(solutions.len(), 1);
747
1
        assert_eq!(solutions[0].get(&Name::user("x")), Some(&Literal::Int(2)));
748
        let Literal::AbstractLiteral(AbstractLiteral::Set(elems)) =
749
1
            solutions[0].get(&Name::user("s")).unwrap()
750
        else {
751
            panic!("expected set");
752
        };
753
1
        assert_eq!(elems, &vec![Literal::Int(1), Literal::Int(3)]);
754

            
755
1
        let rendered = solutions_to_simplified_json(&solutions).unwrap();
756
1
        let again = solutions_from_simplified_json(&rendered, &domains).unwrap();
757
1
        assert_eq!(again, solutions);
758
1
    }
759

            
760
    #[test]
761
1
    fn round_trips_int_indexed_matrix() {
762
1
        let matrix_dom = Domain::matrix(int_dom(1, 3), vec![int_dom(1, 2)]);
763
1
        let mut domains = BTreeMap::new();
764
1
        domains.insert(Name::user("m"), matrix_dom);
765

            
766
1
        let json = serde_json::json!([{"m": {"1": 1, "2": 2}}]);
767
1
        let solutions = solutions_from_simplified_json(&json, &domains).unwrap();
768
1
        let rendered = solutions_to_simplified_json(&solutions).unwrap();
769
1
        let again = solutions_from_simplified_json(&rendered, &domains).unwrap();
770
1
        assert_eq!(again, solutions);
771
1
    }
772

            
773
    #[test]
774
1
    fn guided_variant_object_parses_as_variant() {
775
1
        let mut domains = BTreeMap::new();
776
1
        domains.insert(
777
1
            Name::user("x"),
778
1
            Domain::variant(vec![
779
1
                Field {
780
1
                    name: Name::user("flag"),
781
1
                    value: Domain::bool(),
782
1
                },
783
1
                Field {
784
1
                    name: Name::user("value"),
785
1
                    value: int_dom(1, 3),
786
1
                },
787
1
            ]),
788
1
        );
789

            
790
1
        let json = serde_json::json!([{"x": {"value": 2}}]);
791
1
        let solutions = solutions_from_simplified_json(&json, &domains).unwrap();
792
1
        let expected = Literal::AbstractLiteral(AbstractLiteral::Variant(Moo::new(Field {
793
1
            name: Name::user("value"),
794
1
            value: Literal::Int(2),
795
1
        })));
796
1
        assert_eq!(solutions[0].get(&Name::user("x")), Some(&expected));
797

            
798
1
        let rendered = solutions_to_simplified_json(&solutions).unwrap();
799
1
        assert_eq!(
800
1
            solutions_from_simplified_json(&rendered, &domains).unwrap(),
801
1
            solutions
802
        );
803
1
    }
804

            
805
    #[test]
806
1
    fn unguided_object_with_named_fields_parses_as_record() {
807
1
        let json = serde_json::json!({"a": true, "b": 3});
808
1
        let literal = literal_from_simplified_json(&json, None).unwrap();
809
1
        let Literal::AbstractLiteral(AbstractLiteral::Record(fields)) = literal else {
810
            panic!("expected record, got {literal:?}");
811
        };
812
1
        assert_eq!(fields.len(), 2);
813
1
        assert_eq!(fields[0].name, Name::user("a"));
814
1
        assert_eq!(fields[0].value, Literal::Bool(true));
815
1
        assert_eq!(fields[1].name, Name::user("b"));
816
1
        assert_eq!(fields[1].value, Literal::Int(3));
817
1
    }
818
}