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::LTS;
17
use merc_lts::LabelIndex;
18
use merc_lts::LabelledTransitionSystem;
19
use merc_lts::StateIndex;
20
use merc_lts::Transition;
21
use merc_lts::TransitionLabel;
22
use merc_lts::read_aut;
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)?;
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 = if label.is_tau_label() {
52
2
            MultiAction::tau()
53
        } else {
54
53
            MultiAction::parse(label)?
55
        };
56

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

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

            
79
2
    let original_labels = aut.labels().to_vec();
80

            
81
    // Find the associated feature for each label and relabel the LTS accordingly.
82
55
    let aut = aut.relabel_all(|label| {
83
55
        let associated_feature = feature_labels[original_labels
84
55
            .iter()
85
1290
            .position(|orig| *orig == label)
86
55
            .ok_or("label must have an associated feature")?]
87
55
        .clone();
88

            
89
        // This is fine for the tau label.
90
55
        Ok(FeaturedLabel::new(label, associated_feature))
91
55
    })?;
92

            
93
2
    Ok(FeatureTransitionSystem::new(aut, features))
94
2
}
95

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

            
136
/// A feature diagram represented as a BDD.
137
pub struct FeatureDiagram {
138
    /// The mapping from feature names to their BDD variable.
139
    features: HashMap<String, BDDFunction>,
140

            
141
    /// The feature names in the order they are defined in the input.
142
    feature_names: Vec<String>,
143

            
144
    /// Stores the set of products as a BDD function.
145
    configuration: BDDFunction,
146
}
147

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

            
167
2
        let input = BufReader::new(input);
168
2
        let mut line_iter = input.lines();
169
2
        let first_line = line_iter.next().ok_or("Expected variable names line")??;
170

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

            
180
2
        let variables = HashMap::from_iter(variable_names.iter().cloned().zip(variables));
181

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

            
185
2
        Ok(Self {
186
2
            features: variables,
187
2
            feature_names: variable_names,
188
2
            configuration: initial_configuration,
189
2
        })
190
2
    }
191

            
192
    /// Returns the configuration of the feature diagram.
193
1
    pub fn configuration(&self) -> &BDDFunction {
194
1
        &self.configuration
195
1
    }
196

            
197
    /// Returns the feature variables used in the feature diagram.
198
1
    pub fn features(&self) -> &HashMap<String, BDDFunction> {
199
1
        &self.features
200
1
    }
201

            
202
    /// Returns the feature names used in the feature diagram, in the order they are defined in the input.
203
    pub fn feature_names(&self) -> &[String] {
204
        &self.feature_names
205
    }
206
}
207

            
208
impl fmt::Debug for FeatureDiagram {
209
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210
        write!(f, "features = {}", self.feature_names().join(", "))
211
    }
212
}
213

            
214
/// A feature transition system, i.e., a labelled transition system
215
/// where each label is associated with a feature expression.
216
pub struct FeatureTransitionSystem<L: TransitionLabel> {
217
    /// The underlying labelled transition system.
218
    lts: LabelledTransitionSystem<FeaturedLabel<L>>,
219

            
220
    /// The features associated with this feature transition system.
221
    features: HashMap<String, BDDFunction>,
222
}
223

            
224
impl<L: TransitionLabel> FeatureTransitionSystem<L> {
225
    /// Creates a new feature transition system.
226
2
    pub fn new(lts: LabelledTransitionSystem<FeaturedLabel<L>>, features: HashMap<String, BDDFunction>) -> Self {
227
2
        Self { lts, features }
228
2
    }
229

            
230
    /// Returns the features used in the feature diagram.
231
1
    pub fn features(&self) -> &HashMap<String, BDDFunction> {
232
1
        &self.features
233
1
    }
234
}
235

            
236
/// A transition label associated with a feature expression.
237
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
238
pub struct FeaturedLabel<L: TransitionLabel> {
239
    label: L,
240
    feature_expr: BDDFunction,
241
}
242

            
243
impl<L: TransitionLabel> FeaturedLabel<L> {
244
    /// Creates a new featured label with the given label and feature expression.
245
55
    pub fn new(label: L, feature_expr: BDDFunction) -> Self {
246
55
        Self { label, feature_expr }
247
55
    }
248

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

            
254
    /// Returns the feature expression of this featured label.
255
4
    pub fn feature_expr(&self) -> &BDDFunction {
256
4
        &self.feature_expr
257
4
    }
258
}
259

            
260
impl<L: TransitionLabel> TransitionLabel for FeaturedLabel<L> {
261
    delegate::delegate! {
262
        to self.label {
263
            fn matches_label(&self, label: &str) -> bool;
264
7
            fn is_tau_label(&self) -> bool;
265
        }
266
    }
267

            
268
    fn from_index(_i: usize) -> Self {
269
        panic!("Cannot create FeaturedLabel from index");
270
    }
271

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

            
280
impl<L: TransitionLabel> fmt::Debug for FeaturedLabel<L> {
281
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
282
        write!(f, "{:?} [{}]", self.label, FormatConfigSet(&self.feature_expr))
283
    }
284
}
285

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

            
293
impl<L: TransitionLabel> LTS for FeatureTransitionSystem<L> {
294
    type Label = FeaturedLabel<L>;
295

            
296
    fn merge_disjoint<Lts>(self, _other: &Lts) -> (LabelledTransitionSystem<FeaturedLabel<L>>, StateIndex) {
297
        unimplemented!("Merging feature transition systems is not yet implemented")
298
    }
299

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

            
314
#[cfg(test)]
315
mod tests {
316
    use merc_macros::merc_test;
317

            
318
    use crate::FeatureDiagram;
319
    use crate::read_fts;
320

            
321
    #[merc_test]
322
    #[cfg_attr(miri, ignore)] // Oxidd does not support miri (specifically the crossbeam-epoch dependency)
323
    fn test_read_minepump_fts() {
324
        let manager_ref = oxidd::bdd::new_manager(2048, 1024, 1);
325

            
326
        let feature_diagram = FeatureDiagram::from_reader(
327
            &manager_ref,
328
            include_bytes!("../../../examples/vpg/minepump_fts.fd") as &[u8],
329
        )
330
        .unwrap();
331

            
332
        let _result = read_fts(
333
            &manager_ref,
334
            include_bytes!("../../../examples/vpg/minepump_fts.aut") as &[u8],
335
            feature_diagram.features,
336
        )
337
        .unwrap();
338
    }
339
}