1
//! Authors: Maurice Laveaux and Sjef van Loo
2

            
3
use std::collections::HashMap;
4
use std::fmt;
5
use std::io::BufRead;
6
use std::io::BufReader;
7
use std::io::Read;
8

            
9
use log::debug;
10
use oxidd::BooleanFunction;
11
use oxidd::Manager;
12
use oxidd::ManagerRef;
13
use oxidd::bdd::BDDFunction;
14
use oxidd::bdd::BDDManagerRef;
15

            
16
use merc_lts::LabelIndex;
17
use merc_lts::LabelledTransitionSystem;
18
use merc_lts::LTS;
19
use merc_lts::read_aut;
20
use merc_lts::StateIndex;
21
use merc_lts::Transition;
22
use merc_lts::TransitionLabel;
23
use merc_symbolic::FormatConfigSet;
24
use merc_syntax::DataExpr;
25
use merc_syntax::MultiAction;
26
use merc_utilities::MercError;
27

            
28
/// Reads a .aut file as feature transition system by using the associated
29
/// feature diagram.
30
///
31
/// # Details
32
///
33
/// The action labels of a feature transition system are annotated with a
34
/// special `BDD` struct that is defined as:
35
///
36
/// > `struct BDD = node(var, true, false) | tt | ff`
37
///
38
/// The `features` map contains the mapping from variable names to their
39
/// corresponding BDD variable, which *must* be defined in the BDD manager.
40
2
pub fn read_fts<R: Read>(
41
2
    manager_ref: &BDDManagerRef,
42
2
    reader: R,
43
2
    features: HashMap<String, BDDFunction>,
44
2
) -> Result<FeatureTransitionSystem<String>, MercError> {
45
    // Read the underlying LTS, where the labels are in plain text
46
2
    let aut = read_aut(reader, Vec::new())?;
47

            
48
    // Parse the labels as data expressions
49
2
    let mut feature_labels = Vec::new();
50
55
    for label in aut.labels() {
51
55
        let action = MultiAction::parse(label)?;
52

            
53
55
        debug!("Parsed action: {}", action);
54
55
        if action.actions.len() > 1 {
55
            return Err(MercError::from(format!(
56
                "Cannot read feature transition system: action \"{}\" has multiple actions",
57
                label
58
            )));
59
55
        }
60

            
61
55
        if let Some(action) = action.actions.first() {
62
55
            if let Some(arg) = action.args.first() {
63
44
                let expr = data_expr_to_bdd(manager_ref, &features, arg)?;
64
44
                debug!("Feature label {}", FormatConfigSet(&expr));
65
44
                feature_labels.push(expr);
66
            } else {
67
11
                feature_labels.push(manager_ref.with_manager_shared(|manager| BDDFunction::t(manager)));
68
            }
69
        } else {
70
            // This is the tau action, is always enabled.
71
            feature_labels.push(manager_ref.with_manager_shared(|manager| BDDFunction::t(manager)));
72
        }
73
    }
74

            
75
2
    let original_labels = aut.labels().to_vec();
76

            
77
    // Find the associated feature for each label and relabel the LTS accordingly.
78
55
    let aut = aut.relabel(|label| {
79
55
        let associated_feature = feature_labels[original_labels
80
55
            .iter()
81
1290
            .position(|orig| *orig == label)
82
55
            .expect("label must have an associated feature")]
83
55
        .clone();
84

            
85
55
        FeaturedLabel::new(label, associated_feature)
86
55
    });
87

            
88
2
    Ok(FeatureTransitionSystem::new(aut, features))
89
2
}
90

            
91
/// Converts the given data expression into a BDD function.
92
///
93
/// The input should be a data expression of the shape: expr = node(var, expr, expr) | tt | ff.
94
186
fn data_expr_to_bdd(
95
186
    manager_ref: &BDDManagerRef,
96
186
    variables: &HashMap<String, BDDFunction>,
97
186
    expr: &DataExpr,
98
186
) -> Result<BDDFunction, MercError> {
99
186
    match expr {
100
70
        DataExpr::Application { function, arguments } => {
101
70
            match function.as_ref() {
102
                // A node must be of the shape 'node(var, true_branch, false_branch)'
103
70
                DataExpr::Id(name) => {
104
70
                    if name == "node" {
105
70
                        let variable = format!("{}", arguments[0]);
106
70
                        let then_branch = data_expr_to_bdd(manager_ref, variables, &arguments[1])?;
107
70
                        let else_branch = data_expr_to_bdd(manager_ref, variables, &arguments[2])?;
108
70
                        Ok(variables
109
70
                            .get(&variable)
110
70
                            .ok_or(format!("Variable \"{}\" not found in feature diagram", variable))?
111
70
                            .ite(&then_branch, &else_branch)?)
112
                    } else {
113
                        unimplemented!("Conversion of data expression to BDD not implemented for this function");
114
                    }
115
                }
116
                _ => unimplemented!("Conversion of data expression to BDD not implemented for this function"),
117
            }
118
        }
119
116
        DataExpr::Id(name) => {
120
            // Deal with the base cases.
121
116
            match name.as_str() {
122
116
                "tt" => Ok(manager_ref.with_manager_shared(|manager| BDDFunction::t(manager))),
123
62
                "ff" => Ok(manager_ref.with_manager_shared(|manager| BDDFunction::f(manager))),
124
                _ => unimplemented!("Cannot convert data expression \"{expr}\" to BDD"),
125
            }
126
        }
127
        _ => unimplemented!("Cannot convert data expression \"{expr}\" to BDD"),
128
    }
129
186
}
130

            
131
/// A feature diagram represented as a BDD.
132
pub struct FeatureDiagram {
133
    /// The mapping from feature names to their BDD variable.
134
    features: HashMap<String, BDDFunction>,
135

            
136
    /// The feature names in the order they are defined in the input.
137
    feature_names: Vec<String>,
138

            
139
    /// Stores the set of products as a BDD function.
140
    configuration: BDDFunction,
141
}
142

            
143
impl FeatureDiagram {
144
    /// Reads feature diagram from the input.
145
    ///
146
    /// # Details
147
    ///
148
    /// The first line is a list of variable names, separated by commas. The
149
    /// second line is the initial configuration, represented as a data
150
    /// expression. This function will initialize the BDD manager with the
151
    /// variables read from the first line, and assumes that the manager has no
152
    /// variables yet defined.
153
2
    pub fn from_reader<R: Read>(manager_ref: &BDDManagerRef, input: R) -> Result<Self, MercError> {
154
2
        manager_ref.with_manager_exclusive(|manager| {
155
2
            debug_assert_eq!(
156
2
                manager.num_vars(),
157
                0,
158
                "A BDD manager can only hold the variables for a single feature diagram"
159
            )
160
2
        });
161

            
162
2
        let input = BufReader::new(input);
163
2
        let mut line_iter = input.lines();
164
2
        let first_line = line_iter.next().ok_or("Expected variable names line")??;
165

            
166
12
        let variable_names: Vec<String> = first_line.split(',').map(|s| s.trim().to_string()).collect();
167
2
        let variables = manager_ref.with_manager_exclusive(|manager| -> Result<Vec<BDDFunction>, MercError> {
168
2
            Ok(manager
169
2
                .add_named_vars(variable_names.iter())
170
2
                .map_err(|e| format!("Failed to create variables: {e}"))?
171
12
                .map(|i| BDDFunction::var(manager, i))
172
2
                .collect::<Result<Vec<_>, _>>()?)
173
2
        })?;
174

            
175
2
        let variables = HashMap::from_iter(variable_names.iter().cloned().zip(variables));
176

            
177
2
        let second_line = line_iter.next().ok_or("Expected initial configuration line")??;
178
2
        let initial_configuration = data_expr_to_bdd(manager_ref, &variables, &DataExpr::parse(&second_line)?)?;
179

            
180
2
        Ok(Self {
181
2
            features: variables,
182
2
            feature_names: variable_names,
183
2
            configuration: initial_configuration,
184
2
        })
185
2
    }
186

            
187
    /// Returns the configuration of the feature diagram.
188
1
    pub fn configuration(&self) -> &BDDFunction {
189
1
        &self.configuration
190
1
    }
191

            
192
    /// Returns the feature variables used in the feature diagram.
193
1
    pub fn features(&self) -> &HashMap<String, BDDFunction> {
194
1
        &self.features
195
1
    }
196

            
197
    /// Returns the feature names used in the feature diagram, in the order they are defined in the input.
198
    pub fn feature_names(&self) -> &[String] {
199
        &self.feature_names
200
    }
201
}
202

            
203
impl fmt::Debug for FeatureDiagram {
204
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205
        write!(f, "features = {}", self.feature_names().join(", "))
206
    }
207
}
208

            
209
/// A feature transition system, i.e., a labelled transition system
210
/// where each label is associated with a feature expression.
211
pub struct FeatureTransitionSystem<L: TransitionLabel> {
212
    /// The underlying labelled transition system.
213
    lts: LabelledTransitionSystem<FeaturedLabel<L>>,
214

            
215
    /// The features associated with this feature transition system.
216
    features: HashMap<String, BDDFunction>,
217
}
218

            
219
impl<L: TransitionLabel> FeatureTransitionSystem<L> {
220
    /// Creates a new feature transition system.
221
2
    pub fn new(lts: LabelledTransitionSystem<FeaturedLabel<L>>, features: HashMap<String, BDDFunction>) -> Self {
222
2
        Self { lts, features }
223
2
    }
224

            
225
    /// Returns the features used in the feature diagram.
226
1
    pub fn features(&self) -> &HashMap<String, BDDFunction> {
227
1
        &self.features
228
1
    }
229
}
230

            
231
/// A transition label associated with a feature expression.
232
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
233
pub struct FeaturedLabel<L: TransitionLabel> {
234
    label: L,
235
    feature_expr: BDDFunction,
236
}
237

            
238
impl<L: TransitionLabel> FeaturedLabel<L> {
239
    /// Creates a new featured label with the given label and feature expression.
240
55
    pub fn new(label: L, feature_expr: BDDFunction) -> Self {
241
55
        Self { label, feature_expr }
242
55
    }
243

            
244
    /// Returns the label of this featured label.
245
5
    pub fn label(&self) -> &L {
246
5
        &self.label
247
5
    }
248

            
249
    /// Returns the feature expression of this featured label.
250
4
    pub fn feature_expr(&self) -> &BDDFunction {
251
4
        &self.feature_expr
252
4
    }
253
}
254

            
255
impl<L: TransitionLabel> TransitionLabel for FeaturedLabel<L> {
256
    delegate::delegate! {
257
        to self.label {
258
            fn matches_label(&self, label: &str) -> bool;
259
            fn is_tau_label(&self) -> bool;
260
        }
261
    }
262

            
263
    fn from_index(_i: usize) -> Self {
264
        panic!("Cannot create FeaturedLabel from index");
265
    }
266

            
267
    fn tau_label() -> Self {
268
        // TODO: This is annoying, but we need a BDDManager to create the
269
        // feature label. Perhaps this should be refactored to not require the
270
        // tau label to be a constant.
271
        panic!("Cannot create a tau label for FeaturedLabel");
272
    }
273
}
274

            
275
impl<L: TransitionLabel> fmt::Debug for FeaturedLabel<L> {
276
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277
        write!(f, "{:?} [{}]", self.label, FormatConfigSet(&self.feature_expr))
278
    }
279
}
280

            
281
impl<L: TransitionLabel> fmt::Display for FeaturedLabel<L> {
282
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283
        // This is also used for the output in .aut format, so should be valid labels.
284
        write!(f, "{}", self.label)
285
    }
286
}
287

            
288
impl<L: TransitionLabel> LTS for FeatureTransitionSystem<L> {
289
    type Label = FeaturedLabel<L>;
290

            
291
    fn merge_disjoint<Lts>(self, _other: &Lts) -> (LabelledTransitionSystem<FeaturedLabel<L>>, StateIndex) {
292
        unimplemented!("Merging feature transition systems is not yet implemented")
293
    }
294

            
295
    delegate::delegate! {
296
        to self.lts {
297
1
            fn initial_state_index(&self) -> StateIndex;
298
            fn num_of_states(&self) -> usize;
299
            fn num_of_labels(&self) -> usize;
300
            fn num_of_transitions(&self) -> usize;
301
            fn is_hidden_label(&self, label_index: LabelIndex) -> bool;
302
5
            fn labels(&self) -> &[FeaturedLabel<L>];
303
9
            fn outgoing_transitions(&self, state_index: StateIndex) -> impl Iterator<Item = Transition>;
304
            fn iter_states(&self) -> impl Iterator<Item = StateIndex> + '_;
305
        }
306
    }
307
}
308

            
309
#[cfg(test)]
310
mod tests {
311
    use merc_macros::merc_test;
312

            
313
    use super::*;
314

            
315
    #[merc_test]
316
    #[cfg_attr(miri, ignore)] // Oxidd does not support miri (specifically the crossbeam-epoch dependency)
317
    fn test_read_minepump_fts() {
318
        let manager_ref = oxidd::bdd::new_manager(2048, 1024, 1);
319

            
320
        let feature_diagram = FeatureDiagram::from_reader(
321
            &manager_ref,
322
            include_bytes!("../../../examples/vpg/minepump_fts.fd") as &[u8],
323
        )
324
        .unwrap();
325

            
326
        let _result = read_fts(
327
            &manager_ref,
328
            include_bytes!("../../../examples/vpg/minepump_fts.aut") as &[u8],
329
            feature_diagram.features,
330
        )
331
        .unwrap();
332
    }
333
}