1use crate::char_stream::TextInterval;
2use std::fmt;
3use std::ops::Range;
4use std::rc::Rc;
5
6pub const TOKEN_EOF: i32 = -1;
7pub const INVALID_TOKEN_TYPE: i32 = 0;
8pub const DEFAULT_CHANNEL: i32 = 0;
9pub const HIDDEN_CHANNEL: i32 = 1;
10
11pub const MAX_TOKEN_OFFSET: usize = (u32::MAX - 1) as usize;
16
17#[repr(transparent)]
18#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
19pub struct TokenId(u32);
20
21impl TokenId {
22 #[must_use]
23 pub const fn index(self) -> usize {
24 self.0 as usize
25 }
26}
27
28impl TryFrom<usize> for TokenId {
29 type Error = TokenStoreError;
30
31 fn try_from(value: usize) -> Result<Self, Self::Error> {
32 u32::try_from(value)
33 .map(Self)
34 .map_err(|_| TokenStoreError::overflow("index", value, u32::MAX as usize))
35 }
36}
37
38#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub enum TokenChannel {
40 Default,
41 Hidden,
42 Custom(i32),
43}
44
45impl TokenChannel {
46 pub const fn value(self) -> i32 {
47 match self {
48 Self::Default => DEFAULT_CHANNEL,
49 Self::Hidden => HIDDEN_CHANNEL,
50 Self::Custom(channel) => channel,
51 }
52 }
53}
54
55impl From<i32> for TokenChannel {
56 fn from(value: i32) -> Self {
57 match value {
58 DEFAULT_CHANNEL => Self::Default,
59 HIDDEN_CHANNEL => Self::Hidden,
60 other => Self::Custom(other),
61 }
62 }
63}
64
65pub trait Token: fmt::Debug {
66 fn token_id(&self) -> TokenId;
67 fn token_type(&self) -> i32;
68 fn channel(&self) -> i32;
69 fn start(&self) -> usize;
71 fn stop(&self) -> usize;
74 fn line(&self) -> usize;
76 fn column(&self) -> usize;
79 fn text(&self) -> Option<&str>;
85 fn source_name(&self) -> &str;
86
87 fn interval(&self) -> TextInterval {
88 TextInterval::new(self.start(), self.stop())
89 }
90
91 fn start_byte(&self) -> Option<usize>;
93
94 fn stop_byte(&self) -> Option<usize>;
96
97 fn byte_span(&self) -> Option<Range<usize>> {
99 Some(self.start_byte()?..self.stop_byte()?)
100 }
101}
102
103impl<T: Token + ?Sized> Token for &T {
104 fn token_id(&self) -> TokenId {
105 (**self).token_id()
106 }
107
108 fn token_type(&self) -> i32 {
109 (**self).token_type()
110 }
111
112 fn channel(&self) -> i32 {
113 (**self).channel()
114 }
115
116 fn start(&self) -> usize {
117 (**self).start()
118 }
119
120 fn stop(&self) -> usize {
121 (**self).stop()
122 }
123
124 fn line(&self) -> usize {
125 (**self).line()
126 }
127
128 fn column(&self) -> usize {
129 (**self).column()
130 }
131
132 fn text(&self) -> Option<&str> {
133 (**self).text()
134 }
135
136 fn source_name(&self) -> &str {
137 (**self).source_name()
138 }
139
140 fn start_byte(&self) -> Option<usize> {
141 (**self).start_byte()
142 }
143
144 fn stop_byte(&self) -> Option<usize> {
145 (**self).stop_byte()
146 }
147}
148
149#[derive(Clone, Debug)]
154pub struct TokenSpec {
155 pub token_type: i32,
156 pub channel: i32,
157 pub start: usize,
158 pub stop: usize,
159 pub start_byte: usize,
160 pub stop_byte: usize,
161 pub line: usize,
162 pub column: usize,
163 pub text: Option<String>,
164 pub source_backed: bool,
165}
166
167impl TokenSpec {
168 #[must_use]
169 pub fn explicit(token_type: i32, text: impl Into<String>) -> Self {
170 Self {
171 token_type,
172 channel: DEFAULT_CHANNEL,
173 start: 0,
174 stop: 0,
175 start_byte: usize::MAX,
176 stop_byte: usize::MAX,
177 line: 1,
178 column: 0,
179 text: Some(text.into()),
180 source_backed: false,
181 }
182 }
183
184 #[must_use]
185 pub fn eof(index: usize, byte_offset: usize, line: usize, column: usize) -> Self {
186 Self {
187 token_type: TOKEN_EOF,
188 channel: DEFAULT_CHANNEL,
189 start: index,
190 stop: index.checked_sub(1).unwrap_or(usize::MAX),
191 start_byte: byte_offset,
192 stop_byte: byte_offset,
193 line,
194 column,
195 text: Some("<EOF>".to_owned()),
196 source_backed: false,
197 }
198 }
199
200 #[must_use]
201 pub const fn with_channel(mut self, channel: i32) -> Self {
202 self.channel = channel;
203 self
204 }
205
206 #[must_use]
207 pub const fn with_span(mut self, start: usize, stop: usize) -> Self {
212 self.start = start;
213 self.stop = stop;
214 self
215 }
216
217 #[must_use]
218 pub const fn with_byte_span(mut self, start_byte: usize, stop_byte: usize) -> Self {
220 self.start_byte = start_byte;
221 self.stop_byte = stop_byte;
222 self
223 }
224
225 #[must_use]
226 pub const fn with_position(mut self, line: usize, column: usize) -> Self {
227 self.line = line;
228 self.column = column;
229 self
230 }
231}
232
233#[derive(Clone, Debug, Eq, PartialEq)]
234pub struct TokenStoreError(TokenStoreErrorKind);
235
236impl TokenStoreError {
237 const fn overflow(field: &'static str, value: usize, limit: usize) -> Self {
238 Self(TokenStoreErrorKind::Overflow {
239 field,
240 value,
241 limit,
242 })
243 }
244
245 const fn invalid_source_boundary(offset: usize, source_len: usize) -> Self {
246 Self(TokenStoreErrorKind::InvalidSourceBoundary { offset, source_len })
247 }
248
249 pub(crate) const fn invalid_source_output(
250 expected_id: usize,
251 returned_id: usize,
252 appended: usize,
253 ) -> Self {
254 Self(TokenStoreErrorKind::InvalidSourceOutput {
255 expected_id,
256 returned_id,
257 appended,
258 })
259 }
260}
261
262impl fmt::Display for TokenStoreError {
263 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264 match self.0 {
265 TokenStoreErrorKind::Overflow {
266 field,
267 value,
268 limit,
269 } => write!(
270 f,
271 "token {field} {value} exceeds the supported limit {limit}"
272 ),
273 TokenStoreErrorKind::InvalidSourceBoundary { offset, source_len } => write!(
274 f,
275 "token source byte offset {offset} is not a UTF-8 character boundary \
276 for source length {source_len}"
277 ),
278 TokenStoreErrorKind::InvalidSourceOutput {
279 expected_id,
280 returned_id,
281 appended,
282 } => write!(
283 f,
284 "token source must append exactly one token and return ID {expected_id}, \
285 but appended {appended} and returned ID {returned_id}"
286 ),
287 }
288 }
289}
290
291impl std::error::Error for TokenStoreError {}
292
293#[derive(Clone, Debug, Eq, PartialEq)]
294enum TokenStoreErrorKind {
295 Overflow {
296 field: &'static str,
297 value: usize,
298 limit: usize,
299 },
300 InvalidSourceBoundary {
301 offset: usize,
302 source_len: usize,
303 },
304 InvalidSourceOutput {
305 expected_id: usize,
306 returned_id: usize,
307 appended: usize,
308 },
309}
310
311#[derive(Debug)]
313pub struct TokenStore {
314 source: Option<Rc<str>>,
315 source_name: Rc<str>,
316 token_types: Vec<i32>,
317 channels: Vec<i32>,
318 scalar_starts: Vec<u32>,
319 scalar_stops: Vec<u32>,
320 byte_starts: Vec<u32>,
321 byte_stops: Vec<u32>,
322 lines: Vec<u32>,
323 columns: Vec<u32>,
324 source_backed: Vec<bool>,
325 explicit_text: Vec<(TokenId, Rc<str>)>,
326}
327
328impl TokenStore {
329 pub(crate) fn new(source: Option<Rc<str>>, source_name: impl Into<Rc<str>>) -> Self {
330 Self {
331 source,
332 source_name: source_name.into(),
333 token_types: Vec::new(),
334 channels: Vec::new(),
335 scalar_starts: Vec::new(),
336 scalar_stops: Vec::new(),
337 byte_starts: Vec::new(),
338 byte_stops: Vec::new(),
339 lines: Vec::new(),
340 columns: Vec::new(),
341 source_backed: Vec::new(),
342 explicit_text: Vec::new(),
343 }
344 }
345
346 #[must_use]
347 pub const fn len(&self) -> usize {
348 self.token_types.len()
349 }
350
351 #[must_use]
352 pub const fn is_empty(&self) -> bool {
353 self.token_types.is_empty()
354 }
355
356 pub fn iter(&self) -> TokenIter<'_> {
358 self.iter_prefix(self.len())
359 }
360
361 pub(crate) fn iter_prefix(&self, stop: usize) -> TokenIter<'_> {
362 assert!(
363 stop <= self.len(),
364 "token iterator prefix exceeds store length"
365 );
366 TokenIter {
367 store: self,
368 next: 0,
369 stop,
370 }
371 }
372
373 pub(crate) fn push(&mut self, spec: TokenSpec) -> Result<TokenId, TokenStoreError> {
374 let raw_id = u32::try_from(self.len())
375 .map_err(|_| TokenStoreError::overflow("count", self.len(), u32::MAX as usize))?;
376 let id = TokenId(raw_id);
377 let scalar_start = compact_boundary("start offset", spec.start)?;
378 let scalar_stop = compact_boundary("stop offset", spec.stop)?;
379 let byte_start = compact_boundary("start byte", spec.start_byte)?;
380 let byte_stop = compact_boundary("stop byte", spec.stop_byte)?;
381 let line = compact_offset("line", spec.line)?;
382 let column = compact_offset("column", spec.column)?;
383
384 if spec.source_backed {
385 let Some(source) = self.source.as_ref() else {
386 return Err(TokenStoreError::overflow("source text", 1, 0));
387 };
388 if spec.start_byte > spec.stop_byte || spec.stop_byte > source.len() {
389 return Err(TokenStoreError::overflow(
390 "source byte span",
391 spec.stop_byte,
392 source.len(),
393 ));
394 }
395 if !source.is_char_boundary(spec.start_byte) {
396 return Err(TokenStoreError::invalid_source_boundary(
397 spec.start_byte,
398 source.len(),
399 ));
400 }
401 if !source.is_char_boundary(spec.stop_byte) {
402 return Err(TokenStoreError::invalid_source_boundary(
403 spec.stop_byte,
404 source.len(),
405 ));
406 }
407 }
408
409 self.token_types.push(spec.token_type);
410 self.channels.push(spec.channel);
411 self.scalar_starts.push(scalar_start);
412 self.scalar_stops.push(scalar_stop);
413 self.byte_starts.push(byte_start);
414 self.byte_stops.push(byte_stop);
415 self.lines.push(line);
416 self.columns.push(column);
417 self.source_backed.push(spec.source_backed);
418 if let Some(text) = spec.text {
419 self.explicit_text.push((id, Rc::from(text)));
420 }
421 Ok(id)
422 }
423
424 const fn contains(&self, id: TokenId) -> bool {
425 id.index() < self.len()
426 }
427
428 #[must_use]
430 pub fn view(&self, id: TokenId) -> Option<TokenView<'_>> {
431 self.contains(id).then_some(TokenView { store: self, id })
432 }
433
434 #[must_use]
436 pub fn token_type(&self, id: TokenId) -> Option<i32> {
437 self.token_types.get(id.index()).copied()
438 }
439
440 #[must_use]
442 pub fn channel(&self, id: TokenId) -> Option<i32> {
443 self.channels.get(id.index()).copied()
444 }
445
446 #[must_use]
448 pub fn start(&self, id: TokenId) -> Option<usize> {
449 self.scalar_starts
450 .get(id.index())
451 .copied()
452 .map(expand_boundary)
453 }
454
455 #[must_use]
457 pub fn stop(&self, id: TokenId) -> Option<usize> {
458 self.scalar_stops
459 .get(id.index())
460 .copied()
461 .map(expand_boundary)
462 }
463
464 #[must_use]
466 pub fn line(&self, id: TokenId) -> Option<usize> {
467 self.lines.get(id.index()).map(|line| *line as usize)
468 }
469
470 #[must_use]
472 pub fn column(&self, id: TokenId) -> Option<usize> {
473 self.columns.get(id.index()).map(|column| *column as usize)
474 }
475
476 #[must_use]
481 pub fn start_byte(&self, id: TokenId) -> Option<usize> {
482 self.byte_starts
483 .get(id.index())
484 .copied()
485 .and_then(expand_byte_boundary)
486 }
487
488 #[must_use]
493 pub fn stop_byte(&self, id: TokenId) -> Option<usize> {
494 self.byte_stops
495 .get(id.index())
496 .copied()
497 .and_then(expand_byte_boundary)
498 }
499
500 #[must_use]
502 pub fn byte_span(&self, id: TokenId) -> Option<Range<usize>> {
503 Some(self.start_byte(id)?..self.stop_byte(id)?)
504 }
505
506 fn explicit_text(&self, id: TokenId) -> Option<&str> {
507 self.explicit_text
508 .binary_search_by_key(&id, |(token_id, _)| *token_id)
509 .ok()
510 .map(|index| self.explicit_text[index].1.as_ref())
511 }
512
513 #[must_use]
515 pub fn text(&self, id: TokenId) -> Option<&str> {
516 if let Some(text) = self.explicit_text(id) {
517 return Some(text);
518 }
519 if !self.source_backed.get(id.index()).copied().unwrap_or(false) {
520 return None;
521 }
522 let source = self.source.as_deref()?;
523 let start = self.byte_starts[id.index()] as usize;
524 let stop = self.byte_stops[id.index()] as usize;
525 source.get(start..stop)
526 }
527}
528
529impl<'a> IntoIterator for &'a TokenStore {
530 type Item = TokenView<'a>;
531 type IntoIter = TokenIter<'a>;
532
533 fn into_iter(self) -> Self::IntoIter {
534 self.iter()
535 }
536}
537
538#[derive(Debug)]
540pub struct TokenIter<'a> {
541 store: &'a TokenStore,
542 next: usize,
543 stop: usize,
544}
545
546impl<'a> Iterator for TokenIter<'a> {
547 type Item = TokenView<'a>;
548
549 fn next(&mut self) -> Option<Self::Item> {
550 if self.next >= self.stop {
551 return None;
552 }
553 let id = TokenId::try_from(self.next).ok()?;
554 self.next += 1;
555 self.store.view(id)
556 }
557
558 fn size_hint(&self) -> (usize, Option<usize>) {
559 let remaining = self.stop - self.next;
560 (remaining, Some(remaining))
561 }
562}
563
564impl DoubleEndedIterator for TokenIter<'_> {
565 fn next_back(&mut self) -> Option<Self::Item> {
566 if self.next >= self.stop {
567 return None;
568 }
569 self.stop -= 1;
570 let id = TokenId::try_from(self.stop).ok()?;
571 self.store.view(id)
572 }
573}
574
575impl ExactSizeIterator for TokenIter<'_> {}
576
577const fn compact_boundary(field: &'static str, value: usize) -> Result<u32, TokenStoreError> {
578 if value == usize::MAX {
579 return Ok(u32::MAX);
580 }
581 compact_offset(field, value)
582}
583
584const fn compact_offset(field: &'static str, value: usize) -> Result<u32, TokenStoreError> {
585 if value > MAX_TOKEN_OFFSET {
586 return Err(TokenStoreError::overflow(field, value, MAX_TOKEN_OFFSET));
587 }
588 Ok(value as u32)
589}
590
591#[derive(Clone, Copy)]
593pub struct TokenView<'a> {
594 store: &'a TokenStore,
595 id: TokenId,
596}
597
598impl<'a> TokenView<'a> {
599 #[must_use]
601 #[allow(clippy::trivially_copy_pass_by_ref)]
602 pub fn text(&self) -> Option<&'a str> {
603 self.store.text(self.id)
604 }
605
606 #[must_use]
608 #[allow(clippy::trivially_copy_pass_by_ref)]
609 pub fn text_or_empty(&self) -> &'a str {
610 self.text().unwrap_or("")
611 }
612
613 #[must_use]
616 #[allow(clippy::trivially_copy_pass_by_ref)]
617 pub fn is_synthetic(&self) -> bool {
618 self.start() == usize::MAX && self.stop() == usize::MAX
619 }
620}
621
622impl fmt::Debug for TokenView<'_> {
623 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
624 f.debug_struct("TokenView")
625 .field("id", &self.id)
626 .field("token_type", &self.token_type())
627 .field("channel", &self.channel())
628 .field("text", &self.text())
629 .finish()
630 }
631}
632
633impl PartialEq for TokenView<'_> {
634 fn eq(&self, other: &Self) -> bool {
635 self.id == other.id
636 && self.token_type() == other.token_type()
637 && self.channel() == other.channel()
638 && self.start() == other.start()
639 && self.stop() == other.stop()
640 && self.line() == other.line()
641 && self.column() == other.column()
642 && self.text() == other.text()
643 && self.source_name() == other.source_name()
644 }
645}
646
647impl Eq for TokenView<'_> {}
648
649impl Token for TokenView<'_> {
650 fn token_id(&self) -> TokenId {
651 self.id
652 }
653
654 fn token_type(&self) -> i32 {
655 self.store.token_types[self.id.index()]
656 }
657
658 fn channel(&self) -> i32 {
659 self.store.channels[self.id.index()]
660 }
661
662 fn start(&self) -> usize {
663 expand_boundary(self.store.scalar_starts[self.id.index()])
664 }
665
666 fn stop(&self) -> usize {
667 expand_boundary(self.store.scalar_stops[self.id.index()])
668 }
669
670 fn line(&self) -> usize {
671 self.store.lines[self.id.index()] as usize
672 }
673
674 fn column(&self) -> usize {
675 self.store.columns[self.id.index()] as usize
676 }
677
678 fn text(&self) -> Option<&str> {
679 self.store.text(self.id)
680 }
681
682 fn source_name(&self) -> &str {
683 self.store.source_name.as_ref()
684 }
685
686 fn start_byte(&self) -> Option<usize> {
687 self.store.start_byte(self.id)
688 }
689
690 fn stop_byte(&self) -> Option<usize> {
691 self.store.stop_byte(self.id)
692 }
693}
694
695impl fmt::Display for TokenView<'_> {
696 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
697 let channel = if self.channel() == DEFAULT_CHANNEL {
698 String::new()
699 } else {
700 format!(",channel={}", self.channel())
701 };
702 write!(
703 f,
704 "[@{},{}:{}='{}',<{}>{},{}:{}]",
705 display_token_index(*self),
706 display_token_boundary(self.start()),
707 display_token_boundary(self.stop()),
708 display_text(self.text_or_empty()),
709 self.token_type(),
710 channel,
711 self.line(),
712 self.column()
713 )
714 }
715}
716
717impl AsRef<str> for TokenView<'_> {
718 fn as_ref(&self) -> &str {
719 self.text_or_empty()
720 }
721}
722
723const fn expand_boundary(value: u32) -> usize {
724 if value == u32::MAX {
725 usize::MAX
726 } else {
727 value as usize
728 }
729}
730
731const fn expand_byte_boundary(value: u32) -> Option<usize> {
732 if value == u32::MAX {
733 None
734 } else {
735 Some(value as usize)
736 }
737}
738
739#[derive(Debug)]
741pub struct TokenSink<'a> {
742 store: &'a mut TokenStore,
743}
744
745impl<'a> TokenSink<'a> {
746 pub(crate) const fn new(store: &'a mut TokenStore) -> Self {
747 Self { store }
748 }
749
750 pub fn push(&mut self, spec: TokenSpec) -> Result<TokenId, TokenStoreError> {
751 self.store.push(spec)
752 }
753
754 pub fn view(&self, id: TokenId) -> Option<TokenView<'_>> {
755 self.store.view(id)
756 }
757
758 pub(crate) const fn token_count(&self) -> usize {
759 self.store.len()
760 }
761}
762
763#[derive(Clone, Debug, Eq, PartialEq)]
765#[non_exhaustive]
766pub struct TokenSourceError {
767 pub line: usize,
769 pub column: usize,
771 pub span: Option<Range<usize>>,
773 pub message: String,
775}
776
777impl TokenSourceError {
778 pub fn new(line: usize, column: usize, message: impl Into<String>) -> Self {
780 Self {
781 line,
782 column,
783 span: None,
784 message: message.into(),
785 }
786 }
787
788 #[must_use]
790 pub const fn with_span(mut self, span: Range<usize>) -> Self {
791 self.span = Some(span);
792 self
793 }
794}
795
796pub trait TokenSource {
797 fn next_token(&mut self, sink: &mut TokenSink<'_>) -> Result<TokenId, TokenStoreError>;
798 fn line(&self) -> usize;
799 fn column(&self) -> usize;
800 fn source_name(&self) -> &str;
801
802 fn source_text(&self) -> Option<Rc<str>> {
804 None
805 }
806
807 fn drain_errors(&mut self) -> Vec<TokenSourceError> {
809 Vec::new()
810 }
811
812 fn report_error(&self, _error: &TokenSourceError) -> bool {
818 false
819 }
820
821 fn lexer_dfa_string(&self) -> String {
823 String::new()
824 }
825}
826
827fn display_token_index(token: TokenView<'_>) -> String {
828 if token.is_synthetic() {
829 "-1".to_owned()
830 } else {
831 token.token_id().index().to_string()
832 }
833}
834
835fn display_token_boundary(value: usize) -> String {
837 if value == usize::MAX {
838 "-1".to_owned()
839 } else {
840 value.to_string()
841 }
842}
843
844fn display_text(text: &str) -> String {
846 let mut out = String::new();
847 for ch in text.chars() {
848 match ch {
849 '\n' => out.push_str("\\n"),
850 '\r' => out.push_str("\\r"),
851 '\t' => out.push_str("\\t"),
852 other => out.push(other),
853 }
854 }
855 out
856}
857
858#[cfg(test)]
859mod tests {
860 use super::*;
861
862 fn one_token(spec: TokenSpec) -> TokenStore {
863 let mut store = TokenStore::new(None, "");
864 store.push(spec).expect("test token should fit");
865 store
866 }
867
868 #[test]
869 fn token_view_display_matches_antlr_shape() {
870 let store = one_token(
871 TokenSpec::explicit(7, "abc")
872 .with_span(2, 4)
873 .with_position(3, 9),
874 );
875 assert!(!store.view(TokenId(0)).expect("token").is_synthetic());
876 assert_eq!(
877 store.view(TokenId(0)).expect("token").to_string(),
878 "[@0,2:4='abc',<7>,3:9]"
879 );
880 }
881
882 #[test]
883 fn synthetic_token_display_uses_antlr_negative_index() {
884 let store = one_token(
885 TokenSpec::explicit(7, "<missing X>")
886 .with_span(usize::MAX, usize::MAX)
887 .with_position(3, 9),
888 );
889 assert!(store.view(TokenId(0)).expect("token").is_synthetic());
890 assert_eq!(
891 store.view(TokenId(0)).expect("token").to_string(),
892 "[@-1,-1:-1='<missing X>',<7>,3:9]"
893 );
894 }
895
896 #[test]
897 fn eof_at_the_start_of_input_is_not_synthetic() {
898 let store = one_token(TokenSpec::eof(0, 0, 1, 0));
899 assert!(!store.view(TokenId(0)).expect("EOF").is_synthetic());
900 }
901
902 #[test]
903 fn source_backed_token_exposes_utf8_byte_span() {
904 let mut store = TokenStore::new(Some(Rc::from("éβz")), "");
905 let id = store
906 .push(TokenSpec {
907 token_type: 1,
908 channel: DEFAULT_CHANNEL,
909 start: 1,
910 stop: 1,
911 start_byte: 2,
912 stop_byte: 4,
913 line: 1,
914 column: 1,
915 text: None,
916 source_backed: true,
917 })
918 .expect("token should fit");
919 let token = TokenView { store: &store, id };
920
921 assert_eq!(token.start(), 1);
922 assert_eq!(token.stop(), 1);
923 assert_eq!(token.start_byte(), Some(2));
924 assert_eq!(token.stop_byte(), Some(4));
925 assert_eq!(token.byte_span(), Some(2..4));
926 assert_eq!(store.byte_span(id), Some(2..4));
927 assert_eq!(token.text(), Some("β"));
928 }
929
930 #[test]
931 fn scalar_span_without_byte_offsets_remains_unknown() {
932 let store = one_token(TokenSpec::explicit(1, "β").with_span(0, 0));
933 let token = store.view(TokenId(0)).expect("token");
934
935 assert_eq!((token.start(), token.stop()), (0, 0));
936 assert_eq!(token.start_byte(), None);
937 assert_eq!(token.stop_byte(), None);
938 assert_eq!(token.byte_span(), None);
939 assert_eq!(store.byte_span(TokenId(0)), None);
940 }
941
942 #[test]
943 fn source_backed_token_rejects_non_utf8_boundaries() {
944 for (start_byte, stop_byte) in [(1, 2), (0, 1)] {
945 let mut store = TokenStore::new(Some(Rc::from("éz")), "");
946 let error = store
947 .push(TokenSpec {
948 token_type: 1,
949 channel: DEFAULT_CHANNEL,
950 start: 0,
951 stop: 0,
952 start_byte,
953 stop_byte,
954 line: 1,
955 column: 0,
956 text: None,
957 source_backed: true,
958 })
959 .expect_err("spans that split UTF-8 code points must fail");
960
961 assert!(error.to_string().contains("UTF-8 character boundary"));
962 assert!(store.is_empty());
963 }
964 }
965
966 #[test]
967 fn overlarge_offset_is_rejected() {
968 let mut store = TokenStore::new(None, "");
969 let error = store
970 .push(TokenSpec::explicit(1, "x").with_span(MAX_TOKEN_OFFSET + 1, 0))
971 .expect_err("overlarge offsets must fail");
972 assert!(error.to_string().contains("supported limit"));
973 }
974
975 #[test]
976 fn token_store_iterates_all_records_in_id_order() {
977 let mut store = TokenStore::new(None, "iterator-test");
978 for spec in [
979 TokenSpec::explicit(1, "a"),
980 TokenSpec::explicit(2, " comment").with_channel(HIDDEN_CHANNEL),
981 TokenSpec::eof(9, 9, 1, 9),
982 ] {
983 store.push(spec).expect("test token should fit");
984 }
985
986 let mut iter = store.iter();
987 assert_eq!(iter.len(), 3);
988 assert_eq!(iter.next().and_then(|token| token.text()), Some("a"));
989 assert_eq!(
990 iter.next_back().map(|token| token.token_type()),
991 Some(TOKEN_EOF)
992 );
993 assert_eq!(iter.len(), 1);
994
995 assert_eq!(
996 (&store)
997 .into_iter()
998 .map(|token| (token.token_id().index(), token.channel()))
999 .collect::<Vec<_>>(),
1000 [
1001 (0, DEFAULT_CHANNEL),
1002 (1, HIDDEN_CHANNEL),
1003 (2, DEFAULT_CHANNEL)
1004 ]
1005 );
1006 }
1007
1008 #[test]
1009 fn token_view_text_matches_token_trait_semantics() {
1010 fn generic_text(token: &impl Token) -> Option<&str> {
1011 token.text()
1012 }
1013
1014 let store = one_token(TokenSpec {
1015 token_type: 1,
1016 channel: DEFAULT_CHANNEL,
1017 start: 0,
1018 stop: 0,
1019 start_byte: 0,
1020 stop_byte: 0,
1021 line: 1,
1022 column: 0,
1023 text: None,
1024 source_backed: false,
1025 });
1026 let token = store.view(TokenId(0)).expect("token");
1027
1028 assert_eq!(token.text(), None);
1029 assert_eq!(generic_text(&token), None);
1030 assert_eq!(token.text_or_empty(), "");
1031 }
1032}