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    /// Returns whether parser recovery synthesized this token with ANTLR's
614    /// `-1:-1` source span.
615    #[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/// Mutable append-only view used by a token source.
740#[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/// A diagnostic buffered by a token source while it was producing tokens.
764#[derive(Clone, Debug, Eq, PartialEq)]
765#[non_exhaustive]
766pub struct TokenSourceError {
767    /// One-based input line where the diagnostic starts.
768    pub line: usize,
769    /// Zero-based column within `line` where the diagnostic starts.
770    pub column: usize,
771    /// Half-open UTF-8 byte span of the offending input, when available.
772    pub span: Option<Range<usize>>,
773    /// ANTLR-compatible diagnostic message without the leading line/column.
774    pub message: String,
775}
776
777impl TokenSourceError {
778    /// Creates a token-source diagnostic at the given input position.
779    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    /// Attaches the resolved half-open UTF-8 byte span.
789    #[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    /// Returns the source buffer once for ownership by the token store.
803    fn source_text(&self) -> Option<Rc<str>> {
804        None
805    }
806
807    /// Returns and clears diagnostics emitted while fetching tokens.
808    fn drain_errors(&mut self) -> Vec<TokenSourceError> {
809        Vec::new()
810    }
811
812    /// Reports a buffered diagnostic through source-owned listeners.
813    ///
814    /// Returns `true` when the source owns diagnostic reporting. The parser
815    /// uses its own listeners as a fallback for token sources that return
816    /// `false`.
817    fn report_error(&self, _error: &TokenSourceError) -> bool {
818        false
819    }
820
821    /// Serializes lexer DFA cache state when the token source exposes one.
822    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
835/// Formats synthetic-token boundaries with ANTLR's `-1` sentinel.
836fn display_token_boundary(value: usize) -> String {
837    if value == usize::MAX {
838        "-1".to_owned()
839    } else {
840        value.to_string()
841    }
842}
843
844/// Escapes token text the way ANTLR's token display format expects.
845fn 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}