Skip to main content

antlr4_runtime/
token.rs

1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright (c) 2026 Konstantin Vyatkin
3use 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
13/// Largest source or location offset accepted by the compact token store.
14///
15/// `u32::MAX` is reserved for unknown and ANTLR synthetic `-1` source
16/// boundaries.
17pub 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    /// Zero-based absolute start index measured in Unicode scalar values.
72    fn start(&self) -> usize;
73    /// Zero-based absolute inclusive stop index measured in Unicode scalar
74    /// values.
75    fn stop(&self) -> usize;
76    /// One-based source line where the token starts.
77    fn line(&self) -> usize;
78    /// Zero-based source column where the token starts, measured in Unicode
79    /// scalar values from the start of `line`.
80    fn column(&self) -> usize;
81    /// Returns the token's explicit or source-backed text.
82    ///
83    /// [`TokenView::text`] has the same return type and semantics. Use
84    /// [`TokenView::text_or_empty`] only when missing text should intentionally
85    /// be treated as an empty string.
86    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    /// Zero-based absolute start offset measured in UTF-8 bytes, when available.
94    fn start_byte(&self) -> Option<usize>;
95
96    /// Zero-based exclusive end offset measured in UTF-8 bytes, when available.
97    fn stop_byte(&self) -> Option<usize>;
98
99    /// Zero-based UTF-8 byte span for the token text, when available.
100    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/// The fields emitted for one token.
152///
153/// This is transient sink input, not an owned token representation. Source
154/// text and the source name live once in [`TokenStore`].
155#[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    /// Sets the inclusive Unicode-scalar span without inferring byte offsets.
210    ///
211    /// Call [`Self::with_byte_span`] separately when the token source can
212    /// resolve exact UTF-8 byte boundaries.
213    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    /// Sets the half-open UTF-8 byte span resolved by the token source.
221    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/// Canonical compact storage for every token associated with one token stream.
314#[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    /// Iterates borrowing views of every stored token in [`TokenId`] order.
359    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    /// Returns a borrowing view of one token record.
431    #[must_use]
432    pub fn view(&self, id: TokenId) -> Option<TokenView<'_>> {
433        self.contains(id).then_some(TokenView { store: self, id })
434    }
435
436    /// Returns the token type for `id`.
437    #[must_use]
438    pub fn token_type(&self, id: TokenId) -> Option<i32> {
439        self.token_types.get(id.index()).copied()
440    }
441
442    /// Returns the token channel for `id`.
443    #[must_use]
444    pub fn channel(&self, id: TokenId) -> Option<i32> {
445        self.channels.get(id.index()).copied()
446    }
447
448    /// Returns the token's zero-based scalar start offset.
449    #[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    /// Returns the token's zero-based inclusive scalar stop offset.
458    #[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    /// Returns the token's one-based source line.
467    #[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    /// Returns the token's zero-based source column.
473    #[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    /// Returns the token's zero-based UTF-8 byte start offset.
479    ///
480    /// Returns `None` when `id` is absent or its token source did not provide
481    /// an exact byte offset.
482    #[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    /// Returns the token's zero-based exclusive UTF-8 byte stop offset.
491    ///
492    /// Returns `None` when `id` is absent or its token source did not provide
493    /// an exact byte offset.
494    #[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    /// Returns the token's half-open UTF-8 byte span, when available.
503    #[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    /// Returns explicit or source-backed text for `id`.
516    #[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/// Iterator over borrowing views of a token store.
541#[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/// Borrowing public view of one canonical token-store record.
594#[derive(Clone, Copy)]
595pub struct TokenView<'a> {
596    store: &'a TokenStore,
597    id: TokenId,
598}
599
600impl<'a> TokenView<'a> {
601    /// Returns the token's explicit or source-backed text.
602    #[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    /// Returns the token's text, or an empty string when no text exists.
609    #[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    /// Returns whether parser recovery synthesized this token with ANTLR's
616    /// `-1:-1` source span.
617    #[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/// Mutable append-only view used by a token source.
742#[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/// A diagnostic buffered by a token source while it was producing tokens.
766#[derive(Clone, Debug, Eq, PartialEq)]
767#[non_exhaustive]
768pub struct TokenSourceError {
769    /// One-based input line where the diagnostic starts.
770    pub line: usize,
771    /// Zero-based column within `line` where the diagnostic starts.
772    pub column: usize,
773    /// Half-open UTF-8 byte span of the offending input, when available.
774    pub span: Option<Range<usize>>,
775    /// ANTLR-compatible diagnostic message without the leading line/column.
776    pub message: String,
777}
778
779impl TokenSourceError {
780    /// Creates a token-source diagnostic at the given input position.
781    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    /// Attaches the resolved half-open UTF-8 byte span.
791    #[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    /// Returns the source buffer once for ownership by the token store.
805    fn source_text(&self) -> Option<Rc<str>> {
806        None
807    }
808
809    /// Returns and clears diagnostics emitted while fetching tokens.
810    fn drain_errors(&mut self) -> Vec<TokenSourceError> {
811        Vec::new()
812    }
813
814    /// Reports a buffered diagnostic through source-owned listeners.
815    ///
816    /// Returns `true` when the source owns diagnostic reporting. The parser
817    /// uses its own listeners as a fallback for token sources that return
818    /// `false`.
819    fn report_error(&self, _error: &TokenSourceError) -> bool {
820        false
821    }
822
823    /// Serializes lexer DFA cache state when the token source exposes one.
824    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
837/// Formats synthetic-token boundaries with ANTLR's `-1` sentinel.
838fn display_token_boundary(value: usize) -> String {
839    if value == usize::MAX {
840        "-1".to_owned()
841    } else {
842        value.to_string()
843    }
844}
845
846/// Escapes token text the way ANTLR's token display format expects.
847fn 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}