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
614impl fmt::Debug for TokenView<'_> {
615 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
616 f.debug_struct("TokenView")
617 .field("id", &self.id)
618 .field("token_type", &self.token_type())
619 .field("channel", &self.channel())
620 .field("text", &self.text())
621 .finish()
622 }
623}
624
625impl PartialEq for TokenView<'_> {
626 fn eq(&self, other: &Self) -> bool {
627 self.id == other.id
628 && self.token_type() == other.token_type()
629 && self.channel() == other.channel()
630 && self.start() == other.start()
631 && self.stop() == other.stop()
632 && self.line() == other.line()
633 && self.column() == other.column()
634 && self.text() == other.text()
635 && self.source_name() == other.source_name()
636 }
637}
638
639impl Eq for TokenView<'_> {}
640
641impl Token for TokenView<'_> {
642 fn token_id(&self) -> TokenId {
643 self.id
644 }
645
646 fn token_type(&self) -> i32 {
647 self.store.token_types[self.id.index()]
648 }
649
650 fn channel(&self) -> i32 {
651 self.store.channels[self.id.index()]
652 }
653
654 fn start(&self) -> usize {
655 expand_boundary(self.store.scalar_starts[self.id.index()])
656 }
657
658 fn stop(&self) -> usize {
659 expand_boundary(self.store.scalar_stops[self.id.index()])
660 }
661
662 fn line(&self) -> usize {
663 self.store.lines[self.id.index()] as usize
664 }
665
666 fn column(&self) -> usize {
667 self.store.columns[self.id.index()] as usize
668 }
669
670 fn text(&self) -> Option<&str> {
671 self.store.text(self.id)
672 }
673
674 fn source_name(&self) -> &str {
675 self.store.source_name.as_ref()
676 }
677
678 fn start_byte(&self) -> Option<usize> {
679 self.store.start_byte(self.id)
680 }
681
682 fn stop_byte(&self) -> Option<usize> {
683 self.store.stop_byte(self.id)
684 }
685}
686
687impl fmt::Display for TokenView<'_> {
688 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
689 let channel = if self.channel() == DEFAULT_CHANNEL {
690 String::new()
691 } else {
692 format!(",channel={}", self.channel())
693 };
694 write!(
695 f,
696 "[@{},{}:{}='{}',<{}>{},{}:{}]",
697 display_token_index(self),
698 display_token_boundary(self.start()),
699 display_token_boundary(self.stop()),
700 display_text(self.text_or_empty()),
701 self.token_type(),
702 channel,
703 self.line(),
704 self.column()
705 )
706 }
707}
708
709impl AsRef<str> for TokenView<'_> {
710 fn as_ref(&self) -> &str {
711 self.text_or_empty()
712 }
713}
714
715const fn expand_boundary(value: u32) -> usize {
716 if value == u32::MAX {
717 usize::MAX
718 } else {
719 value as usize
720 }
721}
722
723const fn expand_byte_boundary(value: u32) -> Option<usize> {
724 if value == u32::MAX {
725 None
726 } else {
727 Some(value as usize)
728 }
729}
730
731#[derive(Debug)]
733pub struct TokenSink<'a> {
734 store: &'a mut TokenStore,
735}
736
737impl<'a> TokenSink<'a> {
738 pub(crate) const fn new(store: &'a mut TokenStore) -> Self {
739 Self { store }
740 }
741
742 pub fn push(&mut self, spec: TokenSpec) -> Result<TokenId, TokenStoreError> {
743 self.store.push(spec)
744 }
745
746 pub fn view(&self, id: TokenId) -> Option<TokenView<'_>> {
747 self.store.view(id)
748 }
749
750 pub(crate) const fn token_count(&self) -> usize {
751 self.store.len()
752 }
753}
754
755#[derive(Clone, Debug, Eq, PartialEq)]
757#[non_exhaustive]
758pub struct TokenSourceError {
759 pub line: usize,
761 pub column: usize,
763 pub span: Option<Range<usize>>,
765 pub message: String,
767}
768
769impl TokenSourceError {
770 pub fn new(line: usize, column: usize, message: impl Into<String>) -> Self {
772 Self {
773 line,
774 column,
775 span: None,
776 message: message.into(),
777 }
778 }
779
780 #[must_use]
782 pub const fn with_span(mut self, span: Range<usize>) -> Self {
783 self.span = Some(span);
784 self
785 }
786}
787
788pub trait TokenSource {
789 fn next_token(&mut self, sink: &mut TokenSink<'_>) -> Result<TokenId, TokenStoreError>;
790 fn line(&self) -> usize;
791 fn column(&self) -> usize;
792 fn source_name(&self) -> &str;
793
794 fn source_text(&self) -> Option<Rc<str>> {
796 None
797 }
798
799 fn drain_errors(&mut self) -> Vec<TokenSourceError> {
801 Vec::new()
802 }
803
804 fn report_error(&self, _error: &TokenSourceError) -> bool {
810 false
811 }
812
813 fn lexer_dfa_string(&self) -> String {
815 String::new()
816 }
817}
818
819fn display_token_index(token: &impl Token) -> String {
820 if token.start() == usize::MAX && token.stop() == usize::MAX {
821 "-1".to_owned()
822 } else {
823 token.token_id().index().to_string()
824 }
825}
826
827fn display_token_boundary(value: usize) -> String {
829 if value == usize::MAX {
830 "-1".to_owned()
831 } else {
832 value.to_string()
833 }
834}
835
836fn display_text(text: &str) -> String {
838 let mut out = String::new();
839 for ch in text.chars() {
840 match ch {
841 '\n' => out.push_str("\\n"),
842 '\r' => out.push_str("\\r"),
843 '\t' => out.push_str("\\t"),
844 other => out.push(other),
845 }
846 }
847 out
848}
849
850#[cfg(test)]
851mod tests {
852 use super::*;
853
854 fn one_token(spec: TokenSpec) -> TokenStore {
855 let mut store = TokenStore::new(None, "");
856 store.push(spec).expect("test token should fit");
857 store
858 }
859
860 #[test]
861 fn token_view_display_matches_antlr_shape() {
862 let store = one_token(
863 TokenSpec::explicit(7, "abc")
864 .with_span(2, 4)
865 .with_position(3, 9),
866 );
867 assert_eq!(
868 store.view(TokenId(0)).expect("token").to_string(),
869 "[@0,2:4='abc',<7>,3:9]"
870 );
871 }
872
873 #[test]
874 fn synthetic_token_display_uses_antlr_negative_index() {
875 let store = one_token(
876 TokenSpec::explicit(7, "<missing X>")
877 .with_span(usize::MAX, usize::MAX)
878 .with_position(3, 9),
879 );
880 assert_eq!(
881 store.view(TokenId(0)).expect("token").to_string(),
882 "[@-1,-1:-1='<missing X>',<7>,3:9]"
883 );
884 }
885
886 #[test]
887 fn source_backed_token_exposes_utf8_byte_span() {
888 let mut store = TokenStore::new(Some(Rc::from("éβz")), "");
889 let id = store
890 .push(TokenSpec {
891 token_type: 1,
892 channel: DEFAULT_CHANNEL,
893 start: 1,
894 stop: 1,
895 start_byte: 2,
896 stop_byte: 4,
897 line: 1,
898 column: 1,
899 text: None,
900 source_backed: true,
901 })
902 .expect("token should fit");
903 let token = TokenView { store: &store, id };
904
905 assert_eq!(token.start(), 1);
906 assert_eq!(token.stop(), 1);
907 assert_eq!(token.start_byte(), Some(2));
908 assert_eq!(token.stop_byte(), Some(4));
909 assert_eq!(token.byte_span(), Some(2..4));
910 assert_eq!(store.byte_span(id), Some(2..4));
911 assert_eq!(token.text(), Some("β"));
912 }
913
914 #[test]
915 fn scalar_span_without_byte_offsets_remains_unknown() {
916 let store = one_token(TokenSpec::explicit(1, "β").with_span(0, 0));
917 let token = store.view(TokenId(0)).expect("token");
918
919 assert_eq!((token.start(), token.stop()), (0, 0));
920 assert_eq!(token.start_byte(), None);
921 assert_eq!(token.stop_byte(), None);
922 assert_eq!(token.byte_span(), None);
923 assert_eq!(store.byte_span(TokenId(0)), None);
924 }
925
926 #[test]
927 fn source_backed_token_rejects_non_utf8_boundaries() {
928 for (start_byte, stop_byte) in [(1, 2), (0, 1)] {
929 let mut store = TokenStore::new(Some(Rc::from("éz")), "");
930 let error = store
931 .push(TokenSpec {
932 token_type: 1,
933 channel: DEFAULT_CHANNEL,
934 start: 0,
935 stop: 0,
936 start_byte,
937 stop_byte,
938 line: 1,
939 column: 0,
940 text: None,
941 source_backed: true,
942 })
943 .expect_err("spans that split UTF-8 code points must fail");
944
945 assert!(error.to_string().contains("UTF-8 character boundary"));
946 assert!(store.is_empty());
947 }
948 }
949
950 #[test]
951 fn overlarge_offset_is_rejected() {
952 let mut store = TokenStore::new(None, "");
953 let error = store
954 .push(TokenSpec::explicit(1, "x").with_span(MAX_TOKEN_OFFSET + 1, 0))
955 .expect_err("overlarge offsets must fail");
956 assert!(error.to_string().contains("supported limit"));
957 }
958
959 #[test]
960 fn token_store_iterates_all_records_in_id_order() {
961 let mut store = TokenStore::new(None, "iterator-test");
962 for spec in [
963 TokenSpec::explicit(1, "a"),
964 TokenSpec::explicit(2, " comment").with_channel(HIDDEN_CHANNEL),
965 TokenSpec::eof(9, 9, 1, 9),
966 ] {
967 store.push(spec).expect("test token should fit");
968 }
969
970 let mut iter = store.iter();
971 assert_eq!(iter.len(), 3);
972 assert_eq!(iter.next().and_then(|token| token.text()), Some("a"));
973 assert_eq!(
974 iter.next_back().map(|token| token.token_type()),
975 Some(TOKEN_EOF)
976 );
977 assert_eq!(iter.len(), 1);
978
979 assert_eq!(
980 (&store)
981 .into_iter()
982 .map(|token| (token.token_id().index(), token.channel()))
983 .collect::<Vec<_>>(),
984 [
985 (0, DEFAULT_CHANNEL),
986 (1, HIDDEN_CHANNEL),
987 (2, DEFAULT_CHANNEL)
988 ]
989 );
990 }
991
992 #[test]
993 fn token_view_text_matches_token_trait_semantics() {
994 fn generic_text(token: &impl Token) -> Option<&str> {
995 token.text()
996 }
997
998 let store = one_token(TokenSpec {
999 token_type: 1,
1000 channel: DEFAULT_CHANNEL,
1001 start: 0,
1002 stop: 0,
1003 start_byte: 0,
1004 stop_byte: 0,
1005 line: 1,
1006 column: 0,
1007 text: None,
1008 source_backed: false,
1009 });
1010 let token = store.view(TokenId(0)).expect("token");
1011
1012 assert_eq!(token.text(), None);
1013 assert_eq!(generic_text(&token), None);
1014 assert_eq!(token.text_or_empty(), "");
1015 }
1016}