1
//! Suppress various warnings from the generated bindings.
2

            
3
#![allow(non_upper_case_globals)]
4
#![allow(non_camel_case_types)]
5
#![allow(non_snake_case)]
6
#![allow(unused)]
7

            
8
use std::path::Path;
9

            
10
use merc_utilities::MercError;
11

            
12
use crate::LTS;
13
use crate::LabelledTransitionSystem;
14

            
15
#[cfg(not(feature = "cadp"))]
16
mod inner {
17
    use super::*;
18

            
19
    /// This is a stub implementation used when BCG support is not compiled in.
20
    pub fn read_bcg(_path: &Path) -> Result<LabelledTransitionSystem<String>, MercError> {
21
        Err("BCG format support not compiled in, see the 'cadp' feature.".into())
22
    }
23

            
24
    /// This is a stub implementation used when BCG support is not compiled in.
25
    pub fn write_bcg<L: LTS>(_lts: &L, _path: &Path) -> Result<(), MercError> {
26
        Err("BCG format support not compiled in, see the 'cadp' feature.".into())
27
    }
28
}
29

            
30
#[cfg(feature = "cadp")]
31
mod inner {
32
    use log::info;
33

            
34
    use super::*;
35

            
36
    use std::cell::Cell;
37
    use std::collections::HashMap;
38
    use std::env;
39
    use std::ffi::CStr;
40
    use std::ffi::CString;
41
    use std::pin::Pin;
42
    use std::sync::Mutex;
43
    use std::sync::Once;
44

            
45
    use merc_io::LargeFormatter;
46
    use merc_io::TimeProgress;
47

            
48
    use crate::LabelIndex;
49
    use crate::LtsBuilderMem;
50
    use crate::StateIndex;
51
    use crate::TransitionLabel;
52

            
53
    /// Initialize the BCG library exactly once.
54
    static BCG_INITIALIZED: Once = Once::new();
55

            
56
    /// Mutex to ensure thread-safe access to BCG library functions.
57
    static BCG_LOCK: Mutex<()> = Mutex::new(());
58

            
59
    // Include the generated bindings for the BCG C library.
60
    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
61

            
62
    /// Reads a labelled transition system in the BCG format, from the
63
    /// [CADP](https://cadp.inria.fr/man/bcg.html) toolset.
64
    ///
65
    /// # Details
66
    ///
67
    /// This requires the `CADP` toolset to be installed for the target
68
    /// platform, and the `CADP` environment variable to be set.
69
    ///
70
    /// Note that the C library can only read files from disk; reading from
71
    /// in-memory buffers is not supported.
72
    pub fn read_bcg(path: &Path) -> Result<LabelledTransitionSystem<String>, MercError> {
73
        initialize_bcg()?;
74
        info!("Reading LTS in BCG format...");
75

            
76
        // Take the lock to ensure thread-safe access to BCG functions.
77
        let _guard = BCG_LOCK.lock().expect("Failed to acquire BCG lock");
78

            
79
        let mut bcg_object: BCG_TYPE_OBJECT_TRANSITION = std::ptr::null_mut();
80
        let filename = CString::new(path.to_string_lossy().as_ref())?;
81

            
82
        #[repr(u32)]
83
        enum AccessMode {
84
            Edges = 0,
85
            Succ = 1,
86
            Pred = 2,
87
            SuccPred = 3,
88
            SuccPredAlt = 4,
89
        }
90

            
91
        // SAFETY: The function will not modify the string.
92
        unsafe {
93
            BCG_OT_READ_BCG_BEGIN(
94
                filename.as_ptr() as *mut i8,
95
                &mut bcg_object,
96
                AccessMode::Succ as u32, // With successors we can efficiently create the LTS.
97
            );
98
        }
99

            
100
        // Read the labels.
101
        let num_of_labels = unsafe { BCG_OT_NB_LABELS(bcg_object) };
102

            
103
        let mut labels = Vec::with_capacity(num_of_labels as usize);
104
        labels.push(String::tau_label());
105

            
106
        let mut label_index = HashMap::new();
107
        for i in 0..num_of_labels {
108
            let label = unsafe { BCG_OT_LABEL_STRING(bcg_object, i) };
109

            
110
            let is_visible = unsafe { BCG_OT_LABEL_VISIBLE(bcg_object, i) };
111

            
112
            let label = unsafe { CStr::from_ptr(label).to_string_lossy().into_owned() };
113
            if is_visible {
114
                label_index.insert(i as usize, labels.len()); // Map to new index.
115
                labels.push(label.clone());
116
            } else {
117
                label_index.insert(i as usize, 0); // Map to the internal action.
118
            }
119
        }
120

            
121
        // Read the initial state.
122
        let initial_state = unsafe { BCG_OT_INITIAL_STATE(bcg_object) };
123

            
124
        let num_of_transitions = unsafe { BCG_OT_NB_EDGES(bcg_object) };
125
        let progress = TimeProgress::new(
126
            move |transitions: usize| {
127
                info!(
128
                    "Read {} transitions ({}%)...",
129
                    LargeFormatter(transitions),
130
                    transitions * 100 / (num_of_transitions as usize).max(1)
131
                );
132
            },
133
            1,
134
        );
135

            
136
        // Read the successors for every state.
137
        let num_of_states = unsafe { BCG_OT_NB_STATES(bcg_object) };
138
        let num_of_transitions = Cell::new(0usize);
139
        let lts = LabelledTransitionSystem::with_successors(
140
            StateIndex::new(initial_state as usize),
141
            num_of_states as usize,
142
            labels,
143
            |state| {
144
                unsafe { SuccessorIter::new(bcg_object, state.value() as u64) }.map(|edge| {
145
                    num_of_transitions.set(num_of_transitions.get() + 1);
146
                    progress.print(num_of_transitions.get());
147
                    (LabelIndex::new(label_index[&edge.label]), StateIndex::new(edge.target))
148
                })
149
            },
150
        );
151

            
152
        // Clean up
153
        unsafe {
154
            BCG_OT_READ_BCG_END(&mut bcg_object);
155
        }
156

            
157
        info!("Finished reading LTS.");
158
        Ok(lts)
159
    }
160

            
161
    /// Writes the given labelled transition system to a file in the BCG format, see [read_bcg].
162
    ///
163
    /// # Details
164
    ///
165
    /// We require the label to be convertible into a `String`.
166
    pub fn write_bcg<L: LTS>(lts: &L, path: &Path) -> Result<(), MercError>
167
    where
168
        String: From<L::Label>,
169
    {
170
        initialize_bcg()?;
171
        info!("Writing LTS in BCG format...");
172

            
173
        // Take the lock to ensure thread-safe access to BCG functions.
174
        let _guard = BCG_LOCK.lock().expect("Failed to acquire BCG lock");
175

            
176
        let filename = CString::new(path.to_string_lossy().as_ref())?;
177
        let comment = CString::new("created by merc_lts")?;
178

            
179
        #[repr(u32)]
180
        enum WriteMode {
181
            // In the forthcoming successive invocations of
182
            // function BCG_IO_WRITE_BCG_EDGE(), the sequence of actual values
183
            // given to the state1 argument of BCG_IO_WRITE_BCG_EDGE() will
184
            // increase monotonically
185
            MonotonicStates = 2,
186
        }
187

            
188
        // SAFETY: The C call will not modify the string.
189
        unsafe {
190
            BCG_IO_WRITE_BCG_BEGIN(
191
                filename.as_ptr() as *mut i8,
192
                lts.initial_state_index().value() as u64,
193
                WriteMode::MonotonicStates as u32,
194
                comment.as_ptr() as *mut i8,
195
                false,
196
            );
197
        }
198

            
199
        let num_of_transitions = lts.num_of_transitions();
200
        let progress = TimeProgress::new(
201
            move |transitions: usize| {
202
                info!(
203
                    "Wrote {} transitions ({}%)...",
204
                    LargeFormatter(transitions),
205
                    transitions * 100 / num_of_transitions
206
                );
207
            },
208
            1,
209
        );
210

            
211
        let labels = lts
212
            .labels()
213
            .iter()
214
            .map(|label| CString::new::<String>(label.clone().into()))
215
            .collect::<Result<Vec<_>, _>>()?;
216

            
217
        let mut number_of_transitions = 0;
218
        for state in lts.iter_states() {
219
            for transition in lts.outgoing_transitions(state) {
220
                // SAFETY: The state label is not mutated by the C function.
221
                unsafe {
222
                    BCG_IO_WRITE_BCG_EDGE(
223
                        state.value() as u64,
224
                        labels[transition.label.value() as usize].as_ptr() as *mut i8,
225
                        transition.to.value() as u64,
226
                    );
227
                }
228

            
229
                progress.print(number_of_transitions);
230
                number_of_transitions += 1;
231
            }
232
        }
233

            
234
        unsafe {
235
            BCG_IO_WRITE_BCG_END();
236
        }
237

            
238
        info!("Finished writing LTS.");
239
        Ok(())
240
    }
241

            
242
    /// Initialize the BCG library.
243
    fn initialize_bcg() -> Result<(), MercError> {
244
        BCG_INITIALIZED.call_once(|| {
245
            // SAFETY: Initialize the BCG library only once.
246
            unsafe { BCG_INIT() };
247
            info!("BCG library initialized.");
248
        });
249

            
250
        match env::var("CADP") {
251
            Ok(cadp_path) => {
252
                if Path::new(&cadp_path).exists() {
253
                    info!("Found CADP installation at: {}", cadp_path);
254
                } else {
255
                    return Err(format!("The CADP environment variable is set to '{}', but this path does not exist; the CADP toolset must be installed to read BCG files.", cadp_path).into());
256
                }
257
            }
258
            Err(_) => {
259
                return Err(
260
                    "The CADP environment variable is not set; the CADP toolset must be installed to read BCG files."
261
                        .into(),
262
                );
263
            }
264
        }
265

            
266
        Ok(())
267
    }
268

            
269
    /// Represents an edge in the BCG file.
270
    struct BcgEdge {
271
        source: usize,
272
        label: usize,
273
        target: usize,
274
    }
275

            
276
    // Iterator over all edges in the BCG fil, `BCG_OT_ITERATE_PLN`.
277
    struct EdgeIter {
278
        inner: BcgOtIterator,
279
    }
280

            
281
    impl EdgeIter {
282
        /// Create a new BCG OT iterator.
283
        unsafe fn new(bcg_object: BCG_TYPE_OBJECT_TRANSITION) -> Self {
284
            let mut inner = unsafe { BcgOtIterator::new() };
285

            
286
            unsafe {
287
                BCG_OT_START(
288
                    inner.inner.as_mut().get_unchecked_mut(),
289
                    bcg_object,
290
                    bcg_enum_edge_sort_BCG_UNDEFINED_SORT,
291
                )
292
            };
293
            Self { inner }
294
        }
295
    }
296

            
297
    impl Iterator for EdgeIter {
298
        type Item = BcgEdge;
299

            
300
        fn next(&mut self) -> Option<Self::Item> {
301
            // If we've reached the end, signal iteration end.
302
            if self.inner.end() {
303
                return None;
304
            }
305

            
306
            let edge = self.inner.edge();
307

            
308
            // Advance the underlying C iterator for the next call.
309
            unsafe {
310
                self.inner.next();
311
            }
312

            
313
            Some(edge)
314
        }
315
    }
316

            
317
    /// Iterator for the successors of a specific state, `BCG_OT_ITERATE_P_LN`.
318
    struct SuccessorIter {
319
        inner: BcgOtIterator,
320
        state: u64,
321
    }
322

            
323
    impl SuccessorIter {
324
        /// Constructs a new BCG OT iterator for a specific `state`.
325
        pub unsafe fn new(bcg_object: BCG_TYPE_OBJECT_TRANSITION, state: u64) -> Self {
326
            let mut inner = unsafe { BcgOtIterator::new() };
327

            
328
            unsafe {
329
                BCG_OT_START_P(
330
                    inner.inner.as_mut().get_unchecked_mut(),
331
                    bcg_object,
332
                    bcg_enum_edge_sort_BCG_P_SORT,
333
                    state,
334
                )
335
            };
336
            Self { inner, state }
337
        }
338
    }
339

            
340
    impl Iterator for SuccessorIter {
341
        type Item = BcgEdge;
342

            
343
        fn next(&mut self) -> Option<Self::Item> {
344
            // If we've reached the end, or the state has changed, signal iteration end.
345
            if self.inner.end() || self.inner.p() != self.state {
346
                return None;
347
            }
348

            
349
            let edge = self.inner.edge();
350

            
351
            unsafe {
352
                self.inner.next();
353
            }
354

            
355
            Some(edge)
356
        }
357
    }
358

            
359
    /// Wrapper around the BCG OT iterator.
360
    struct BcgOtIterator {
361
        inner: Pin<Box<BCG_TYPE_OT_ITERATOR>>,
362
    }
363

            
364
    impl BcgOtIterator {
365
        /// Constructs a new BCG OT iterator
366
        pub unsafe fn new() -> Self {
367
            Self {
368
                inner: Box::pin(BCG_TYPE_OT_ITERATOR {
369
                    bcg_object_transition: std::ptr::null_mut(),
370
                    bcg_bcg_file_iterator: bcg_body_bcg_file_iterator { bcg_nb_edges: 0 },
371
                    bcg_et1_iterator: BCG_TYPE_ET1_ITERATOR {
372
                        bcg_edge_table: std::ptr::null_mut(),
373
                        bcg_current_state: 0,
374
                        bcg_last_edge_of_state: 0,
375
                        bcg_edge_number: 0,
376
                        bcg_edge_buffer: std::ptr::null_mut(),
377
                        bcg_given_state: false,
378
                    },
379
                    bcg_et2_iterator: BCG_TYPE_ET2_ITERATOR {
380
                        bcg_edge_table: std::ptr::null_mut(),
381
                        bcg_edge_number: 0,
382
                        bcg_index_number: 0,
383
                        bcg_edge_buffer: std::ptr::null_mut(),
384
                    },
385
                    bcg_edge_buffer: BCG_TYPE_EDGE {
386
                        bcg_end: false,
387
                        bcg_i: 0,
388
                        bcg_p: 0,
389
                        bcg_l: 0,
390
                        bcg_n: 0,
391
                    },
392
                }),
393
            }
394
        }
395

            
396
        /// Returns true if the iterator has reached the end, `BCG_OT_END`.
397
        fn end(&self) -> bool {
398
            self.inner.bcg_edge_buffer.bcg_end
399
        }
400

            
401
        /// Returns the current source state, `BCG_OT_P`.
402
        fn p(&self) -> u64 {
403
            self.inner.bcg_edge_buffer.bcg_p as u64
404
        }
405

            
406
        /// Returns the current edge.
407
        fn edge(&self) -> BcgEdge {
408
            BcgEdge {
409
                source: self.inner.bcg_edge_buffer.bcg_p as usize,
410
                label: self.inner.bcg_edge_buffer.bcg_l as usize,
411
                target: self.inner.bcg_edge_buffer.bcg_n as usize,
412
            }
413
        }
414

            
415
        /// Advance the underlying C iterator for the next call, `BCG_OT_NEXT`.
416
        unsafe fn next(&mut self) {
417
            unsafe {
418
                BCG_OT_NEXT(self.inner.as_mut().get_unchecked_mut());
419
            }
420
        }
421
    }
422

            
423
    impl Drop for BcgOtIterator {
424
        fn drop(&mut self) {
425
            unsafe {
426
                // The same as BCG_OT_END_ITERATE.
427
                BCG_OT_STOP(self.inner.as_mut().get_unchecked_mut());
428
            }
429
        }
430
    }
431

            
432
    #[cfg(test)]
433
    mod tests {
434
        use std::env::temp_dir;
435
        use std::path::Path;
436

            
437
        use merc_utilities::random_test;
438

            
439
        use crate::LTS;
440
        use crate::random_lts;
441
        use crate::read_bcg;
442
        use crate::write_bcg;
443

            
444
        #[test]
445
        fn test_read_bcg() {
446
            // Test reading a BCG file.
447
            let lts = read_bcg(Path::new("../../examples/lts/abp.bcg")).unwrap();
448

            
449
            assert_eq!(lts.num_of_states(), 74);
450
            assert_eq!(lts.num_of_transitions(), 92);
451
            assert_eq!(lts.num_of_labels(), 19);
452
        }
453

            
454
        /// Round-trips a random LTS through the BCG format: a generated LTS is
455
        /// written to a temporary `.bcg` file and read back. The BCG C library
456
        /// only operates on files, so an in-memory buffer cannot be used.
457
        #[test]
458
        #[cfg_attr(miri, ignore)] // Too slow with miri, and exercises the C BCG library.
459
        fn test_random_bcg_io() {
460
            random_test(100, |rng| {
461
                let lts = random_lts::<String, _>(rng, 100, 3);
462

            
463
                // Use a temporary file since the BCG library cannot read buffers.
464
                let file = temp_dir().join("test_random_bcg_io.bcg");
465
                write_bcg(&lts, &file).unwrap();
466

            
467
                let result_lts = read_bcg(&file).unwrap();
468

            
469
                // The number of transitions must be identical after the round-trip.
470
                assert_eq!(
471
                    result_lts.num_of_transitions(),
472
                    lts.num_of_transitions(),
473
                    "BCG round-trip must preserve the number of transitions"
474
                );
475
                crate::check_equivalent(&lts, &result_lts);
476
            });
477
        }
478
    }
479
}
480

            
481
pub use inner::read_bcg;
482
pub use inner::write_bcg;