1pub const ENTRY_INITIAL_OFFSET: usize = 32;
18pub const PARAMS_SIZE: usize = 6;
19pub const FIXED_PART_SIZE: usize = 32;
20pub const WORD_INFO_FIXED_SIZE: usize = FIXED_PART_SIZE - PARAMS_SIZE;
21pub const WORD_ID_ALIGNMENT_BITS: usize = 3;
22pub const WORD_INFO_OFFSET_ALIGNMENT: usize = 1 << WORD_ID_ALIGNMENT_BITS;
23
24pub const OFFSET_C_UNIT_SPLIT_LENGTH: usize = 26;
25pub const OFFSET_B_UNIT_SPLIT_LENGTH: usize = 27;
26pub const OFFSET_A_UNIT_SPLIT_LENGTH: usize = 28;
27pub const OFFSET_WORD_STRUCTURE_LENGTH: usize = 29;
28pub const OFFSET_SYNONYM_GROUP_IDS_LENGTH: usize = 30;
29pub const OFFSET_USER_DATA_FLAG: usize = 31;
30
31#[inline]
32pub const fn aligned_size(size: usize) -> usize {
33 (size + (WORD_INFO_OFFSET_ALIGNMENT - 1)) & !(WORD_INFO_OFFSET_ALIGNMENT - 1)
34}
35
36#[inline]
37pub fn embedded_len(len: i8) -> usize {
38 std::cmp::max(0, len) as usize
39}
40
41#[inline]
42pub fn is_valid_user_data_flag(flag: i8) -> bool {
43 matches!(flag, 0 | 1)
44}
45
46pub fn size_from_lengths(
47 c_len: i8,
48 b_len: i8,
49 a_len: i8,
50 ws_len: i8,
51 syn_len: i8,
52 user_data_units: Option<i16>,
53) -> Option<usize> {
54 let size = unaligned_size_from_lengths(c_len, b_len, a_len, ws_len, syn_len, user_data_units)?;
55 Some(aligned_size(size))
56}
57
58pub(crate) fn size_from_variable_layout(layout: WordInfoVariableLayout) -> Option<usize> {
59 size_from_lengths(
60 layout.c_unit_split_length,
61 layout.b_unit_split_length,
62 layout.a_unit_split_length,
63 layout.word_structure_length,
64 layout.synonym_group_ids_length,
65 layout.user_data_units(),
66 )
67}
68
69pub fn unaligned_size_from_lengths(
70 c_len: i8,
71 b_len: i8,
72 a_len: i8,
73 ws_len: i8,
74 syn_len: i8,
75 user_data_units: Option<i16>,
76) -> Option<usize> {
77 if c_len < 0 || syn_len < 0 {
78 return None;
79 }
80
81 let mut size = FIXED_PART_SIZE;
82 size = size.checked_add(4 * c_len as usize)?;
83 size = size.checked_add(4 * embedded_len(b_len))?;
84 size = size.checked_add(4 * embedded_len(a_len))?;
85 size = size.checked_add(4 * embedded_len(ws_len))?;
86 size = size.checked_add(4 * syn_len as usize)?;
87
88 if let Some(units) = user_data_units {
89 if units < 0 {
90 return None;
91 }
92 size = size.checked_add(2 + units as usize * 2)?;
94 }
95
96 Some(size)
97}
98
99#[derive(Clone, Copy, Debug, Eq, PartialEq)]
100pub(crate) struct WordInfoVariableLayout {
101 pub c_unit_split_length: i8,
102 pub b_unit_split_length: i8,
103 pub a_unit_split_length: i8,
104 pub word_structure_length: i8,
105 pub synonym_group_ids_length: i8,
106 pub user_data_flag: i8,
107 pub user_data_units: i16,
108}
109
110impl WordInfoVariableLayout {
111 #[allow(clippy::too_many_arguments)]
112 pub fn new(
113 c_unit_split_len: usize,
114 b_unit_split_shared: bool,
115 b_unit_split_len: usize,
116 a_unit_split_shared: bool,
117 a_unit_split_len: usize,
118 word_structure_shared: bool,
119 word_structure_len: usize,
120 synonym_group_ids_len: usize,
121 user_data_units: usize,
122 ) -> Option<Self> {
123 let c_len = i8::try_from(c_unit_split_len).ok()?;
124 let b_len = if b_unit_split_shared {
125 -1
126 } else {
127 i8::try_from(b_unit_split_len).ok()?
128 };
129 let a_len = if a_unit_split_shared {
130 -1
131 } else {
132 i8::try_from(a_unit_split_len).ok()?
133 };
134 let ws_len = if word_structure_shared {
135 -1
136 } else {
137 i8::try_from(word_structure_len).ok()?
138 };
139 let syn_len = i8::try_from(synonym_group_ids_len).ok()?;
140 let user_data_units = i16::try_from(user_data_units).ok()?;
141 let user_data_flag = if user_data_units == 0 { 0 } else { 1 };
142 Some(Self {
143 c_unit_split_length: c_len,
144 b_unit_split_length: b_len,
145 a_unit_split_length: a_len,
146 word_structure_length: ws_len,
147 synonym_group_ids_length: syn_len,
148 user_data_flag,
149 user_data_units,
150 })
151 }
152
153 #[inline]
154 pub fn user_data_units(self) -> Option<i16> {
155 if self.user_data_flag == 0 {
156 None
157 } else {
158 Some(self.user_data_units)
159 }
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn computes_size_without_user_data() {
169 let size = size_from_lengths(2, -1, -1, 3, 1, None).unwrap();
170 assert_eq!(size, 56);
171 }
172
173 #[test]
174 fn computes_size_with_user_data() {
175 let size = size_from_lengths(1, 2, 3, -1, 2, Some(4)).unwrap();
176 assert_eq!(size, 80);
177 }
178
179 #[test]
180 fn computes_size_from_variable_layout() {
181 let layout = WordInfoVariableLayout::new(1, false, 2, false, 3, true, 3, 2, 4).unwrap();
182 let size = size_from_variable_layout(layout).unwrap();
183 assert_eq!(size, 80);
184 }
185
186 #[test]
187 fn rejects_invalid_lengths() {
188 assert!(size_from_lengths(-1, 0, 0, 0, 0, None).is_none());
189 assert!(size_from_lengths(0, 0, 0, 0, -1, None).is_none());
190 assert!(size_from_lengths(0, 0, 0, 0, 0, Some(-1)).is_none());
191 }
192
193 #[test]
194 fn variable_layout_marks_shared_split_arrays() {
195 let layout = WordInfoVariableLayout::new(2, true, 2, false, 1, true, 1, 3, 0).unwrap();
196 assert_eq!(layout.c_unit_split_length, 2);
197 assert_eq!(layout.b_unit_split_length, -1);
198 assert_eq!(layout.a_unit_split_length, 1);
199 assert_eq!(layout.word_structure_length, -1);
200 assert_eq!(layout.synonym_group_ids_length, 3);
201 assert_eq!(layout.user_data_flag, 0);
202 assert_eq!(layout.user_data_units, 0);
203 }
204
205 #[test]
206 fn variable_layout_marks_user_data_presence() {
207 let layout = WordInfoVariableLayout::new(0, false, 1, false, 2, false, 3, 4, 5).unwrap();
208 assert_eq!(layout.user_data_flag, 1);
209 assert_eq!(layout.user_data_units, 5);
210 }
211
212 #[test]
213 fn variable_layout_rejects_large_lengths() {
214 assert!(WordInfoVariableLayout::new(
215 usize::from(i8::MAX as u8) + 1,
216 false,
217 0,
218 false,
219 0,
220 false,
221 0,
222 0,
223 0,
224 )
225 .is_none());
226 assert!(WordInfoVariableLayout::new(
227 0,
228 false,
229 0,
230 false,
231 0,
232 false,
233 0,
234 0,
235 i16::MAX as usize + 1
236 )
237 .is_none());
238 }
239}