1
#![forbid(unsafe_code)]
2

            
3
use std::ffi::OsStr;
4
use std::fs::File;
5
use std::path::Path;
6

            
7
use merc_utilities::MercError;
8
use merc_utilities::Timing;
9

            
10
use crate::LTS;
11
use crate::LabelledTransitionSystem;
12
use crate::LtsAction;
13
use crate::LtsMultiAction;
14
use crate::SimpleAction;
15
use crate::read_aut;
16
use crate::read_bcg;
17
use crate::read_lts;
18
use crate::read_mcrl2_aut;
19

            
20
/// Convenience macro to call `GenericLts::apply` with the same function for all variants.
21
/// Useful with generic functions that can be monomorphized for all label types.
22
///
23
/// Examples:
24
/// - apply_lts!(lts, my_fn)
25
/// - apply_lts!(lts, |lts| do_something(lts))
26
#[macro_export]
27
macro_rules! apply_lts {
28
    ($lts:expr, $arguments:expr, $f:path) => {
29
        $lts.apply($arguments, $f, $f, $f)
30
    };
31
    ($lts:expr, $arguments:expr, $f:expr) => {
32
        $lts.apply($arguments, $f, $f, $f)
33
    };
34
}
35

            
36
/// Convenience macro to apply a function to a pair of `GenericLts` only when both
37
/// are the same variant; returns an error otherwise.
38
///
39
/// Examples:
40
/// - apply_lts_pair!(lhs, rhs, args, my_fn)
41
/// - apply_lts_pair!(lhs, rhs, args, |a, b, args| do_something(a, b, args))
42
#[macro_export]
43
macro_rules! apply_lts_pair {
44
    ($lhs:expr, $rhs:expr, $arguments:expr, $f:path) => {
45
        $lhs.apply_pair($rhs, $arguments, $f, $f, $f)
46
    };
47
    ($lhs:expr, $rhs:expr, $arguments:expr, $f:expr) => {
48
        $lhs.apply_pair($rhs, $arguments, $f, $f, $f)
49
    };
50
}
51

            
52
/// Convenience macro to apply a function to a slice of `GenericLts` when all
53
/// are the same variant; returns an error otherwise.
54
///
55
/// Examples:
56
/// - apply_lts_slice!(lts_slice, args, my_fn)
57
/// - apply_lts_slice!(lts_slice, args, |lts_vec, args| do_something(lts_vec, args))
58
#[macro_export]
59
macro_rules! apply_lts_slice {
60
    ($lts_slice:expr, $arguments:expr, $f:path) => {
61
        $crate::apply_slice($lts_slice, $arguments, $f, $f, $f)
62
    };
63
    ($lts_slice:expr, $arguments:expr, $f:expr) => {
64
        $crate::apply_slice($lts_slice, $arguments, $f, $f, $f)
65
    };
66
}
67

            
68
/// Explicitly specify the LTS file format.
69
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
71
pub enum LtsFormat {
72
    /// The AUTomaton or ALDEBARAN format
73
    Aut,
74
    /// The [Self::Aut] format with `tau` as hidden label instead of `i`, and multi-actions as labels, used in the mCRL2 toolset.
75
    AutMcrl2,
76
    /// The mCRL2 binary LTS format
77
    Lts,
78
    /// The CADP BCG format (requires 'cadp' feature)
79
    Bcg,
80
}
81

            
82
/// Guesses the LTS file format from the file extension.
83
pub fn guess_lts_format_from_extension(path: &Path, format: Option<LtsFormat>) -> Option<LtsFormat> {
84
    if let Some(format) = format {
85
        return Some(format);
86
    }
87

            
88
    if path.extension() == Some(OsStr::new("aut")) {
89
        Some(LtsFormat::Aut)
90
    } else if path.extension() == Some(OsStr::new("lts")) {
91
        Some(LtsFormat::Lts)
92
    } else if path.extension() == Some(OsStr::new("bcg")) {
93
        Some(LtsFormat::Bcg)
94
    } else {
95
        None
96
    }
97
}
98

            
99
/// A general struct to deal with the polymorphic LTS types. The `apply_lts`
100
/// macro can be then used to conveniently apply functions which are generic on
101
/// the LTS trait to all variants.
102
pub enum GenericLts {
103
    /// The LTS in the Aldebaran format.
104
    Aut(LabelledTransitionSystem<String>),
105
    /// The mCRL2 LTS in the Aldebaran format. Multi-action labels are stored as
106
    /// [`SimpleAction`]s so the label name and string arguments are accessible.
107
    AutMcrl2(LabelledTransitionSystem<LtsMultiAction<SimpleAction>>),
108
    /// The LTS in the mCRL2 binary `.lts` format. Multi-action labels are
109
    /// stored as proper terms so they can be written back via [`crate::write_lts`].
110
    Lts(LabelledTransitionSystem<LtsMultiAction<LtsAction>>),
111
    /// The LTS in the CADP BCG format.
112
    Bcg(LabelledTransitionSystem<String>),
113
}
114

            
115
impl GenericLts {
116
    /// Applies the given function to both LTSs when they are the same variant.
117
    /// Returns an error if the variants do not match.
118
    pub fn apply_pair<T, FAut, FAutMcrl2, FLts, R>(
119
        self,
120
        other: GenericLts,
121
        arguments: T,
122
        apply_aut: FAut,
123
        apply_aut_mcrl2: FAutMcrl2,
124
        apply_lts: FLts,
125
    ) -> R
126
    where
127
        FAut: FnOnce(LabelledTransitionSystem<String>, LabelledTransitionSystem<String>, T) -> R,
128
        FAutMcrl2: FnOnce(
129
            LabelledTransitionSystem<LtsMultiAction<SimpleAction>>,
130
            LabelledTransitionSystem<LtsMultiAction<SimpleAction>>,
131
            T,
132
        ) -> R,
133
        FLts: FnOnce(
134
            LabelledTransitionSystem<LtsMultiAction<LtsAction>>,
135
            LabelledTransitionSystem<LtsMultiAction<LtsAction>>,
136
            T,
137
        ) -> R,
138
    {
139
        match (self, other) {
140
            (GenericLts::Aut(a), GenericLts::Aut(b)) => apply_aut(a, b, arguments),
141
            (GenericLts::AutMcrl2(a), GenericLts::AutMcrl2(b)) => apply_aut_mcrl2(a, b, arguments),
142
            (GenericLts::Lts(a), GenericLts::Lts(b)) => apply_lts(a, b, arguments),
143
            (GenericLts::Bcg(a), GenericLts::Bcg(b)) => apply_aut(a, b, arguments),
144
            _ => unreachable!("Mismatched GenericLts variants in apply_pair; this indicates a programming error"),
145
        }
146
    }
147

            
148
    pub fn apply<T, F, G, H, R>(self, arguments: T, apply_aut: F, apply_aut_mcrl2: G, apply_lts: H) -> R
149
    where
150
        F: FnOnce(LabelledTransitionSystem<String>, T) -> R,
151
        G: FnOnce(LabelledTransitionSystem<LtsMultiAction<SimpleAction>>, T) -> R,
152
        H: FnOnce(LabelledTransitionSystem<LtsMultiAction<LtsAction>>, T) -> R,
153
    {
154
        match self {
155
            GenericLts::Aut(lts) => apply_aut(lts, arguments),
156
            GenericLts::AutMcrl2(lts) => apply_aut_mcrl2(lts, arguments),
157
            GenericLts::Lts(lts) => apply_lts(lts, arguments),
158
            GenericLts::Bcg(lts) => apply_aut(lts, arguments),
159
        }
160
    }
161

            
162
    // These are convenience functions to get LTS metrics.
163

            
164
    /// Returns the number of states in the LTS.
165
    pub fn num_of_states(&self) -> usize {
166
        match self {
167
            GenericLts::Aut(lts) => lts.num_of_states(),
168
            GenericLts::AutMcrl2(lts) => lts.num_of_states(),
169
            GenericLts::Lts(lts) => lts.num_of_states(),
170
            GenericLts::Bcg(lts) => lts.num_of_states(),
171
        }
172
    }
173

            
174
    /// Returns the number of transitions in the LTS.
175
    pub fn num_of_transitions(&self) -> usize {
176
        match self {
177
            GenericLts::Aut(lts) => lts.num_of_transitions(),
178
            GenericLts::AutMcrl2(lts) => lts.num_of_transitions(),
179
            GenericLts::Lts(lts) => lts.num_of_transitions(),
180
            GenericLts::Bcg(lts) => lts.num_of_transitions(),
181
        }
182
    }
183
}
184

            
185
/// Internal helper function for the `apply_lts_slice!` macro.
186
/// Applies a function to a slice of `GenericLts` when all are the same variant.
187
pub fn apply_slice<T, FAut, FAutMcrl2, FLts, R>(
188
    lts_slice: &[GenericLts],
189
    arguments: T,
190
    apply_aut: FAut,
191
    apply_aut_mcrl2: FAutMcrl2,
192
    apply_lts: FLts,
193
) -> Result<R, MercError>
194
where
195
    FAut: FnOnce(Vec<&LabelledTransitionSystem<String>>, T) -> R,
196
    FAutMcrl2: FnOnce(Vec<&LabelledTransitionSystem<LtsMultiAction<SimpleAction>>>, T) -> R,
197
    FLts: FnOnce(Vec<&LabelledTransitionSystem<LtsMultiAction<LtsAction>>>, T) -> R,
198
{
199
    if lts_slice.is_empty() {
200
        return Err("Cannot apply function to empty slice of GenericLts".into());
201
    }
202

            
203
    match &lts_slice[0] {
204
        GenericLts::Aut(_) | GenericLts::Bcg(_) => {
205
            let aut_lts: Result<Vec<&LabelledTransitionSystem<String>>, MercError> = lts_slice
206
                .iter()
207
                .enumerate()
208
                .map(|(idx, lts)| match lts {
209
                    GenericLts::Aut(aut) | GenericLts::Bcg(aut) => Ok(aut),
210
                    _ => Err(format!("Expected Aut/Bcg variant at index {}, got a different variant", idx).into()),
211
                })
212
                .collect();
213
            Ok(apply_aut(aut_lts?, arguments))
214
        }
215
        GenericLts::AutMcrl2(_) => {
216
            let aut_lts: Result<Vec<&LabelledTransitionSystem<LtsMultiAction<SimpleAction>>>, MercError> = lts_slice
217
                .iter()
218
                .enumerate()
219
                .map(|(idx, lts)| match lts {
220
                    GenericLts::AutMcrl2(aut) => Ok(aut),
221
                    _ => Err(format!("Expected AutMcrl2 variant at index {}, got a different variant", idx).into()),
222
                })
223
                .collect();
224
            Ok(apply_aut_mcrl2(aut_lts?, arguments))
225
        }
226
        GenericLts::Lts(_) => {
227
            let lts_lts: Result<Vec<&LabelledTransitionSystem<LtsMultiAction<LtsAction>>>, MercError> = lts_slice
228
                .iter()
229
                .enumerate()
230
                .map(|(idx, lts)| match lts {
231
                    GenericLts::Lts(lts_obj) => Ok(lts_obj),
232
                    _ => Err(format!("Expected Lts variant at index {}, got a different variant", idx).into()),
233
                })
234
                .collect();
235
            Ok(apply_lts(lts_lts?, arguments))
236
        }
237
    }
238
}
239

            
240
/// Reads an explicit labelled transition system from the given path and format.
241
pub fn read_explicit_lts(path: &Path, format: LtsFormat, timing: &mut Timing) -> Result<GenericLts, MercError> {
242
    timing.measure("read_explicit_lts", || {
243
        let result = match format {
244
            LtsFormat::Aut => {
245
                let file = File::open(path)?;
246
                GenericLts::Aut(read_aut(&file)?)
247
            }
248
            LtsFormat::AutMcrl2 => {
249
                let file = File::open(path)?;
250
                let lts = read_mcrl2_aut(&file)?;
251
                GenericLts::AutMcrl2(lts.relabel(|label| LtsMultiAction::<SimpleAction>::from_string(&label))?)
252
            }
253
            LtsFormat::Lts => {
254
                let file = File::open(path)?;
255
                GenericLts::Lts(read_lts(&file, false)?)
256
            }
257
            LtsFormat::Bcg => GenericLts::Bcg(read_bcg(path)?),
258
        };
259

            
260
        Ok(result)
261
    })
262
}