Skip to main content

antlr4_runtime/
token.rs

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
11/// Largest source or location offset accepted by the compact token store.
12///
13/// `u32::MAX` is reserved for unknown and ANTLR synthetic `-1` source
14/// boundaries.
15pub 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    /// Zero-based absolute start index measured in Unicode scalar values.
70    fn start(&self) -> usize;
71    /// Zero-based absolute inclusive stop index measured in Unicode scalar
72    /// values.
73    fn stop(&self) -> usize;
74    /// One-based source line where the token starts.
75    fn line(&self) -> usize;
76    /// Zero-based source column where the token starts, measured in Unicode
77    /// scalar values from the start of `line`.
78    fn column(&self) -> usize;
79    /// Returns the token's explicit or source-backed text.
80    ///
81    /// [`TokenView::text`] has the same return type and semantics. Use
82    /// [`TokenView::text_or_empty`] only when missing text should intentionally
83    /// be treated as an empty string.
84    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    /// Zero-based absolute start offset measured in UTF-8 bytes, when available.
92    fn start_byte(&self) -> Option<usize>;
93
94    /// Zero-based exclusive end offset measured in UTF-8 bytes, when available.
95    fn stop_byte(&self) -> Option<usize>;
96
97    /// Zero-based UTF-8 byte span for the token text, when available.
98    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/// The fields emitted for one token.
150///
151/// This is transient sink input, not an owned token representation. Source
152/// text and the source name live once in [`TokenStore`].
153#[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    /// Sets the inclusive Unicode-scalar span without inferring byte offsets.
208    ///
209    /// Call [`Self::with_byte_span`] separately when the token source can
210    /// resolve exact UTF-8 byte boundaries.
211    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    /// Sets the half-open UTF-8 byte span resolved by the token source.
219    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/// Canonical compact storage for every token associated with one token stream.
312#[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    /// Iterates borrowing views of every stored token in [`TokenId`] order.
357    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    /// Returns a borrowing view of one token record.
429    #[must_use]
430    pub fn view(&self, id: TokenId) -> Option<TokenView<'_>> {
431        self.contains(id).then_some(TokenView { store: self, id })
432    }
433
434    /// Returns the token type for `id`.
435    #[must_use]
436    pub fn token_type(&self, id: TokenId) -> Option<i32> {
437        self.token_types.get(id.index()).copied()
438    }
439
440    /// Returns the token channel for `id`.
441    #[must_use]
442    pub fn channel(&self, id: TokenId) -> Option<i32> {
443        self.channels.get(id.index()).copied()
444    }
445
446    /// Returns the token's zero-based scalar start offset.
447    #[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    /// Returns the token's zero-based inclusive scalar stop offset.
456    #[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    /// Returns the token's one-based source line.
465    #[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    /// Returns the token's zero-based source column.
471    #[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    /// Returns the token's zero-based UTF-8 byte start offset.
477    ///
478    /// Returns `None` when `id` is absent or its token source did not provide
479    /// an exact byte offset.
480    #[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    /// Returns the token's zero-based exclusive UTF-8 byte stop offset.
489    ///
490    /// Returns `None` when `id` is absent or its token source did not provide
491    /// an exact byte offset.
492    #[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    /// Returns the token's half-open UTF-8 byte span, when available.
501    #[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    /// Returns explicit or source-backed text for `id`.
514    #[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/// Iterator over borrowing views of a token store.
539#[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/// Borrowing public view of one canonical token-store record.
592#[derive(Clone, Copy)]
593pub struct TokenView<'a> {
594    store: &'a TokenStore,
595    id: TokenId,
596}
597
598impl<'a> TokenView<'a> {
599    /// Returns the token's explicit or source-backed text.
600    #[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    /// Returns the token's text, or an empty string when no text exists.
607    #[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/// Mutable append-only view used by a token source.
732#[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/// A diagnostic buffered by a token source while it was producing tokens.
756#[derive(Clone, Debug, Eq, PartialEq)]
757#[non_exhaustive]
758pub struct TokenSourceError {
759    /// One-based input line where the diagnostic starts.
760    pub line: usize,
761    /// Zero-based column within `line` where the diagnostic starts.
762    pub column: usize,
763    /// Half-open UTF-8 byte span of the offending input, when available.
764    pub span: Option<Range<usize>>,
765    /// ANTLR-compatible diagnostic message without the leading line/column.
766    pub message: String,
767}
768
769impl TokenSourceError {
770    /// Creates a token-source diagnostic at the given input position.
771    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    /// Attaches the resolved half-open UTF-8 byte span.
781    #[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    /// Returns the source buffer once for ownership by the token store.
795    fn source_text(&self) -> Option<Rc<str>> {
796        None
797    }
798
799    /// Returns and clears diagnostics emitted while fetching tokens.
800    fn drain_errors(&mut self) -> Vec<TokenSourceError> {
801        Vec::new()
802    }
803
804    /// Reports a buffered diagnostic through source-owned listeners.
805    ///
806    /// Returns `true` when the source owns diagnostic reporting. The parser
807    /// uses its own listeners as a fallback for token sources that return
808    /// `false`.
809    fn report_error(&self, _error: &TokenSourceError) -> bool {
810        false
811    }
812
813    /// Serializes lexer DFA cache state when the token source exposes one.
814    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
827/// Formats synthetic-token boundaries with ANTLR's `-1` sentinel.
828fn display_token_boundary(value: usize) -> String {
829    if value == usize::MAX {
830        "-1".to_owned()
831    } else {
832        value.to_string()
833    }
834}
835
836/// Escapes token text the way ANTLR's token display format expects.
837fn 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}