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, _hidden_labels: Vec<String>) -> 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(_lts: &impl LTS, _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 core::num;
37
    use std::cell::Cell;
38
    use std::collections::HashMap;
39
    use std::env;
40
    use std::ffi::CStr;
41
    use std::ffi::CString;
42
    use std::pin::Pin;
43
    use std::sync::Mutex;
44
    use std::sync::Once;
45

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
235
        unsafe {
236
            BCG_IO_WRITE_BCG_END();
237
        }
238

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

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

            
251
        match env::var("CADP") {
252
            Ok(cadp_path) => {
253
                if Path::new(&cadp_path).exists() {
254
                    info!("Found CADP installation at: {}", cadp_path);
255
                } else {
256
                    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());
257
                }
258
            }
259
            Err(_) => {
260
                return Err(
261
                    "The CADP environment variable is not set; the CADP toolset must be installed to read BCG files."
262
                        .into(),
263
                );
264
            }
265
        }
266

            
267
        Ok(())
268
    }
269

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

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

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

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

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

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

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

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

            
314
            Some(edge)
315
        }
316
    }
317

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

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

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

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

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

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

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

            
356
            Some(edge)
357
        }
358
    }
359

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

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

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

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

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

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

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

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

            
438
        use merc_utilities::random_test;
439

            
440
        use crate::LTS;
441
        use crate::random_lts_monolithic;
442
        use crate::read_bcg;
443
        use crate::write_bcg;
444

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

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

            
455
        #[test]
456
        #[cfg_attr(miri, ignore)] // Too slow with miri
457
        fn test_random_bcg_io() {
458
            random_test(100, |rng| {
459
                let lts = random_lts_monolithic::<String>(rng, 100, 3, 20);
460

            
461
                // Use a temporary file in the target directory.
462
                let tmp = temp_dir();
463

            
464
                let file = tmp.join("test_random_bcg_io.bcg");
465
                write_bcg(&lts, &file).unwrap();
466

            
467
                let result_lts = read_bcg(&file, Vec::new()).unwrap();
468

            
469
                crate::check_equivalent(&lts, &result_lts);
470
            });
471
        }
472
    }
473
}
474

            
475
pub use inner::read_bcg;
476
pub use inner::write_bcg;