1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
use crate::sketchbook::ids::{LayoutId, UninterpretedFnId, VarId};
use crate::sketchbook::model::ModelState;
use crate::sketchbook::Manager;

/// Methods for safely generating valid instances of identifiers for the current `ModelState`.
impl ModelState {
    /// Generate valid `VarId` that's currently not used by any variable in this `ModelState`.
    ///
    /// First, the given `ideal_id` or its transformation by replacing invalid characters are tried.
    /// If they are both invalid (non-unique), a numerical identifier is added at the end.
    /// By specifying `start_index`, the index search starts directly at that number (e.g., when
    /// ideal ID is "var" and start index is 3, search for ID starts with "var_3", "var_4", ...)
    ///
    /// **Warning:** Do not use this to pre-generate more than one id at a time, as the process
    /// is deterministic and might generate the same IDs. Always generate an Id, add that variable to
    /// the model, and then repeat for other variables.
    pub fn generate_var_id(&self, ideal_id: &str, start_index: Option<usize>) -> VarId {
        self.generate_id(
            ideal_id,
            &(Self::is_valid_var_id),
            self.num_vars(),
            start_index,
        )
    }

    /// Generate valid `LayoutId` that's currently not used by layouts in this `ModelState`.
    ///
    /// First, the given `ideal_id` or its transformation by replacing invalid characters are tried.
    /// If they are both invalid (non-unique), a numerical identifier is added at the end.
    /// By specifying `start_index`, the index search starts directly at that number (e.g., when
    /// ideal ID is "l" and start index is 3, search for ID starts with "l_3", "l_4", ...)
    ///
    /// **Warning:** Do not use this to pre-generate more than one id at a time, as the process
    /// is deterministic and might generate the same IDs. Always generate an Id, add that layout to
    /// the model, and then repeat for other layouts.
    pub fn generate_layout_id(&self, ideal_id: &str, start_index: Option<usize>) -> LayoutId {
        self.generate_id(
            ideal_id,
            &(Self::is_valid_layout_id),
            self.num_layouts(),
            start_index,
        )
    }

    /// Generate valid `UninterpretedFnId` that's currently not used by uninterpreted_fns in this `ModelState`.
    ///
    /// First, the given `ideal_id` or its transformation by replacing invalid characters are tried.
    /// If they are both invalid (non-unique), a numerical identifier is added at the end.
    /// By specifying `start_index`, the index search starts directly at that number (e.g., when
    /// ideal ID is "fn" and start index is 3, search for ID starts with "fn_3", "fn_4", ...)
    ///
    /// **Warning:** Do not use this to pre-generate more than one id at a time, as the process
    /// is deterministic and might generate the same IDs. Always generate an Id, add that fn to
    /// the model, and then repeat for other fns.
    pub fn generate_uninterpreted_fn_id(
        &self,
        ideal_id: &str,
        start_index: Option<usize>,
    ) -> UninterpretedFnId {
        self.generate_id(
            ideal_id,
            &(Self::is_valid_uninterpreted_fn_id),
            self.num_uninterpreted_fns(),
            start_index,
        )
    }
}

#[cfg(test)]
mod tests {
    use crate::sketchbook::ids::{LayoutId, VarId};
    use crate::sketchbook::model::ModelState;

    #[test]
    fn test_var_id_generating() {
        let model =
            ModelState::new_from_vars(vec![("a", "name"), ("b", "name"), ("c", "name")]).unwrap();
        assert_eq!(model.num_vars(), 3);

        // name slice that is a valid identifier as is
        let var_name_1 = "d";
        assert_eq!(
            model.generate_var_id(var_name_1, Some(1)),
            VarId::new("d_1").unwrap()
        );

        // name that is not a valid identifier as it contains various invalid characters
        let var_name_2 = "-d ??)&    ";
        assert_eq!(
            model.generate_var_id(var_name_2, Some(1)),
            VarId::new("d_1").unwrap()
        );

        // name that is already used in the network
        let var_name_3 = "a";
        // result will contain an numerical index in the end
        assert_eq!(
            model.generate_var_id(var_name_3, None),
            VarId::new("a_1").unwrap()
        );

        // name that starts with a number - will be cleaned and prefixed with "v_"
        let var_name_4 = "4ab??";
        // result will contain an numerical index in the end
        assert_eq!(
            model.generate_var_id(var_name_4, None),
            VarId::new("x_4ab").unwrap()
        );
    }

    #[test]
    fn test_layout_id_generating() {
        let mut model = ModelState::new_empty();
        let layout_id = LayoutId::new("l").unwrap();
        let default_layout_id = ModelState::get_default_layout_id();
        model.add_layout_simple(layout_id, "name").unwrap();
        assert_eq!(model.num_layouts(), 2);

        // expected result for all the following IDs will be the same
        let expected = LayoutId::new("l_1").unwrap();

        // name slice that is a valid identifier as is
        let name_1 = "l";
        assert_eq!(model.generate_layout_id(name_1, None), expected);

        // name that is not a valid identifier as it contains various invalid characters
        let name_2 = "%%%%l    &)";
        assert_eq!(model.generate_layout_id(name_2, None), expected);

        // add new layout
        let layout_id = LayoutId::new("ll").unwrap();
        model
            .add_layout_copy(layout_id, "name", &default_layout_id)
            .unwrap();

        // try generate ID for the same layout again - the result will have numerical index appended
        // however, this time we cant just add index 0 because the result would not be unique

        let name_3 = "ll";
        let expected = LayoutId::new("ll_1").unwrap();
        // search for unused index is incremental, starting at 0 (until valid index 1 is found)
        assert_eq!(model.generate_layout_id(name_3, None), expected);
    }
}