1use core::fmt;
29
30#[cfg(not(feature = "std"))]
31extern crate alloc;
32#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct Token<'a> {
38 pub token_type: TokenType,
40
41 pub span: &'a str,
43
44 pub line: usize,
46
47 pub column: usize,
49}
50
51impl<'a> Token<'a> {
52 #[must_use]
54 pub const fn new(token_type: TokenType, span: &'a str, line: usize, column: usize) -> Self {
55 Self {
56 token_type,
57 span,
58 line,
59 column,
60 }
61 }
62
63 #[must_use]
65 pub fn len(&self) -> usize {
66 self.span.chars().count()
67 }
68
69 #[must_use]
71 pub const fn is_empty(&self) -> bool {
72 self.span.is_empty()
73 }
74
75 #[must_use]
77 pub fn end_column(&self) -> usize {
78 self.column + self.len()
79 }
80
81 #[must_use]
83 pub const fn is_whitespace(&self) -> bool {
84 matches!(self.token_type, TokenType::Whitespace)
85 }
86
87 #[must_use]
89 pub const fn is_delimiter(&self) -> bool {
90 matches!(
91 self.token_type,
92 TokenType::Comma
93 | TokenType::Colon
94 | TokenType::SectionOpen
95 | TokenType::SectionClose
96 | TokenType::OverrideOpen
97 | TokenType::OverrideClose
98 )
99 }
100
101 #[must_use]
103 pub const fn is_content(&self) -> bool {
104 matches!(
105 self.token_type,
106 TokenType::Text
107 | TokenType::Number
108 | TokenType::HexValue
109 | TokenType::SectionName
110 | TokenType::OverrideBlock
111 )
112 }
113
114 #[must_use]
116 pub const fn validate_utf8(&self) -> bool {
117 true
118 }
119}
120
121impl fmt::Display for Token<'_> {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 write!(
124 f,
125 "{:?}@{}:{} '{}'",
126 self.token_type, self.line, self.column, self.span
127 )
128 }
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
136pub enum TokenType {
137 Text,
139
140 Number,
142
143 HexValue,
145
146 Colon,
148
149 Comma,
151
152 Newline,
154
155 SectionOpen,
157
158 SectionClose,
160
161 SectionName,
163
164 SectionHeader,
166
167 OverrideOpen,
169
170 OverrideClose,
172
173 OverrideBlock,
175
176 Comment,
178
179 Whitespace,
181
182 DrawingScale,
184
185 UuEncodedLine,
187
188 FontFilename,
190
191 GraphicFilename,
193
194 FormatLine,
196
197 EventType,
199
200 TimeValue,
202
203 BooleanValue,
205
206 PercentageValue,
208
209 StringLiteral,
211
212 Invalid,
214
215 Eof,
217}
218
219impl TokenType {
220 #[must_use]
222 pub const fn is_delimiter(self) -> bool {
223 matches!(
224 self,
225 Self::Colon
226 | Self::Comma
227 | Self::SectionOpen
228 | Self::SectionClose
229 | Self::OverrideOpen
230 | Self::OverrideClose
231 )
232 }
233
234 #[must_use]
236 pub const fn is_structural(self) -> bool {
237 matches!(
238 self,
239 Self::SectionHeader
240 | Self::SectionOpen
241 | Self::SectionClose
242 | Self::FormatLine
243 | Self::Newline
244 )
245 }
246
247 #[must_use]
249 pub const fn is_content(self) -> bool {
250 matches!(
251 self,
252 Self::Text
253 | Self::Number
254 | Self::HexValue
255 | Self::TimeValue
256 | Self::BooleanValue
257 | Self::PercentageValue
258 | Self::StringLiteral
259 )
260 }
261
262 #[must_use]
264 pub const fn is_skippable(self) -> bool {
265 matches!(self, Self::Whitespace | Self::Comment)
266 }
267
268 #[must_use]
270 pub const fn name(self) -> &'static str {
271 match self {
272 Self::Text => "text",
273 Self::Number => "number",
274 Self::HexValue => "hex value",
275 Self::Colon => "colon",
276 Self::Comma => "comma",
277 Self::Newline => "newline",
278 Self::SectionOpen => "section open",
279 Self::SectionClose => "section close",
280 Self::SectionName => "section name",
281 Self::SectionHeader => "section header",
282 Self::OverrideOpen => "override open",
283 Self::OverrideClose => "override close",
284 Self::OverrideBlock => "override block",
285 Self::Comment => "comment",
286 Self::Whitespace => "whitespace",
287 Self::DrawingScale => "drawing scale",
288 Self::UuEncodedLine => "UU-encoded line",
289 Self::FontFilename => "font filename",
290 Self::GraphicFilename => "graphic filename",
291 Self::FormatLine => "format line",
292 Self::EventType => "event type",
293 Self::TimeValue => "time value",
294 Self::BooleanValue => "boolean value",
295 Self::PercentageValue => "percentage value",
296 Self::StringLiteral => "string literal",
297 Self::Invalid => "invalid token",
298 Self::Eof => "end of file",
299 }
300 }
301}
302
303impl fmt::Display for TokenType {
304 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305 write!(f, "{}", self.name())
306 }
307}
308
309#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
314pub enum DelimiterType {
315 FieldSeparator,
317
318 ValueSeparator,
320
321 SectionBoundary,
323
324 OverrideBoundary,
326
327 CommentMarker,
329
330 LineTerminator,
332
333 DrawingSeparator,
335
336 TimeSeparator,
338
339 ColorSeparator,
341}
342
343impl DelimiterType {
344 #[must_use]
346 pub const fn chars(self) -> &'static [char] {
347 match self {
348 Self::FieldSeparator => &[':'],
349 Self::ValueSeparator => &[','],
350 Self::SectionBoundary => &['[', ']'],
351 Self::OverrideBoundary => &['{', '}'],
352 Self::CommentMarker => &[';'],
353 Self::LineTerminator => &['\n', '\r'],
354 Self::DrawingSeparator => &[' ', '\t'],
355 Self::TimeSeparator => &[':', '.'],
356 Self::ColorSeparator => &['&', 'H'],
357 }
358 }
359
360 #[must_use]
362 pub fn matches(self, ch: char) -> bool {
363 self.chars().contains(&ch)
364 }
365}
366
367#[derive(Debug, Clone, Copy, PartialEq, Eq)]
369pub struct TokenPosition {
370 pub offset: usize,
372
373 pub line: usize,
375
376 pub column: usize,
378}
379
380impl TokenPosition {
381 #[must_use]
383 pub const fn new(offset: usize, line: usize, column: usize) -> Self {
384 Self {
385 offset,
386 line,
387 column,
388 }
389 }
390
391 #[must_use]
393 pub const fn start() -> Self {
394 Self::new(0, 1, 1)
395 }
396
397 #[must_use]
399 pub const fn advance(mut self, ch: char) -> Self {
400 self.offset += ch.len_utf8();
401 if ch == '\n' {
402 self.line += 1;
403 self.column = 1;
404 } else {
405 self.column += 1;
406 }
407 self
408 }
409
410 #[must_use]
412 pub fn advance_by_str(mut self, s: &str) -> Self {
413 for ch in s.chars() {
414 self = self.advance(ch);
415 }
416 self
417 }
418}
419
420impl Default for TokenPosition {
421 fn default() -> Self {
422 Self::start()
423 }
424}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429 #[cfg(not(feature = "std"))]
430 use alloc::format;
431
432 #[test]
433 fn token_creation() {
434 let span = "test";
435 let token = Token::new(TokenType::Text, span, 1, 5);
436
437 assert_eq!(token.token_type, TokenType::Text);
438 assert_eq!(token.span, "test");
439 assert_eq!(token.line, 1);
440 assert_eq!(token.column, 5);
441 assert_eq!(token.len(), 4);
442 assert_eq!(token.end_column(), 9);
443 }
444
445 #[test]
446 fn token_empty_check() {
447 let empty_token = Token::new(TokenType::Text, "", 1, 1);
448 assert!(empty_token.is_empty());
449
450 let normal_token = Token::new(TokenType::Text, "text", 1, 1);
451 assert!(!normal_token.is_empty());
452 }
453
454 #[test]
455 fn token_type_checks() {
456 assert!(TokenType::Comma.is_delimiter());
457 assert!(TokenType::SectionHeader.is_structural());
458 assert!(TokenType::Text.is_content());
459 assert!(TokenType::Whitespace.is_skippable());
460 assert!(TokenType::Comment.is_skippable());
461 }
462
463 #[test]
464 fn token_classification() {
465 let text_token = Token::new(TokenType::Text, "hello", 1, 1);
466 assert!(text_token.is_content());
467 assert!(!text_token.is_delimiter());
468 assert!(!text_token.is_whitespace());
469
470 let comma_token = Token::new(TokenType::Comma, ",", 1, 6);
471 assert!(comma_token.is_delimiter());
472 assert!(!comma_token.is_content());
473
474 let ws_token = Token::new(TokenType::Whitespace, " ", 1, 7);
475 assert!(ws_token.is_whitespace());
476 assert!(!ws_token.is_content());
477 }
478
479 #[test]
480 fn delimiter_type_matching() {
481 assert!(DelimiterType::FieldSeparator.matches(':'));
482 assert!(DelimiterType::ValueSeparator.matches(','));
483 assert!(DelimiterType::SectionBoundary.matches('['));
484 assert!(DelimiterType::SectionBoundary.matches(']'));
485 assert!(DelimiterType::LineTerminator.matches('\n'));
486
487 assert!(!DelimiterType::FieldSeparator.matches(','));
488 assert!(!DelimiterType::ValueSeparator.matches(':'));
489 }
490
491 #[test]
492 fn token_position_advance() {
493 let mut pos = TokenPosition::start();
494 assert_eq!(pos.line, 1);
495 assert_eq!(pos.column, 1);
496 assert_eq!(pos.offset, 0);
497
498 pos = pos.advance('a');
499 assert_eq!(pos.line, 1);
500 assert_eq!(pos.column, 2);
501 assert_eq!(pos.offset, 1);
502
503 pos = pos.advance('\n');
504 assert_eq!(pos.line, 2);
505 assert_eq!(pos.column, 1);
506 assert_eq!(pos.offset, 2);
507 }
508
509 #[test]
510 fn token_position_advance_string() {
511 let pos = TokenPosition::start();
512 let new_pos = pos.advance_by_str("hello\nworld");
513
514 assert_eq!(new_pos.line, 2);
515 assert_eq!(new_pos.column, 6); assert_eq!(new_pos.offset, 11); }
518
519 #[test]
520 fn token_type_names() {
521 assert_eq!(TokenType::Text.name(), "text");
522 assert_eq!(TokenType::Number.name(), "number");
523 assert_eq!(TokenType::HexValue.name(), "hex value");
524 assert_eq!(TokenType::Invalid.name(), "invalid token");
525 }
526
527 #[test]
528 fn token_display() {
529 let token = Token::new(TokenType::Text, "hello", 2, 5);
530 let display = format!("{token}");
531 assert!(display.contains("Text"));
532 assert!(display.contains("2:5"));
533 assert!(display.contains("hello"));
534 }
535
536 #[test]
537 fn token_utf8_validation() {
538 let token = Token::new(TokenType::Text, "valid utf8", 1, 1);
539 assert!(token.validate_utf8());
540
541 let unicode_token = Token::new(TokenType::Text, "🎵", 1, 1);
542 assert!(unicode_token.validate_utf8());
543 }
544
545 #[test]
546 fn delimiter_token_types_are_delimiters() {
547 assert!(TokenType::Colon.is_delimiter());
548 assert!(TokenType::Comma.is_delimiter());
549 assert!(TokenType::SectionOpen.is_delimiter());
550 assert!(TokenType::SectionClose.is_delimiter());
551 assert!(TokenType::OverrideOpen.is_delimiter());
552 assert!(TokenType::OverrideClose.is_delimiter());
553 }
554
555 #[test]
556 fn non_delimiter_token_types_are_not_delimiters() {
557 assert!(!TokenType::Text.is_delimiter());
558 assert!(!TokenType::Number.is_delimiter());
559 assert!(!TokenType::HexValue.is_delimiter());
560 assert!(!TokenType::SectionName.is_delimiter());
561 assert!(!TokenType::SectionHeader.is_delimiter());
562 assert!(!TokenType::OverrideBlock.is_delimiter());
563 assert!(!TokenType::Comment.is_delimiter());
564 assert!(!TokenType::Whitespace.is_delimiter());
565 assert!(!TokenType::Newline.is_delimiter());
566 assert!(!TokenType::DrawingScale.is_delimiter());
567 assert!(!TokenType::UuEncodedLine.is_delimiter());
568 assert!(!TokenType::FontFilename.is_delimiter());
569 assert!(!TokenType::GraphicFilename.is_delimiter());
570 assert!(!TokenType::FormatLine.is_delimiter());
571 assert!(!TokenType::EventType.is_delimiter());
572 assert!(!TokenType::TimeValue.is_delimiter());
573 assert!(!TokenType::BooleanValue.is_delimiter());
574 assert!(!TokenType::PercentageValue.is_delimiter());
575 assert!(!TokenType::StringLiteral.is_delimiter());
576 assert!(!TokenType::Invalid.is_delimiter());
577 assert!(!TokenType::Eof.is_delimiter());
578 }
579
580 #[test]
581 fn structural_token_types_are_structural() {
582 assert!(TokenType::SectionHeader.is_structural());
583 assert!(TokenType::SectionOpen.is_structural());
584 assert!(TokenType::SectionClose.is_structural());
585 assert!(TokenType::FormatLine.is_structural());
586 assert!(TokenType::Newline.is_structural());
587 }
588
589 #[test]
590 fn non_structural_token_types_are_not_structural() {
591 assert!(!TokenType::Text.is_structural());
592 assert!(!TokenType::Number.is_structural());
593 assert!(!TokenType::HexValue.is_structural());
594 assert!(!TokenType::Colon.is_structural());
595 assert!(!TokenType::Comma.is_structural());
596 assert!(!TokenType::SectionName.is_structural());
597 assert!(!TokenType::OverrideOpen.is_structural());
598 assert!(!TokenType::OverrideClose.is_structural());
599 assert!(!TokenType::OverrideBlock.is_structural());
600 assert!(!TokenType::Comment.is_structural());
601 assert!(!TokenType::Whitespace.is_structural());
602 assert!(!TokenType::DrawingScale.is_structural());
603 assert!(!TokenType::UuEncodedLine.is_structural());
604 assert!(!TokenType::FontFilename.is_structural());
605 assert!(!TokenType::GraphicFilename.is_structural());
606 assert!(!TokenType::EventType.is_structural());
607 assert!(!TokenType::TimeValue.is_structural());
608 assert!(!TokenType::BooleanValue.is_structural());
609 assert!(!TokenType::PercentageValue.is_structural());
610 assert!(!TokenType::StringLiteral.is_structural());
611 assert!(!TokenType::Invalid.is_structural());
612 assert!(!TokenType::Eof.is_structural());
613 }
614
615 #[test]
616 fn content_token_types_are_content() {
617 assert!(TokenType::Text.is_content());
618 assert!(TokenType::Number.is_content());
619 assert!(TokenType::HexValue.is_content());
620 assert!(TokenType::TimeValue.is_content());
621 assert!(TokenType::BooleanValue.is_content());
622 assert!(TokenType::PercentageValue.is_content());
623 assert!(TokenType::StringLiteral.is_content());
624 }
625
626 #[test]
627 fn non_content_token_types_are_not_content() {
628 assert!(!TokenType::Colon.is_content());
629 assert!(!TokenType::Comma.is_content());
630 assert!(!TokenType::Newline.is_content());
631 assert!(!TokenType::SectionOpen.is_content());
632 assert!(!TokenType::SectionClose.is_content());
633 assert!(!TokenType::SectionName.is_content());
634 assert!(!TokenType::SectionHeader.is_content());
635 assert!(!TokenType::OverrideOpen.is_content());
636 assert!(!TokenType::OverrideClose.is_content());
637 assert!(!TokenType::OverrideBlock.is_content());
638 assert!(!TokenType::Comment.is_content());
639 assert!(!TokenType::Whitespace.is_content());
640 assert!(!TokenType::DrawingScale.is_content());
641 assert!(!TokenType::UuEncodedLine.is_content());
642 assert!(!TokenType::FontFilename.is_content());
643 assert!(!TokenType::GraphicFilename.is_content());
644 assert!(!TokenType::FormatLine.is_content());
645 assert!(!TokenType::EventType.is_content());
646 assert!(!TokenType::Invalid.is_content());
647 assert!(!TokenType::Eof.is_content());
648 }
649
650 #[test]
651 fn skippable_token_types_are_skippable() {
652 assert!(TokenType::Whitespace.is_skippable());
653 assert!(TokenType::Comment.is_skippable());
654 }
655
656 #[test]
657 fn basic_non_skippable_token_types() {
658 assert!(!TokenType::Text.is_skippable());
659 assert!(!TokenType::Number.is_skippable());
660 assert!(!TokenType::HexValue.is_skippable());
661 assert!(!TokenType::Colon.is_skippable());
662 assert!(!TokenType::Comma.is_skippable());
663 assert!(!TokenType::Newline.is_skippable());
664 }
665
666 #[test]
667 fn section_non_skippable_token_types() {
668 assert!(!TokenType::SectionOpen.is_skippable());
669 assert!(!TokenType::SectionClose.is_skippable());
670 assert!(!TokenType::SectionName.is_skippable());
671 assert!(!TokenType::SectionHeader.is_skippable());
672 }
673
674 #[test]
675 fn override_non_skippable_token_types() {
676 assert!(!TokenType::OverrideOpen.is_skippable());
677 assert!(!TokenType::OverrideClose.is_skippable());
678 assert!(!TokenType::OverrideBlock.is_skippable());
679 }
680
681 #[test]
682 fn special_non_skippable_token_types() {
683 assert!(!TokenType::DrawingScale.is_skippable());
684 assert!(!TokenType::UuEncodedLine.is_skippable());
685 assert!(!TokenType::FontFilename.is_skippable());
686 assert!(!TokenType::GraphicFilename.is_skippable());
687 assert!(!TokenType::FormatLine.is_skippable());
688 assert!(!TokenType::EventType.is_skippable());
689 assert!(!TokenType::TimeValue.is_skippable());
690 assert!(!TokenType::BooleanValue.is_skippable());
691 assert!(!TokenType::PercentageValue.is_skippable());
692 assert!(!TokenType::StringLiteral.is_skippable());
693 assert!(!TokenType::Invalid.is_skippable());
694 assert!(!TokenType::Eof.is_skippable());
695 }
696
697 #[test]
698 fn basic_token_type_names() {
699 assert_eq!(TokenType::Text.name(), "text");
700 assert_eq!(TokenType::Number.name(), "number");
701 assert_eq!(TokenType::HexValue.name(), "hex value");
702 assert_eq!(TokenType::Colon.name(), "colon");
703 assert_eq!(TokenType::Comma.name(), "comma");
704 assert_eq!(TokenType::Newline.name(), "newline");
705 assert_eq!(TokenType::Invalid.name(), "invalid token");
706 assert_eq!(TokenType::Eof.name(), "end of file");
707 }
708
709 #[test]
710 fn section_token_type_names() {
711 assert_eq!(TokenType::SectionOpen.name(), "section open");
712 assert_eq!(TokenType::SectionClose.name(), "section close");
713 assert_eq!(TokenType::SectionName.name(), "section name");
714 assert_eq!(TokenType::SectionHeader.name(), "section header");
715 }
716
717 #[test]
718 fn override_token_type_names() {
719 assert_eq!(TokenType::OverrideOpen.name(), "override open");
720 assert_eq!(TokenType::OverrideClose.name(), "override close");
721 assert_eq!(TokenType::OverrideBlock.name(), "override block");
722 }
723
724 #[test]
725 fn special_token_type_names() {
726 assert_eq!(TokenType::Comment.name(), "comment");
727 assert_eq!(TokenType::Whitespace.name(), "whitespace");
728 assert_eq!(TokenType::DrawingScale.name(), "drawing scale");
729 assert_eq!(TokenType::UuEncodedLine.name(), "UU-encoded line");
730 assert_eq!(TokenType::FontFilename.name(), "font filename");
731 assert_eq!(TokenType::GraphicFilename.name(), "graphic filename");
732 assert_eq!(TokenType::FormatLine.name(), "format line");
733 assert_eq!(TokenType::EventType.name(), "event type");
734 assert_eq!(TokenType::TimeValue.name(), "time value");
735 assert_eq!(TokenType::BooleanValue.name(), "boolean value");
736 assert_eq!(TokenType::PercentageValue.name(), "percentage value");
737 assert_eq!(TokenType::StringLiteral.name(), "string literal");
738 }
739
740 #[test]
741 fn token_type_display() {
742 assert_eq!(format!("{}", TokenType::Text), "text");
743 assert_eq!(format!("{}", TokenType::Number), "number");
744 assert_eq!(format!("{}", TokenType::Invalid), "invalid token");
745 assert_eq!(format!("{}", TokenType::Eof), "end of file");
746 }
747
748 #[test]
749 fn all_delimiter_types_chars() {
750 assert_eq!(DelimiterType::FieldSeparator.chars(), &[':']);
751 assert_eq!(DelimiterType::ValueSeparator.chars(), &[',']);
752 assert_eq!(DelimiterType::SectionBoundary.chars(), &['[', ']']);
753 assert_eq!(DelimiterType::OverrideBoundary.chars(), &['{', '}']);
754 assert_eq!(DelimiterType::CommentMarker.chars(), &[';']);
755 assert_eq!(DelimiterType::LineTerminator.chars(), &['\n', '\r']);
756 assert_eq!(DelimiterType::DrawingSeparator.chars(), &[' ', '\t']);
757 assert_eq!(DelimiterType::TimeSeparator.chars(), &[':', '.']);
758 assert_eq!(DelimiterType::ColorSeparator.chars(), &['&', 'H']);
759 }
760
761 #[test]
762 fn field_and_value_separator_matching() {
763 assert!(DelimiterType::FieldSeparator.matches(':'));
764 assert!(!DelimiterType::FieldSeparator.matches(','));
765 assert!(!DelimiterType::FieldSeparator.matches('['));
766
767 assert!(DelimiterType::ValueSeparator.matches(','));
768 assert!(!DelimiterType::ValueSeparator.matches(':'));
769 assert!(!DelimiterType::ValueSeparator.matches('['));
770 }
771
772 #[test]
773 fn boundary_delimiter_matching() {
774 assert!(DelimiterType::SectionBoundary.matches('['));
775 assert!(DelimiterType::SectionBoundary.matches(']'));
776 assert!(!DelimiterType::SectionBoundary.matches('{'));
777
778 assert!(DelimiterType::OverrideBoundary.matches('{'));
779 assert!(DelimiterType::OverrideBoundary.matches('}'));
780 assert!(!DelimiterType::OverrideBoundary.matches('['));
781 }
782
783 #[test]
784 fn line_and_comment_delimiter_matching() {
785 assert!(DelimiterType::CommentMarker.matches(';'));
786 assert!(!DelimiterType::CommentMarker.matches('#'));
787
788 assert!(DelimiterType::LineTerminator.matches('\n'));
789 assert!(DelimiterType::LineTerminator.matches('\r'));
790 assert!(!DelimiterType::LineTerminator.matches('\t'));
791 }
792
793 #[test]
794 fn special_delimiter_matching() {
795 assert!(DelimiterType::DrawingSeparator.matches(' '));
796 assert!(DelimiterType::DrawingSeparator.matches('\t'));
797 assert!(!DelimiterType::DrawingSeparator.matches('\n'));
798
799 assert!(DelimiterType::TimeSeparator.matches(':'));
800 assert!(DelimiterType::TimeSeparator.matches('.'));
801 assert!(!DelimiterType::TimeSeparator.matches(','));
802
803 assert!(DelimiterType::ColorSeparator.matches('&'));
804 assert!(DelimiterType::ColorSeparator.matches('H'));
805 assert!(!DelimiterType::ColorSeparator.matches('#'));
806 }
807
808 #[test]
809 fn token_position_edge_cases() {
810 let pos = TokenPosition::new(100, 50, 25);
811 assert_eq!(pos.offset, 100);
812 assert_eq!(pos.line, 50);
813 assert_eq!(pos.column, 25);
814
815 let default_pos = TokenPosition::default();
817 assert_eq!(default_pos.offset, 0);
818 assert_eq!(default_pos.line, 1);
819 assert_eq!(default_pos.column, 1);
820
821 let start_pos = TokenPosition::start();
823 assert_eq!(start_pos.offset, 0);
824 assert_eq!(start_pos.line, 1);
825 assert_eq!(start_pos.column, 1);
826 }
827
828 #[test]
829 fn token_position_unicode_advance() {
830 let mut pos = TokenPosition::start();
831
832 pos = pos.advance('🎵'); assert_eq!(pos.offset, 4);
835 assert_eq!(pos.line, 1);
836 assert_eq!(pos.column, 2);
837
838 pos = pos.advance('a');
840 assert_eq!(pos.offset, 5);
841 assert_eq!(pos.line, 1);
842 assert_eq!(pos.column, 3);
843
844 pos = pos.advance('\n');
846 assert_eq!(pos.offset, 6);
847 assert_eq!(pos.line, 2);
848 assert_eq!(pos.column, 1);
849 }
850
851 #[test]
852 fn token_position_advance_by_str_edge_cases() {
853 let pos = TokenPosition::start();
854
855 let pos = pos.advance_by_str("");
857 assert_eq!(pos.offset, 0);
858 assert_eq!(pos.line, 1);
859 assert_eq!(pos.column, 1);
860
861 let pos = pos.advance_by_str("\n\n\n");
863 assert_eq!(pos.offset, 3);
864 assert_eq!(pos.line, 4);
865 assert_eq!(pos.column, 1);
866
867 let pos = pos.advance_by_str("hello🎵world\ntest");
869 assert_eq!(pos.offset, 3 + 19); assert_eq!(pos.line, 5);
871 assert_eq!(pos.column, 5); }
873
874 #[test]
875 fn token_unicode_length() {
876 let unicode_token = Token::new(TokenType::Text, "🎵🎶🎤", 1, 1);
878 assert_eq!(unicode_token.len(), 3); assert_eq!(unicode_token.end_column(), 4); let mixed_token = Token::new(TokenType::Text, "hello🎵world", 1, 1);
883 assert_eq!(mixed_token.len(), 11); assert_eq!(mixed_token.end_column(), 12);
885
886 let empty_token = Token::new(TokenType::Text, "", 1, 1);
888 assert_eq!(empty_token.len(), 0);
889 assert_eq!(empty_token.end_column(), 1);
890 }
891
892 #[test]
893 fn token_comprehensive_classification() {
894 let text_token = Token::new(TokenType::Text, "text", 1, 1);
896 assert_eq!(text_token.is_content(), TokenType::Text.is_content());
897 assert_eq!(text_token.is_delimiter(), TokenType::Text.is_delimiter());
898
899 let comma_token = Token::new(TokenType::Comma, ",", 1, 1);
900 assert_eq!(comma_token.is_content(), TokenType::Comma.is_content());
901 assert_eq!(comma_token.is_delimiter(), TokenType::Comma.is_delimiter());
902
903 let whitespace_token = Token::new(TokenType::Whitespace, " ", 1, 1);
904 assert_eq!(
905 whitespace_token.is_whitespace(),
906 matches!(TokenType::Whitespace, TokenType::Whitespace)
907 );
908 }
909
910 #[test]
911 fn token_equality_and_cloning() {
912 let token1 = Token::new(TokenType::Text, "test", 1, 5);
913 let token2 = Token::new(TokenType::Text, "test", 1, 5);
914 let token3 = Token::new(TokenType::Number, "test", 1, 5);
915 let token4 = Token::new(TokenType::Text, "different", 1, 5);
916
917 assert_eq!(token1, token2);
918 assert_ne!(token1, token3);
919 assert_ne!(token1, token4);
920
921 let cloned = token1.clone();
922 assert_eq!(token1, cloned);
923 }
924
925 #[test]
926 fn token_debug_formatting() {
927 let token = Token::new(TokenType::SectionHeader, "[Script Info]", 2, 1);
928 let debug_output = format!("{token:?}");
929 assert!(debug_output.contains("SectionHeader"));
930 assert!(debug_output.contains("[Script Info]"));
931 assert!(debug_output.contains("line: 2"));
932 assert!(debug_output.contains("column: 1"));
933 }
934}