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