flatzinc_serde/
lib.rs

1//! Serialization of the FlatZinc data format
2//!
3//! FlatZinc is the language in which data and solver specific constraint models
4//! are produced by the [MiniZinc](https://www.minizinc.org) compiler. This
5//! crate implements the FlatZinc serialization format as described in the
6//! [Interfacing Solvers to
7//! FlatZinc](https://www.minizinc.org/doc-latest/en/fzn-spec.html#specification-of-flatzinc-json)
8//! section of the MiniZinc reference manual. Although the
9//! [serde](https://serde.rs) specification in this crate could be used with a
10//! range of data formats, MiniZinc currently only outputs this formulation
11//! using the JSON data format. We suggest using
12//! [`serde_json`](https://crates.io/crates/serde_json) with the specification
13//! in this crate to parse the FlatZinc JSON files produced by the MiniZinc
14//! compiler.
15//!
16//! # Getting Started
17//!
18//! Install `flatzinc-serde` and `serde_json` for your package:
19//!
20//! ```bash
21//! cargo add flatzinc-serde serde_json
22//! ```
23//!
24//! Once these dependencies have been installed to your crate, you could
25//! deserialize a FlatZinc JSON file as follows:
26//!
27//! ```
28//! # use flatzinc_serde::FlatZinc;
29//! # use std::{fs::File, io::BufReader, path::Path};
30//! # let path = Path::new("./corpus/documentation_example.fzn.json");
31//! // let path = Path::new("/lorem/ipsum/model.fzn.json");
32//! let rdr = BufReader::new(File::open(path).unwrap());
33//! let fzn: FlatZinc = serde_json::from_reader(rdr).unwrap();
34//! // ... process FlatZinc ...
35//! ```
36//!
37//! If, however, you want to serialize a FlatZinc format you could follow the
38//! following fragment:
39//!
40//! ```
41//! # use flatzinc_serde::FlatZinc;
42//! let fzn = FlatZinc::<String>::default();
43//! // ... create  solver constraint model ...
44//! let json_str = serde_json::to_string(&fzn).unwrap();
45//! ```
46//! Note that `serde_json::to_writer`, using a buffered file writer, would be
47//! preferred when writing larger FlatZinc files.
48//!
49//! # Register your solver with MiniZinc
50//!
51//! If your goal is to deserialize FlatZinc to implement a MiniZinc solver, then
52//! the next step is to register your solver executable with MiniZinc. This can
53//! be done by creating a [MiniZinc Solver
54//! Configuration](https://www.minizinc.org/doc-2.8.2/en/fzn-spec.html#solver-configuration-files)
55//! (`.msc`) file, and adding it to a folder on the `MZN_SOLVER_PATH` or a
56//! standardized path, like `~/.minizinc/solvers/`. A basic solver configuration
57//! for a solver that accepts JSON input would look as follows:
58//!
59//! ```json
60//! {
61//!   "name" : "My Solver",
62//!   "version": "0.0.1",
63//!   "id": "my.organisation.mysolver",
64//!   "inputType": "JSON",
65//!   "executable": "../../../bin/fzn-my-solver",
66//!   "mznlib": "../mysolver"
67//!   "stdFlags": [],
68//!   "extraFlags": []
69//! }
70//! ```
71//!
72//! Once you have placed your configuration file on the correct path, then you
73//! solver will be listed by `minizinc --solvers`. Calling `minizinc --solver
74//! mysolver model.mzn data.dzn`, assuming a valid MiniZinc instance, will
75//! (after compilation) invoke the registered executable with a path of a
76//! FlatZinc JSON file, and potentially any registered standard and extra flags
77//! (e.g., `../../../bin/fzn-my-solver model.fzn.json`).
78
79#![warn(missing_docs)]
80#![warn(unused_crate_dependencies, unused_extern_crates)]
81#![warn(variant_size_differences)]
82
83use std::{collections::BTreeMap, fmt::Display};
84
85pub use rangelist::RangeList;
86use serde::{Deserialize, Serialize};
87
88use crate::encapsulate::{
89	deserialize_encapsulated_set, deserialize_encapsulated_string, deserialize_set,
90	serialize_encapsulate_set, serialize_encapsulate_string, serialize_set,
91};
92mod encapsulate;
93
94/// Helper function to help skip in serialization
95fn is_false(b: &bool) -> bool {
96	!(*b)
97}
98
99/// Additional information provided in a standardized format for declarations,
100/// constraints, or solve objectives
101///
102/// In MiniZinc annotations can both be added explicitly in the model, or can be
103/// added during compilation process.
104///
105/// Note that annotations are generally defined either in the MiniZinc standard
106/// library or in a solver's redefinition library. Solvers are encouraged to
107/// rewrite annotations in their redefinitions library when required.
108#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
109#[serde(untagged)]
110pub enum Annotation<Identifier = String> {
111	/// Atom annotation (i.e., a single `Identifier`)
112	Atom(Identifier),
113	/// Call annotation
114	Call(AnnotationCall<Identifier>),
115}
116
117impl<Identifier: Display> Display for Annotation<Identifier> {
118	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119		write!(f, "::")?;
120		match self {
121			Annotation::Atom(a) => write!(f, "{a}"),
122			Annotation::Call(c) => write!(f, "{c}"),
123		}
124	}
125}
126
127/// The argument type associated with [`AnnotationCall`]
128#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
129#[serde(untagged)]
130pub enum AnnotationArgument<Identifier = String> {
131	/// Sequence of [`Literal`]s
132	Array(Vec<AnnotationLiteral<Identifier>>),
133	/// Singular argument
134	Literal(AnnotationLiteral<Identifier>),
135}
136
137impl<Idenfier: Display> Display for AnnotationArgument<Idenfier> {
138	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139		match self {
140			AnnotationArgument::Array(arr) => {
141				write!(f, "[")?;
142				let mut first = true;
143				for v in arr {
144					if !first {
145						write!(f, ", ")?
146					}
147					write!(f, "{v}")?;
148					first = false;
149				}
150				write!(f, "]")
151			}
152			AnnotationArgument::Literal(lit) => write!(f, "{lit}"),
153		}
154	}
155}
156
157/// An object depicting an annotation in the form of a call
158#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
159#[serde(rename = "annotation_call")]
160pub struct AnnotationCall<Identifier = String> {
161	/// Identifier of the constraint predicate
162	pub id: Identifier,
163	/// Arguments of the constraint
164	pub args: Vec<AnnotationArgument<Identifier>>,
165}
166
167impl<Identifier: Display> Display for AnnotationCall<Identifier> {
168	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169		write!(f, "{}(", self.id)?;
170		let mut first = true;
171		for arg in &self.args {
172			if !first {
173				write!(f, ", ")?
174			}
175			write!(f, "{arg}")?;
176			first = false;
177		}
178		write!(f, ")")
179	}
180}
181
182///Literal values as arguments to [`AnnotationCall`]
183#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
184#[serde(untagged)]
185pub enum AnnotationLiteral<Identifier = String> {
186	/// Basic FlatZinc literal
187	BaseLiteral(Literal<Identifier>),
188	/// An annotation object
189	Annotation(Annotation<Identifier>),
190}
191
192impl<Idenfier: Display> Display for AnnotationLiteral<Idenfier> {
193	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194		match self {
195			AnnotationLiteral::BaseLiteral(lit) => write!(f, "{lit}"),
196			AnnotationLiteral::Annotation(ann) => write!(f, "{ann}"),
197		}
198	}
199}
200
201/// The argument type associated with [`Constraint`]
202#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
203#[serde(untagged)]
204pub enum Argument<Identifier = String> {
205	/// Sequence of [`Literal`]s
206	Array(Vec<Literal<Identifier>>),
207	/// Literal
208	Literal(Literal<Identifier>),
209}
210
211impl<Identifier: Display> Display for Argument<Identifier> {
212	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213		match self {
214			Argument::Array(arr) => {
215				write!(f, "[")?;
216				let mut first = true;
217				for v in arr {
218					if !first {
219						write!(f, ", ")?
220					}
221					write!(f, "{v}")?;
222					first = false;
223				}
224				write!(f, "]")
225			}
226			Argument::Literal(lit) => write!(f, "{lit}"),
227		}
228	}
229}
230
231/// A definition of a named array literal in FlatZinc
232///
233/// FlatZinc Arrays are a simple (one-dimensional) sequence of [`Literal`]s.
234/// These values are stored as the [`Array::contents`] member. Additional
235/// information, in the form of [`Annotation`]s, from the MiniZinc model is
236/// stored in [`Array::ann`] when present. When [`Array::defined`] is set to
237/// `true`, then
238#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
239#[serde(rename = "array")]
240pub struct Array<Identifier = String> {
241	/// The values stored within the array literal
242	#[serde(rename = "a")]
243	pub contents: Vec<Literal<Identifier>>,
244	#[serde(default, skip_serializing_if = "Vec::is_empty")]
245	/// List of annotations
246	pub ann: Vec<Annotation<Identifier>>,
247	#[serde(default, skip_serializing_if = "is_false")]
248	/// This field is set to `true` when there is a constraint that has been marked as
249	/// defining this array.
250	pub defined: bool,
251	#[serde(default, skip_serializing_if = "is_false")]
252	/// This field is set to `true` when the array has been introduced by the
253	/// MiniZinc compiler, rather than being explicitly defined at the top-level
254	/// of the MiniZinc model.
255	pub introduced: bool,
256}
257
258impl<Identifier: Ord> Array<Identifier> {
259	/// Heuristic to determine the type of the array
260	fn determine_type(&self, fzn: &FlatZinc<Identifier>) -> (&str, bool) {
261		let ty = match self.contents.first().unwrap() {
262			Literal::Int(_) => "int",
263			Literal::Float(_) => "float",
264			Literal::Identifier(ident) => match fzn.variables[ident].ty {
265				Type::Bool => "bool",
266				Type::Int => "int",
267				Type::Float => "float",
268				Type::IntSet => "set of int",
269			},
270			Literal::Bool(_) => "bool",
271			Literal::IntSet(_) => "set of int",
272			Literal::FloatSet(_) => "set of float",
273			Literal::String(_) => "string",
274		};
275		let is_var = self.contents.iter().any(|lit| match lit {
276			Literal::Identifier(ident) => fzn.variables[ident].value.is_none(),
277			_ => false,
278		});
279		(ty, is_var)
280	}
281}
282
283/// An object depicting a constraint
284#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
285#[serde(rename = "constraint")]
286pub struct Constraint<Identifier = String> {
287	/// Identifier of the constraint predicate
288	pub id: Identifier,
289	/// Arguments of the constraint
290	pub args: Vec<Argument<Identifier>>,
291	/// Identifier of the variable that the constraint defines
292	#[serde(default, skip_serializing_if = "Option::is_none")]
293	pub defines: Option<Identifier>,
294	/// List of annotations
295	#[serde(default = "Vec::new", skip_serializing_if = "Vec::is_empty")]
296	pub ann: Vec<Annotation<Identifier>>,
297}
298
299impl<Identifier: Display> Display for Constraint<Identifier> {
300	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
301		write!(f, "{}(", self.id)?;
302		let mut first = true;
303		for arg in &self.args {
304			if !first {
305				write!(f, ", ")?
306			}
307			write!(f, "{arg}")?;
308			first = false;
309		}
310		write!(f, ")")?;
311		if let Some(defines) = &self.defines {
312			write!(f, " ::defines_var({defines})")?
313		}
314		for a in &self.ann {
315			write!(f, " {a}")?
316		}
317		Ok(())
318	}
319}
320
321/// The possible values that a (decision) [`Variable`] can take
322///
323/// In the case of a integer or floating point variable, a solution for the FlatZinc instance must
324#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
325#[serde(untagged)]
326pub enum Domain {
327	/// Integer (or set of integer) decision variable domain
328	#[serde(deserialize_with = "deserialize_set", serialize_with = "serialize_set")]
329	Int(RangeList<i64>),
330	/// Floating point decision variable domain
331	#[serde(deserialize_with = "deserialize_set", serialize_with = "serialize_set")]
332	Float(RangeList<f64>),
333}
334
335impl Display for Domain {
336	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
337		match self {
338			Domain::Int(is) => write!(f, "{is}"),
339			Domain::Float(fs) => write!(f, "{fs}"),
340		}
341	}
342}
343
344/// The structure depicting a FlatZinc instance
345///
346/// FlatZinc is (generally) a format produced by the MiniZinc compiler as a
347/// result of instantiating the parameter variables of a MiniZinc model and
348/// generating a solver-specific equisatisfiable model.
349#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
350pub struct FlatZinc<Identifier: Ord = String> {
351	/// A mapping from decision variable `Identifier` to their definitions
352	#[serde(default)]
353	pub variables: BTreeMap<Identifier, Variable<Identifier>>,
354	/// A mapping from array `Identifier` to their definitions
355	#[serde(default)]
356	pub arrays: BTreeMap<Identifier, Array<Identifier>>,
357	/// A list of (solver-specific) constraints, that must be satisfied in a solution.
358	#[serde(default)]
359	pub constraints: Vec<Constraint<Identifier>>,
360	/// A list of all identifiers for which the solver must produce output for each solution
361	#[serde(default)]
362	pub output: Vec<Identifier>,
363	/// A specification of the goal of solving the FlatZinc instance.
364	pub solve: SolveObjective<Identifier>,
365	/// The version of the FlatZinc serialization specification used
366	#[serde(default, skip_serializing_if = "String::is_empty")]
367	pub version: String,
368}
369
370impl<Identifier: Ord> Default for FlatZinc<Identifier> {
371	fn default() -> Self {
372		Self {
373			variables: Default::default(),
374			arrays: BTreeMap::new(),
375			constraints: Vec::new(),
376			output: Default::default(),
377			solve: Default::default(),
378			version: "1.0".into(),
379		}
380	}
381}
382
383impl<Identifier: Ord + Display> Display for FlatZinc<Identifier> {
384	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
385		let output_map: BTreeMap<&Identifier, ()> =
386			self.output.iter().map(|ident| (ident, ())).collect();
387
388		for (ident, var) in &self.variables {
389			write!(f, "var ")?;
390			if let Some(dom) = &var.domain {
391				write!(f, "{dom}")?
392			} else {
393				write!(f, "{}", var.ty)?
394			}
395			write!(f, ": {ident}")?;
396			if output_map.contains_key(&ident) {
397				write!(f, " ::output_var")?;
398			}
399			if var.defined {
400				write!(f, " ::is_defined_var")?;
401			}
402			if var.introduced {
403				write!(f, " ::var_is_introduced")?;
404			}
405			for ann in &var.ann {
406				write!(f, " {ann}")?
407			}
408			if let Some(val) = &var.value {
409				write!(f, " = {val}")?
410			}
411			writeln!(f, ";")?
412		}
413		for (ident, arr) in &self.arrays {
414			let (ty, is_var) = arr.determine_type(self);
415			write!(
416				f,
417				"array[1..{}] of {}{ty}: {ident}",
418				arr.contents.len(),
419				if is_var { "var " } else { "" }
420			)?;
421			if output_map.contains_key(&ident) {
422				write!(f, " ::output_array([1..{}])", arr.contents.len())?;
423			}
424			if arr.defined {
425				write!(f, " ::is_defined_var")?;
426			}
427			if arr.introduced {
428				write!(f, " ::var_is_introduced")?;
429			}
430			for ann in &arr.ann {
431				write!(f, " {ann}")?
432			}
433			write!(f, " = [")?;
434			let mut first = true;
435			for v in &arr.contents {
436				if !first {
437					write!(f, ", ")?;
438				}
439				write!(f, "{v}")?;
440				first = false;
441			}
442			writeln!(f, "];")?
443		}
444		for c in &self.constraints {
445			writeln!(f, "constraint {c};")?;
446		}
447		writeln!(f, "{};", self.solve)
448	}
449}
450
451// /// A name used to refer to an [`Array`], function, or [`Variable`]
452// pub type Identifier = String;
453
454/// Literal values
455#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
456#[serde(untagged)]
457pub enum Literal<Identifier = String> {
458	/// Integer value
459	Int(i64),
460	/// Floating point value
461	Float(f64),
462	/// Identifier, i.e., reference to an [`Array`] or [`Variable`]
463	Identifier(Identifier),
464	/// Boolean value
465	Bool(bool),
466	#[serde(
467		serialize_with = "serialize_encapsulate_set",
468		deserialize_with = "deserialize_encapsulated_set"
469	)]
470	/// Set of integers, represented as a list of integer ranges
471	IntSet(RangeList<i64>),
472	#[serde(
473		serialize_with = "serialize_encapsulate_set",
474		deserialize_with = "deserialize_encapsulated_set"
475	)]
476	/// Set of floating point values, represented as a list of floating point
477	/// ranges
478	FloatSet(RangeList<f64>),
479	#[serde(
480		serialize_with = "serialize_encapsulate_string",
481		deserialize_with = "deserialize_encapsulated_string"
482	)]
483	/// String value
484	String(String),
485}
486
487impl<Identifier: Display> Display for Literal<Identifier> {
488	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
489		match self {
490			Literal::Int(i) => write!(f, "{i}"),
491			Literal::Float(x) => write!(f, "{x:?}"),
492			Literal::Identifier(ident) => write!(f, "{ident}"),
493			Literal::Bool(b) => write!(f, "{b}"),
494			Literal::IntSet(is) => write!(f, "{is}"),
495			Literal::FloatSet(fs) => write!(f, "{fs}"),
496			Literal::String(s) => write!(f, "{s:?}"),
497		}
498	}
499}
500
501/// Goal of solving a FlatZinc instance
502#[derive(Default, Clone, PartialEq, Debug, Deserialize, Serialize)]
503#[serde(rename = "method")]
504pub enum Method {
505	/// Find any solution
506	#[serde(rename = "satisfy")]
507	#[default]
508	Satisfy,
509	/// Find the solution with the lowest objective value
510	#[serde(rename = "minimize")]
511	Minimize,
512	/// Find the solution with the highest objective value
513	#[serde(rename = "maximize")]
514	Maximize,
515}
516
517impl Display for Method {
518	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
519		match self {
520			Method::Satisfy => write!(f, "satisfy"),
521			Method::Minimize => write!(f, "minimize"),
522			Method::Maximize => write!(f, "maximize"),
523		}
524	}
525}
526
527/// A specification of objective of a FlatZinc instance
528#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
529pub struct SolveObjective<Identifier = String> {
530	/// The type of goal
531	pub method: Method,
532	/// The variable to optimize, or `None` if [`SolveObjective::method`] is [`Method::Satisfy`]
533	#[serde(skip_serializing_if = "Option::is_none")]
534	pub objective: Option<Literal<Identifier>>,
535	/// A list of annotations from the solve statement in the MiniZinc model
536	///
537	/// Note that this includes the search annotations if they are present in the
538	/// model.
539	#[serde(default, skip_serializing_if = "Vec::is_empty")]
540	pub ann: Vec<Annotation<Identifier>>,
541}
542
543impl<Identifier> Default for SolveObjective<Identifier> {
544	fn default() -> Self {
545		Self {
546			method: Default::default(),
547			objective: None,
548			ann: Vec::new(),
549		}
550	}
551}
552
553impl<Identifier: Display> Display for SolveObjective<Identifier> {
554	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
555		write!(f, "solve ")?;
556		for a in &self.ann {
557			write!(f, "{a} ")?;
558		}
559		write!(f, "{}", self.method)?;
560		if let Some(obj) = &self.objective {
561			write!(f, " {obj}")?
562		}
563		Ok(())
564	}
565}
566
567/// Used to signal the type of (decision) [`Variable`]
568#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
569#[serde(rename = "type")]
570pub enum Type {
571	/// Boolean decision variable
572	#[serde(rename = "bool")]
573	Bool,
574	/// Integer decision variable
575	#[serde(rename = "int")]
576	Int,
577	/// Floating point decision variable
578	#[serde(rename = "float")]
579	Float,
580	/// Integer set decision variable
581	#[serde(rename = "set of int")]
582	IntSet,
583}
584
585impl Display for Type {
586	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
587		match self {
588			Type::Bool => write!(f, "bool"),
589			Type::Int => write!(f, "int"),
590			Type::Float => write!(f, "float"),
591			Type::IntSet => write!(f, "set of int"),
592		}
593	}
594}
595
596/// The definition of a decision variable
597#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
598#[serde(rename = "variable")]
599pub struct Variable<Identifier = String> {
600	/// The type of the decision variable
601	#[serde(rename = "type")]
602	pub ty: Type,
603	/// The set of potential values from which the decision variable must take its
604	/// value in a solution
605	///
606	/// If domain has the value `None`, then all values of the decision variable's
607	/// `Type` are allowed in a solution.
608	#[serde(skip_serializing_if = "Option::is_none")]
609	pub domain: Option<Domain>,
610	/// The “right hand side” of the variable, i.e., its value or alias to another
611	/// variable
612	#[serde(rename = "rhs", skip_serializing_if = "Option::is_none")]
613	pub value: Option<Literal<Identifier>>,
614	/// A list of annotations
615	#[serde(default, skip_serializing_if = "Vec::is_empty")]
616	pub ann: Vec<Annotation<Identifier>>,
617	/// This field is set to `true` when there is a constraint that has been marked as
618	/// defining this variable.
619	#[serde(default, skip_serializing_if = "is_false")]
620	pub defined: bool,
621	/// This field is set to `true` when the variable has been introduced by the
622	/// MiniZinc compiler, rather than being explicitly defined at the top-level
623	/// of the MiniZinc model.
624	#[serde(default, skip_serializing_if = "is_false")]
625	pub introduced: bool,
626}
627
628#[cfg(test)]
629mod tests {
630	use std::{
631		collections::BTreeMap,
632		fs::File,
633		io::{BufReader, Read},
634		path::Path,
635	};
636
637	use expect_test::ExpectFile;
638	use ustr::Ustr;
639
640	use crate::{
641		Annotation, AnnotationArgument, AnnotationCall, AnnotationLiteral, Array, Domain, FlatZinc,
642		Literal, Method, RangeList, SolveObjective, Type, Variable,
643	};
644
645	test_file!(documentation_example);
646	test_file!(encapsulated_string);
647	test_file!(float_sets);
648	test_file!(set_literals);
649	test_file!(unit_test_example);
650
651	fn test_successful_serialization(file: &Path, exp: ExpectFile) {
652		let rdr = BufReader::new(File::open(file).unwrap());
653		let fzn: FlatZinc = serde_json::from_reader(rdr).unwrap();
654		exp.assert_debug_eq(&fzn);
655		let fzn2: FlatZinc = serde_json::from_str(&serde_json::to_string(&fzn).unwrap()).unwrap();
656		assert_eq!(fzn, fzn2)
657	}
658
659	macro_rules! test_file {
660		($file: ident) => {
661			#[test]
662			fn $file() {
663				test_successful_serialization(
664					std::path::Path::new(&format!("./corpus/{}.fzn.json", stringify!($file))),
665					expect_test::expect_file![&format!(
666						"../corpus/{}.debug.txt",
667						stringify!($file)
668					)],
669				)
670			}
671		};
672	}
673	pub(crate) use test_file;
674
675	#[test]
676	fn test_ident_no_copy() {
677		let mut rdr = BufReader::new(
678			File::open(Path::new("./corpus/documentation_example.fzn.json")).unwrap(),
679		);
680		let mut content = String::new();
681		let _ = rdr.read_to_string(&mut content).unwrap();
682
683		let fzn: FlatZinc<&str> = serde_json::from_str(&content).unwrap();
684		expect_test::expect_file!["../corpus/documentation_example.debug.txt"].assert_debug_eq(&fzn)
685	}
686
687	#[test]
688	fn test_ident_interned() {
689		let rdr = BufReader::new(
690			File::open(Path::new("./corpus/documentation_example.fzn.json")).unwrap(),
691		);
692		let fzn: FlatZinc<Ustr> = serde_json::from_reader(rdr).unwrap();
693		expect_test::expect_file!["../corpus/documentation_example.debug_ustr.txt"]
694			.assert_debug_eq(&fzn)
695	}
696
697	#[test]
698	fn test_print_flatzinc() {
699		let mut rdr = BufReader::new(
700			File::open(Path::new("./corpus/documentation_example.fzn.json")).unwrap(),
701		);
702		let mut content = String::new();
703		let _ = rdr.read_to_string(&mut content).unwrap();
704
705		let fzn: FlatZinc<&str> = serde_json::from_str(&content).unwrap();
706		expect_test::expect_file!["../corpus/documentation_example.fzn"]
707			.assert_eq(&fzn.to_string());
708
709		let ann: Annotation<&str> = Annotation::Call(AnnotationCall {
710			id: "bool_search",
711			args: vec![
712				AnnotationArgument::Literal(AnnotationLiteral::BaseLiteral(Literal::Identifier(
713					"input_order",
714				))),
715				AnnotationArgument::Literal(AnnotationLiteral::BaseLiteral(Literal::Identifier(
716					"indomain_min",
717				))),
718			],
719		});
720		assert_eq!(ann.to_string(), "::bool_search(input_order, indomain_min)");
721
722		let dom = Domain::Float(RangeList::from(1.0..=4.0));
723		assert_eq!(dom.to_string(), "1.0..4.0");
724
725		let ty = Type::Bool;
726		assert_eq!(ty.to_string(), "bool");
727		let ty = Type::Int;
728		assert_eq!(ty.to_string(), "int");
729		let ty = Type::Float;
730		assert_eq!(ty.to_string(), "float");
731		let ty = Type::IntSet;
732		assert_eq!(ty.to_string(), "set of int");
733
734		let lit = Literal::<&str>::Int(1);
735		assert_eq!(lit.to_string(), "1");
736		let lit = Literal::<&str>::Float(1.0);
737		assert_eq!(lit.to_string(), "1.0");
738		let lit = Literal::<&str>::Identifier("x");
739		assert_eq!(lit.to_string(), "x");
740		let lit = Literal::<&str>::Bool(true);
741		assert_eq!(lit.to_string(), "true");
742		let lit = Literal::<&str>::IntSet(RangeList::from(2..=3));
743		assert_eq!(lit.to_string(), "2..3");
744		let lit = Literal::<&str>::FloatSet(RangeList::from(2.0..=3.0));
745		assert_eq!(lit.to_string(), "2.0..3.0");
746		let lit = Literal::<&str>::String(String::from("hello"));
747		assert_eq!(lit.to_string(), "\"hello\"");
748
749		let fzn = FlatZinc {
750			variables: BTreeMap::from([(
751				"x",
752				Variable {
753					ty: Type::IntSet,
754					domain: None,
755					ann: vec![Annotation::Atom("special")],
756					defined: false,
757					introduced: true,
758					value: Some(Literal::IntSet(RangeList::from(1..=4))),
759				},
760			)]),
761			arrays: BTreeMap::from([(
762				"y",
763				Array {
764					ann: vec![Annotation::Atom("special")],
765					contents: vec![Literal::Int(1), Literal::Int(2), Literal::Int(3)],
766					introduced: true,
767					defined: true,
768				},
769			)]),
770			output: vec!["y"],
771			..Default::default()
772		};
773		assert_eq!(
774			fzn.to_string(),
775			"var set of int: x ::var_is_introduced ::special = 1..4;\narray[1..3] of int: y ::output_array([1..3]) ::is_defined_var ::var_is_introduced ::special = [1, 2, 3];\nsolve satisfy;\n"
776		);
777
778		let sat = SolveObjective {
779			method: Method::Minimize,
780			ann: vec![ann],
781			objective: Some(Literal::Identifier("x")),
782		};
783		assert_eq!(
784			sat.to_string(),
785			"solve ::bool_search(input_order, indomain_min) minimize x"
786		);
787	}
788}