1use serde::{Deserialize, Serialize};
19use std::fmt;
20use std::ops::{Add, AddAssign, Sub, SubAssign};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
24#[repr(transparent)]
25pub struct TokenCount(u32);
26
27impl TokenCount {
28 #[inline]
30 pub const fn new(count: u32) -> Self {
31 Self(count)
32 }
33
34 #[inline]
36 pub const fn zero() -> Self {
37 Self(0)
38 }
39
40 #[inline]
42 pub const fn get(self) -> u32 {
43 self.0
44 }
45
46 #[inline]
48 pub const fn is_zero(self) -> bool {
49 self.0 == 0
50 }
51
52 #[inline]
54 pub const fn saturating_sub(self, rhs: Self) -> Self {
55 Self(self.0.saturating_sub(rhs.0))
56 }
57
58 #[inline]
60 pub const fn saturating_add(self, rhs: Self) -> Self {
61 Self(self.0.saturating_add(rhs.0))
62 }
63
64 #[inline]
66 pub fn percentage_of(self, total: Self) -> f32 {
67 if total.0 == 0 {
68 0.0
69 } else {
70 (self.0 as f32 / total.0 as f32) * 100.0
71 }
72 }
73}
74
75impl Add for TokenCount {
76 type Output = Self;
77
78 #[inline]
79 fn add(self, rhs: Self) -> Self::Output {
80 Self(self.0 + rhs.0)
81 }
82}
83
84impl AddAssign for TokenCount {
85 #[inline]
86 fn add_assign(&mut self, rhs: Self) {
87 self.0 += rhs.0;
88 }
89}
90
91impl Sub for TokenCount {
92 type Output = Self;
93
94 #[inline]
95 fn sub(self, rhs: Self) -> Self::Output {
96 Self(self.0 - rhs.0)
97 }
98}
99
100impl SubAssign for TokenCount {
101 #[inline]
102 fn sub_assign(&mut self, rhs: Self) {
103 self.0 -= rhs.0;
104 }
105}
106
107impl From<u32> for TokenCount {
108 #[inline]
109 fn from(value: u32) -> Self {
110 Self(value)
111 }
112}
113
114impl From<TokenCount> for u32 {
115 #[inline]
116 fn from(value: TokenCount) -> Self {
117 value.0
118 }
119}
120
121impl fmt::Display for TokenCount {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 write!(f, "{} tokens", self.0)
124 }
125}
126
127impl std::iter::Sum for TokenCount {
128 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
129 iter.fold(Self::zero(), |acc, x| acc + x)
130 }
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
135#[repr(transparent)]
136pub struct LineNumber(u32);
137
138impl LineNumber {
139 #[inline]
141 pub const fn new(line: u32) -> Self {
142 Self(line)
143 }
144
145 #[inline]
147 pub const fn first() -> Self {
148 Self(1)
149 }
150
151 #[inline]
153 pub const fn get(self) -> u32 {
154 self.0
155 }
156
157 #[inline]
159 pub const fn is_valid(self) -> bool {
160 self.0 > 0
161 }
162
163 #[inline]
165 pub const fn to_zero_indexed(self) -> u32 {
166 self.0.saturating_sub(1)
167 }
168
169 #[inline]
171 pub const fn from_zero_indexed(offset: u32) -> Self {
172 Self(offset + 1)
173 }
174
175 #[inline]
177 pub const fn lines_to(self, end: Self) -> u32 {
178 if end.0 >= self.0 {
179 end.0 - self.0 + 1
180 } else {
181 1
182 }
183 }
184}
185
186impl From<u32> for LineNumber {
187 #[inline]
188 fn from(value: u32) -> Self {
189 Self(value)
190 }
191}
192
193impl From<LineNumber> for u32 {
194 #[inline]
195 fn from(value: LineNumber) -> Self {
196 value.0
197 }
198}
199
200impl fmt::Display for LineNumber {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 write!(f, "L{}", self.0)
203 }
204}
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
208#[repr(transparent)]
209pub struct ByteOffset(usize);
210
211impl ByteOffset {
212 #[inline]
214 pub const fn new(offset: usize) -> Self {
215 Self(offset)
216 }
217
218 #[inline]
220 pub const fn zero() -> Self {
221 Self(0)
222 }
223
224 #[inline]
226 pub const fn get(self) -> usize {
227 self.0
228 }
229}
230
231impl From<usize> for ByteOffset {
232 #[inline]
233 fn from(value: usize) -> Self {
234 Self(value)
235 }
236}
237
238impl From<ByteOffset> for usize {
239 #[inline]
240 fn from(value: ByteOffset) -> Self {
241 value.0
242 }
243}
244
245impl fmt::Display for ByteOffset {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 write!(f, "@{}", self.0)
248 }
249}
250
251#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
253#[repr(transparent)]
254pub struct SymbolId(u32);
255
256impl SymbolId {
257 #[inline]
259 pub const fn new(id: u32) -> Self {
260 Self(id)
261 }
262
263 #[inline]
265 pub const fn unknown() -> Self {
266 Self(0)
267 }
268
269 #[inline]
271 pub const fn get(self) -> u32 {
272 self.0
273 }
274
275 #[inline]
277 pub const fn is_valid(self) -> bool {
278 self.0 > 0
279 }
280}
281
282impl From<u32> for SymbolId {
283 #[inline]
284 fn from(value: u32) -> Self {
285 Self(value)
286 }
287}
288
289impl From<SymbolId> for u32 {
290 #[inline]
291 fn from(value: SymbolId) -> Self {
292 value.0
293 }
294}
295
296impl fmt::Display for SymbolId {
297 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
298 write!(f, "#{}", self.0)
299 }
300}
301
302#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
304#[repr(transparent)]
305pub struct FileSize(u64);
306
307impl FileSize {
308 #[inline]
310 pub const fn new(bytes: u64) -> Self {
311 Self(bytes)
312 }
313
314 #[inline]
316 pub const fn zero() -> Self {
317 Self(0)
318 }
319
320 #[inline]
322 pub const fn bytes(self) -> u64 {
323 self.0
324 }
325
326 #[inline]
328 pub const fn kilobytes(self) -> u64 {
329 self.0 / 1024
330 }
331
332 #[inline]
334 pub const fn megabytes(self) -> u64 {
335 self.0 / (1024 * 1024)
336 }
337
338 #[inline]
340 pub const fn exceeds(self, limit: Self) -> bool {
341 self.0 > limit.0
342 }
343}
344
345impl From<u64> for FileSize {
346 #[inline]
347 fn from(value: u64) -> Self {
348 Self(value)
349 }
350}
351
352impl From<FileSize> for u64 {
353 #[inline]
354 fn from(value: FileSize) -> Self {
355 value.0
356 }
357}
358
359impl fmt::Display for FileSize {
360 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361 if self.0 >= 1024 * 1024 {
362 write!(f, "{:.1} MB", self.0 as f64 / (1024.0 * 1024.0))
363 } else if self.0 >= 1024 {
364 write!(f, "{:.1} KB", self.0 as f64 / 1024.0)
365 } else {
366 write!(f, "{} bytes", self.0)
367 }
368 }
369}
370
371#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default, Serialize, Deserialize)]
373#[repr(transparent)]
374pub struct ImportanceScore(f32);
375
376impl ImportanceScore {
377 #[inline]
379 pub fn new(score: f32) -> Self {
380 Self(score.clamp(0.0, 1.0))
381 }
382
383 #[inline]
385 pub const fn zero() -> Self {
386 Self(0.0)
387 }
388
389 #[inline]
391 pub const fn max() -> Self {
392 Self(1.0)
393 }
394
395 #[inline]
397 pub const fn default_score() -> Self {
398 Self(0.5)
399 }
400
401 #[inline]
403 pub const fn get(self) -> f32 {
404 self.0
405 }
406
407 #[inline]
409 pub fn is_high(self) -> bool {
410 self.0 > 0.7
411 }
412
413 #[inline]
415 pub fn is_low(self) -> bool {
416 self.0 < 0.3
417 }
418}
419
420impl From<f32> for ImportanceScore {
421 #[inline]
422 fn from(value: f32) -> Self {
423 Self::new(value)
424 }
425}
426
427impl From<ImportanceScore> for f32 {
428 #[inline]
429 fn from(value: ImportanceScore) -> Self {
430 value.0
431 }
432}
433
434impl fmt::Display for ImportanceScore {
435 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436 write!(f, "{:.2}", self.0)
437 }
438}
439
440impl Eq for ImportanceScore {}
441
442#[allow(clippy::derive_ord_xor_partial_ord)]
443impl Ord for ImportanceScore {
444 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
445 self.partial_cmp(other).unwrap_or(std::cmp::Ordering::Equal)
446 }
447}
448
449impl std::hash::Hash for ImportanceScore {
450 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
451 self.0.to_bits().hash(state);
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458
459 #[test]
461 fn test_token_count_operations() {
462 let a = TokenCount::new(100);
463 let b = TokenCount::new(50);
464
465 assert_eq!((a + b).get(), 150);
466 assert_eq!((a - b).get(), 50);
467 assert_eq!(a.saturating_sub(TokenCount::new(200)).get(), 0);
468 }
469
470 #[test]
471 fn test_token_count_percentage() {
472 let part = TokenCount::new(25);
473 let total = TokenCount::new(100);
474
475 assert!((part.percentage_of(total) - 25.0).abs() < 0.01);
476 assert_eq!(part.percentage_of(TokenCount::zero()), 0.0);
477 }
478
479 #[test]
480 fn test_token_count_sum() {
481 let counts = vec![TokenCount::new(10), TokenCount::new(20), TokenCount::new(30)];
482 let sum: TokenCount = counts.into_iter().sum();
483 assert_eq!(sum.get(), 60);
484 }
485
486 #[test]
488 fn test_line_number_indexing() {
489 let line = LineNumber::new(10);
490
491 assert_eq!(line.to_zero_indexed(), 9);
492 assert_eq!(LineNumber::from_zero_indexed(9).get(), 10);
493 }
494
495 #[test]
496 fn test_line_number_range() {
497 let start = LineNumber::new(5);
498 let end = LineNumber::new(10);
499
500 assert_eq!(start.lines_to(end), 6);
501 assert_eq!(end.lines_to(start), 1); }
503
504 #[test]
506 fn test_byte_offset() {
507 let offset = ByteOffset::new(1024);
508 assert_eq!(offset.get(), 1024);
509 assert_eq!(ByteOffset::zero().get(), 0);
510 }
511
512 #[test]
514 fn test_symbol_id_validity() {
515 assert!(!SymbolId::unknown().is_valid());
516 assert!(SymbolId::new(1).is_valid());
517 assert!(!SymbolId::new(0).is_valid());
518 }
519
520 #[test]
522 fn test_file_size_conversions() {
523 let size = FileSize::new(1024 * 1024 + 512 * 1024); assert_eq!(size.kilobytes(), 1536);
526 assert_eq!(size.megabytes(), 1);
527 }
528
529 #[test]
530 fn test_file_size_display() {
531 assert_eq!(FileSize::new(500).to_string(), "500 bytes");
532 assert_eq!(FileSize::new(2048).to_string(), "2.0 KB");
533 assert_eq!(FileSize::new(1024 * 1024).to_string(), "1.0 MB");
534 }
535
536 #[test]
538 fn test_importance_score_clamping() {
539 assert_eq!(ImportanceScore::new(-0.5).get(), 0.0);
540 assert_eq!(ImportanceScore::new(1.5).get(), 1.0);
541 assert_eq!(ImportanceScore::new(0.5).get(), 0.5);
542 }
543
544 #[test]
545 fn test_importance_score_classification() {
546 assert!(ImportanceScore::new(0.8).is_high());
547 assert!(!ImportanceScore::new(0.5).is_high());
548 assert!(ImportanceScore::new(0.2).is_low());
549 assert!(!ImportanceScore::new(0.5).is_low());
550 }
551
552 #[test]
554 fn test_display_formatting() {
555 assert_eq!(TokenCount::new(100).to_string(), "100 tokens");
556 assert_eq!(LineNumber::new(42).to_string(), "L42");
557 assert_eq!(ByteOffset::new(1000).to_string(), "@1000");
558 assert_eq!(SymbolId::new(5).to_string(), "#5");
559 }
560}