donet_core/
dcswitch.rs

1/*
2    This file is part of Donet.
3
4    Copyright © 2024-2025 Max Rodriguez <[email protected]>
5
6    Donet is free software; you can redistribute it and/or modify
7    it under the terms of the GNU Affero General Public License,
8    as published by the Free Software Foundation, either version 3
9    of the License, or (at your option) any later version.
10
11    Donet is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14    GNU Affero General Public License for more details.
15
16    You should have received a copy of the GNU Affero General Public
17    License along with Donet. If not, see <https://www.gnu.org/licenses/>.
18*/
19
20//! Data model that represents a DC switch statement.
21
22use crate::dcfield::DCField;
23use crate::hashgen::*;
24use std::collections::HashMap;
25
26/// Represents a case in a DC switch declaration.
27#[derive(Debug)]
28pub struct SwitchCase {
29    /// Note that in the legacy DC language, switch cases
30    /// always assume to break, no matter if a break
31    /// statement was parsed at syntax analysis. This
32    /// legacy behavior is not followed by Donet.
33    breaks: bool,
34    /// Empty byte array signifies default case.
35    value: Vec<u8>,
36    fields: Vec<DCField>,
37}
38
39impl std::fmt::Display for SwitchCase {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        if self.is_default() {
42            writeln!(f, "default:")?;
43        } else {
44            write!(f, "case ")?;
45            //self.switch.key.format_packed_data(f, &self.value, false)?;
46            writeln!(f, ":")?;
47        }
48
49        for field in &self.fields {
50            f.write_str(&field.to_string())?;
51        }
52        if self.breaks {
53            writeln!(f, "break;")?;
54        }
55        Ok(())
56    }
57}
58
59impl LegacyDCHash for SwitchCase {
60    fn generate_hash(&self, hashgen: &mut DCHashGenerator) {
61        if !self.is_default() {
62            hashgen.add_blob(self.value.clone());
63        }
64
65        hashgen.add_int(self.get_num_fields() as i32);
66
67        for field in &self.fields {
68            field.generate_hash(hashgen);
69        }
70    }
71}
72
73impl<'dc> SwitchCase {
74    /// Returns true if this case is a default case.
75    pub fn is_default(&self) -> bool {
76        self.value.is_empty()
77    }
78
79    /// Returns the number of fields in the case.
80    pub fn get_num_fields(&self) -> usize {
81        self.fields.len()
82    }
83
84    pub fn get_field(&self, index: usize) -> Option<&DCField> {
85        self.fields.get(index)
86    }
87
88    pub fn get_field_by_name(&self, _name: String) -> Option<&DCField> {
89        todo!()
90    }
91}
92
93/// Represents a DC Switch statement, which can appear inside
94/// a dclass declaration and represents two or more alternative
95/// unpacking schemes based on the first field read.
96#[derive(Debug)]
97pub struct DCSwitch {
98    name: Option<String>,
99    key: DCField,
100    cases: Vec<SwitchCase>,
101    default_case: Option<SwitchCase>,
102    cases_by_value: HashMap<Vec<u8>, usize>,
103}
104
105impl std::fmt::Display for DCSwitch {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        write!(f, "switch")?;
108
109        if let Some(name) = &self.name {
110            write!(f, " {}", name)?;
111        }
112        write!(f, " (")?;
113        f.write_str(&self.key.to_string())?;
114
115        writeln!(f, ") {{")?;
116
117        for case in &self.cases {
118            f.write_str(&case.to_string())?;
119        }
120        writeln!(f, "}};")
121    }
122}
123
124impl LegacyDCHash for DCSwitch {
125    fn generate_hash(&self, hashgen: &mut DCHashGenerator) {
126        if let Some(name) = self.get_name() {
127            hashgen.add_string(name)
128        }
129
130        self.key.generate_hash(hashgen);
131
132        hashgen.add_int(self.get_num_cases() as i32);
133
134        for case in &self.cases {
135            case.generate_hash(hashgen);
136        }
137
138        if let Some(default) = &self.default_case {
139            default.generate_hash(hashgen);
140        }
141    }
142}
143
144impl DCSwitch {
145    /// Returns the optional identifier for this switch.
146    #[inline(always)]
147    pub fn get_name(&self) -> Option<String> {
148        self.name.clone()
149    }
150
151    /// Returns the key parameter on which the switch is based.
152    ///
153    /// The value of this parameter in the record determines which
154    /// one of the several cases within the switch will be used.
155    #[inline(always)]
156    pub fn get_key_parameter(&self) -> &DCField {
157        &self.key
158    }
159
160    /// Returns the number of different cases within the switch.
161    ///
162    /// The legal values for case_index range from 0 to n_cases - 1.
163    #[inline(always)]
164    pub fn get_num_cases(&self) -> usize {
165        self.cases.len()
166    }
167
168    /// Returns case reference from given index wrapped in an Option.
169    pub fn get_case(&self, index: usize) -> Option<&SwitchCase> {
170        self.cases.get(index)
171    }
172
173    /// Returns default case reference wrapped in an Option.
174    ///
175    /// A default case is optional, so `None` can be returned.
176    pub fn get_default_case(&self) -> Option<&SwitchCase> {
177        self.default_case.as_ref()
178    }
179
180    /// Returns the index of the case with the given packed value.
181    ///
182    /// `None` is returned if no case with that value is found.
183    pub fn get_case_index_by_value(&self, value: Vec<u8>) -> Option<usize> {
184        self.cases_by_value.get(&value).copied()
185    }
186
187    // TODO
188    pub fn apply_switch(&self, _value: Vec<u8>, _length: usize) {}
189}