1use crate::char_stream::TextInterval;
2use std::fmt;
3use std::ops::Range;
4use std::rc::Rc;
5
6pub const TOKEN_EOF: i32 = -1;
7pub const INVALID_TOKEN_TYPE: i32 = 0;
8pub const DEFAULT_CHANNEL: i32 = 0;
9pub const HIDDEN_CHANNEL: i32 = 1;
10
11pub const MAX_TOKEN_OFFSET: usize = (u32::MAX - 1) as usize;
15
16#[repr(transparent)]
17#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
18pub struct TokenId(u32);
19
20impl TokenId {
21 #[must_use]
22 pub const fn index(self) -> usize {
23 self.0 as usize
24 }
25}
26
27impl TryFrom<usize> for TokenId {
28 type Error = TokenStoreError;
29
30 fn try_from(value: usize) -> Result<Self, Self::Error> {
31 u32::try_from(value)
32 .map(Self)
33 .map_err(|_| TokenStoreError::overflow("index", value, u32::MAX as usize))
34 }
35}
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
38pub enum TokenChannel {
39 Default,
40 Hidden,
41 Custom(i32),
42}
43
44impl TokenChannel {
45 pub const fn value(self) -> i32 {
46 match self {
47 Self::Default => DEFAULT_CHANNEL,
48 Self::Hidden => HIDDEN_CHANNEL,
49 Self::Custom(channel) => channel,
50 }
51 }
52}
53
54impl From<i32> for TokenChannel {
55 fn from(value: i32) -> Self {
56 match value {
57 DEFAULT_CHANNEL => Self::Default,
58 HIDDEN_CHANNEL => Self::Hidden,
59 other => Self::Custom(other),
60 }
61 }
62}
63
64pub trait Token: fmt::Debug {
65 fn token_id(&self) -> TokenId;
66 fn token_type(&self) -> i32;
67 fn channel(&self) -> i32;
68 fn start(&self) -> usize;
70 fn stop(&self) -> usize;
73 fn line(&self) -> usize;
75 fn column(&self) -> usize;
78 fn text(&self) -> Option<&str>;
79 fn source_name(&self) -> &str;
80
81 fn interval(&self) -> TextInterval {
82 TextInterval::new(self.start(), self.stop())
83 }
84
85 fn start_byte(&self) -> usize;
87
88 fn stop_byte(&self) -> usize;
90
91 fn byte_span(&self) -> Range<usize> {
93 self.start_byte()..self.stop_byte()
94 }
95}
96
97impl<T: Token + ?Sized> Token for &T {
98 fn token_id(&self) -> TokenId {
99 (**self).token_id()
100 }
101
102 fn token_type(&self) -> i32 {
103 (**self).token_type()
104 }
105
106 fn channel(&self) -> i32 {
107 (**self).channel()
108 }
109
110 fn start(&self) -> usize {
111 (**self).start()
112 }
113
114 fn stop(&self) -> usize {
115 (**self).stop()
116 }
117
118 fn line(&self) -> usize {
119 (**self).line()
120 }
121
122 fn column(&self) -> usize {
123 (**self).column()
124 }
125
126 fn text(&self) -> Option<&str> {
127 (**self).text()
128 }
129
130 fn source_name(&self) -> &str {
131 (**self).source_name()
132 }
133
134 fn start_byte(&self) -> usize {
135 (**self).start_byte()
136 }
137
138 fn stop_byte(&self) -> usize {
139 (**self).stop_byte()
140 }
141}
142
143#[derive(Clone, Debug)]
148pub struct TokenSpec {
149 pub token_type: i32,
150 pub channel: i32,
151 pub start: usize,
152 pub stop: usize,
153 pub start_byte: usize,
154 pub stop_byte: usize,
155 pub line: usize,
156 pub column: usize,
157 pub text: Option<String>,
158 pub source_backed: bool,
159}
160
161impl TokenSpec {
162 #[must_use]
163 pub fn explicit(token_type: i32, text: impl Into<String>) -> Self {
164 Self {
165 token_type,
166 channel: DEFAULT_CHANNEL,
167 start: 0,
168 stop: 0,
169 start_byte: 0,
170 stop_byte: 1,
171 line: 1,
172 column: 0,
173 text: Some(text.into()),
174 source_backed: false,
175 }
176 }
177
178 #[must_use]
179 pub fn eof(index: usize, byte_offset: usize, line: usize, column: usize) -> Self {
180 Self {
181 token_type: TOKEN_EOF,
182 channel: DEFAULT_CHANNEL,
183 start: index,
184 stop: index.checked_sub(1).unwrap_or(usize::MAX),
185 start_byte: byte_offset,
186 stop_byte: byte_offset,
187 line,
188 column,
189 text: Some("<EOF>".to_owned()),
190 source_backed: false,
191 }
192 }
193
194 #[must_use]
195 pub const fn with_channel(mut self, channel: i32) -> Self {
196 self.channel = channel;
197 self
198 }
199
200 #[must_use]
201 pub const fn with_span(mut self, start: usize, stop: usize) -> Self {
202 self.start = start;
203 self.stop = stop;
204 self.start_byte = start;
205 self.stop_byte = default_stop_byte(start, stop);
206 self
207 }
208
209 #[must_use]
210 pub const fn with_byte_span(mut self, start_byte: usize, stop_byte: usize) -> Self {
211 self.start_byte = start_byte;
212 self.stop_byte = stop_byte;
213 self
214 }
215
216 #[must_use]
217 pub const fn with_position(mut self, line: usize, column: usize) -> Self {
218 self.line = line;
219 self.column = column;
220 self
221 }
222}
223
224#[derive(Clone, Debug, Eq, PartialEq)]
225pub struct TokenStoreError(TokenStoreErrorKind);
226
227impl TokenStoreError {
228 const fn overflow(field: &'static str, value: usize, limit: usize) -> Self {
229 Self(TokenStoreErrorKind::Overflow {
230 field,
231 value,
232 limit,
233 })
234 }
235
236 const fn invalid_source_boundary(offset: usize, source_len: usize) -> Self {
237 Self(TokenStoreErrorKind::InvalidSourceBoundary { offset, source_len })
238 }
239
240 pub(crate) const fn invalid_source_output(
241 expected_id: usize,
242 returned_id: usize,
243 appended: usize,
244 ) -> Self {
245 Self(TokenStoreErrorKind::InvalidSourceOutput {
246 expected_id,
247 returned_id,
248 appended,
249 })
250 }
251}
252
253impl fmt::Display for TokenStoreError {
254 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255 match self.0 {
256 TokenStoreErrorKind::Overflow {
257 field,
258 value,
259 limit,
260 } => write!(
261 f,
262 "token {field} {value} exceeds the supported limit {limit}"
263 ),
264 TokenStoreErrorKind::InvalidSourceBoundary { offset, source_len } => write!(
265 f,
266 "token source byte offset {offset} is not a UTF-8 character boundary \
267 for source length {source_len}"
268 ),
269 TokenStoreErrorKind::InvalidSourceOutput {
270 expected_id,
271 returned_id,
272 appended,
273 } => write!(
274 f,
275 "token source must append exactly one token and return ID {expected_id}, \
276 but appended {appended} and returned ID {returned_id}"
277 ),
278 }
279 }
280}
281
282impl std::error::Error for TokenStoreError {}
283
284#[derive(Clone, Debug, Eq, PartialEq)]
285enum TokenStoreErrorKind {
286 Overflow {
287 field: &'static str,
288 value: usize,
289 limit: usize,
290 },
291 InvalidSourceBoundary {
292 offset: usize,
293 source_len: usize,
294 },
295 InvalidSourceOutput {
296 expected_id: usize,
297 returned_id: usize,
298 appended: usize,
299 },
300}
301
302#[derive(Debug)]
304pub struct TokenStore {
305 source: Option<Rc<str>>,
306 source_name: Rc<str>,
307 token_types: Vec<i32>,
308 channels: Vec<i32>,
309 scalar_starts: Vec<u32>,
310 scalar_stops: Vec<u32>,
311 byte_starts: Vec<u32>,
312 byte_stops: Vec<u32>,
313 lines: Vec<u32>,
314 columns: Vec<u32>,
315 source_backed: Vec<bool>,
316 explicit_text: Vec<(TokenId, Rc<str>)>,
317}
318
319impl TokenStore {
320 pub(crate) fn new(source: Option<Rc<str>>, source_name: impl Into<Rc<str>>) -> Self {
321 Self {
322 source,
323 source_name: source_name.into(),
324 token_types: Vec::new(),
325 channels: Vec::new(),
326 scalar_starts: Vec::new(),
327 scalar_stops: Vec::new(),
328 byte_starts: Vec::new(),
329 byte_stops: Vec::new(),
330 lines: Vec::new(),
331 columns: Vec::new(),
332 source_backed: Vec::new(),
333 explicit_text: Vec::new(),
334 }
335 }
336
337 #[must_use]
338 pub const fn len(&self) -> usize {
339 self.token_types.len()
340 }
341
342 #[must_use]
343 pub const fn is_empty(&self) -> bool {
344 self.token_types.is_empty()
345 }
346
347 pub(crate) fn push(&mut self, spec: TokenSpec) -> Result<TokenId, TokenStoreError> {
348 let raw_id = u32::try_from(self.len())
349 .map_err(|_| TokenStoreError::overflow("count", self.len(), u32::MAX as usize))?;
350 let id = TokenId(raw_id);
351 let scalar_start = compact_boundary("start offset", spec.start)?;
352 let scalar_stop = compact_boundary("stop offset", spec.stop)?;
353 let byte_start = compact_offset("start byte", spec.start_byte)?;
354 let byte_stop = compact_offset("stop byte", spec.stop_byte)?;
355 let line = compact_offset("line", spec.line)?;
356 let column = compact_offset("column", spec.column)?;
357
358 if spec.source_backed {
359 let Some(source) = self.source.as_ref() else {
360 return Err(TokenStoreError::overflow("source text", 1, 0));
361 };
362 if spec.start_byte > spec.stop_byte || spec.stop_byte > source.len() {
363 return Err(TokenStoreError::overflow(
364 "source byte span",
365 spec.stop_byte,
366 source.len(),
367 ));
368 }
369 if !source.is_char_boundary(spec.start_byte) {
370 return Err(TokenStoreError::invalid_source_boundary(
371 spec.start_byte,
372 source.len(),
373 ));
374 }
375 if !source.is_char_boundary(spec.stop_byte) {
376 return Err(TokenStoreError::invalid_source_boundary(
377 spec.stop_byte,
378 source.len(),
379 ));
380 }
381 }
382
383 self.token_types.push(spec.token_type);
384 self.channels.push(spec.channel);
385 self.scalar_starts.push(scalar_start);
386 self.scalar_stops.push(scalar_stop);
387 self.byte_starts.push(byte_start);
388 self.byte_stops.push(byte_stop);
389 self.lines.push(line);
390 self.columns.push(column);
391 self.source_backed.push(spec.source_backed);
392 if let Some(text) = spec.text {
393 self.explicit_text.push((id, Rc::from(text)));
394 }
395 Ok(id)
396 }
397
398 const fn contains(&self, id: TokenId) -> bool {
399 id.index() < self.len()
400 }
401
402 #[must_use]
404 pub fn view(&self, id: TokenId) -> Option<TokenView<'_>> {
405 self.contains(id).then_some(TokenView { store: self, id })
406 }
407
408 #[must_use]
410 pub fn token_type(&self, id: TokenId) -> Option<i32> {
411 self.token_types.get(id.index()).copied()
412 }
413
414 #[must_use]
416 pub fn channel(&self, id: TokenId) -> Option<i32> {
417 self.channels.get(id.index()).copied()
418 }
419
420 #[must_use]
422 pub fn start(&self, id: TokenId) -> Option<usize> {
423 self.scalar_starts
424 .get(id.index())
425 .copied()
426 .map(expand_boundary)
427 }
428
429 #[must_use]
431 pub fn stop(&self, id: TokenId) -> Option<usize> {
432 self.scalar_stops
433 .get(id.index())
434 .copied()
435 .map(expand_boundary)
436 }
437
438 #[must_use]
440 pub fn line(&self, id: TokenId) -> Option<usize> {
441 self.lines.get(id.index()).map(|line| *line as usize)
442 }
443
444 #[must_use]
446 pub fn column(&self, id: TokenId) -> Option<usize> {
447 self.columns.get(id.index()).map(|column| *column as usize)
448 }
449
450 #[must_use]
452 pub fn start_byte(&self, id: TokenId) -> Option<usize> {
453 self.byte_starts
454 .get(id.index())
455 .map(|offset| *offset as usize)
456 }
457
458 #[must_use]
460 pub fn stop_byte(&self, id: TokenId) -> Option<usize> {
461 self.byte_stops
462 .get(id.index())
463 .map(|offset| *offset as usize)
464 }
465
466 fn explicit_text(&self, id: TokenId) -> Option<&str> {
467 self.explicit_text
468 .binary_search_by_key(&id, |(token_id, _)| *token_id)
469 .ok()
470 .map(|index| self.explicit_text[index].1.as_ref())
471 }
472
473 #[must_use]
475 pub fn text(&self, id: TokenId) -> Option<&str> {
476 if let Some(text) = self.explicit_text(id) {
477 return Some(text);
478 }
479 if !self.source_backed.get(id.index()).copied().unwrap_or(false) {
480 return None;
481 }
482 let source = self.source.as_deref()?;
483 let start = self.byte_starts[id.index()] as usize;
484 let stop = self.byte_stops[id.index()] as usize;
485 source.get(start..stop)
486 }
487}
488
489const fn default_stop_byte(start: usize, stop: usize) -> usize {
490 match stop.checked_add(1) {
491 Some(end) if end >= start => end,
492 Some(_) | None => start,
493 }
494}
495
496const fn compact_boundary(field: &'static str, value: usize) -> Result<u32, TokenStoreError> {
497 if value == usize::MAX {
498 return Ok(u32::MAX);
499 }
500 compact_offset(field, value)
501}
502
503const fn compact_offset(field: &'static str, value: usize) -> Result<u32, TokenStoreError> {
504 if value > MAX_TOKEN_OFFSET {
505 return Err(TokenStoreError::overflow(field, value, MAX_TOKEN_OFFSET));
506 }
507 Ok(value as u32)
508}
509
510#[derive(Clone, Copy)]
512pub struct TokenView<'a> {
513 store: &'a TokenStore,
514 id: TokenId,
515}
516
517impl<'a> TokenView<'a> {
518 #[must_use]
520 #[allow(clippy::trivially_copy_pass_by_ref)]
521 pub fn text(&self) -> &'a str {
522 self.store.text(self.id).unwrap_or("")
523 }
524}
525
526impl fmt::Debug for TokenView<'_> {
527 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
528 f.debug_struct("TokenView")
529 .field("id", &self.id)
530 .field("token_type", &self.token_type())
531 .field("channel", &self.channel())
532 .field("text", &self.text())
533 .finish()
534 }
535}
536
537impl PartialEq for TokenView<'_> {
538 fn eq(&self, other: &Self) -> bool {
539 self.id == other.id
540 && self.token_type() == other.token_type()
541 && self.channel() == other.channel()
542 && self.start() == other.start()
543 && self.stop() == other.stop()
544 && self.line() == other.line()
545 && self.column() == other.column()
546 && self.text() == other.text()
547 && self.source_name() == other.source_name()
548 }
549}
550
551impl Eq for TokenView<'_> {}
552
553impl Token for TokenView<'_> {
554 fn token_id(&self) -> TokenId {
555 self.id
556 }
557
558 fn token_type(&self) -> i32 {
559 self.store.token_types[self.id.index()]
560 }
561
562 fn channel(&self) -> i32 {
563 self.store.channels[self.id.index()]
564 }
565
566 fn start(&self) -> usize {
567 expand_boundary(self.store.scalar_starts[self.id.index()])
568 }
569
570 fn stop(&self) -> usize {
571 expand_boundary(self.store.scalar_stops[self.id.index()])
572 }
573
574 fn line(&self) -> usize {
575 self.store.lines[self.id.index()] as usize
576 }
577
578 fn column(&self) -> usize {
579 self.store.columns[self.id.index()] as usize
580 }
581
582 fn text(&self) -> Option<&str> {
583 self.store.text(self.id)
584 }
585
586 fn source_name(&self) -> &str {
587 self.store.source_name.as_ref()
588 }
589
590 fn start_byte(&self) -> usize {
591 self.store.byte_starts[self.id.index()] as usize
592 }
593
594 fn stop_byte(&self) -> usize {
595 self.store.byte_stops[self.id.index()] as usize
596 }
597}
598
599impl fmt::Display for TokenView<'_> {
600 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
601 let channel = if self.channel() == DEFAULT_CHANNEL {
602 String::new()
603 } else {
604 format!(",channel={}", self.channel())
605 };
606 write!(
607 f,
608 "[@{},{}:{}='{}',<{}>{},{}:{}]",
609 display_token_index(self),
610 display_token_boundary(self.start()),
611 display_token_boundary(self.stop()),
612 display_text(self.text()),
613 self.token_type(),
614 channel,
615 self.line(),
616 self.column()
617 )
618 }
619}
620
621impl AsRef<str> for TokenView<'_> {
622 fn as_ref(&self) -> &str {
623 self.text()
624 }
625}
626
627const fn expand_boundary(value: u32) -> usize {
628 if value == u32::MAX {
629 usize::MAX
630 } else {
631 value as usize
632 }
633}
634
635#[derive(Debug)]
637pub struct TokenSink<'a> {
638 store: &'a mut TokenStore,
639}
640
641impl<'a> TokenSink<'a> {
642 pub(crate) const fn new(store: &'a mut TokenStore) -> Self {
643 Self { store }
644 }
645
646 pub fn push(&mut self, spec: TokenSpec) -> Result<TokenId, TokenStoreError> {
647 self.store.push(spec)
648 }
649
650 pub fn view(&self, id: TokenId) -> Option<TokenView<'_>> {
651 self.store.view(id)
652 }
653
654 pub(crate) const fn token_count(&self) -> usize {
655 self.store.len()
656 }
657}
658
659#[derive(Clone, Debug, Eq, PartialEq)]
661pub struct TokenSourceError {
662 pub line: usize,
664 pub column: usize,
666 pub message: String,
668}
669
670impl TokenSourceError {
671 pub fn new(line: usize, column: usize, message: impl Into<String>) -> Self {
673 Self {
674 line,
675 column,
676 message: message.into(),
677 }
678 }
679}
680
681pub trait TokenSource {
682 fn next_token(&mut self, sink: &mut TokenSink<'_>) -> Result<TokenId, TokenStoreError>;
683 fn line(&self) -> usize;
684 fn column(&self) -> usize;
685 fn source_name(&self) -> &str;
686
687 fn source_text(&self) -> Option<Rc<str>> {
689 None
690 }
691
692 fn drain_errors(&mut self) -> Vec<TokenSourceError> {
694 Vec::new()
695 }
696
697 fn lexer_dfa_string(&self) -> String {
699 String::new()
700 }
701}
702
703fn display_token_index(token: &impl Token) -> String {
704 if token.start() == usize::MAX && token.stop() == usize::MAX {
705 "-1".to_owned()
706 } else {
707 token.token_id().index().to_string()
708 }
709}
710
711fn display_token_boundary(value: usize) -> String {
713 if value == usize::MAX {
714 "-1".to_owned()
715 } else {
716 value.to_string()
717 }
718}
719
720fn display_text(text: &str) -> String {
722 let mut out = String::new();
723 for ch in text.chars() {
724 match ch {
725 '\n' => out.push_str("\\n"),
726 '\r' => out.push_str("\\r"),
727 '\t' => out.push_str("\\t"),
728 other => out.push(other),
729 }
730 }
731 out
732}
733
734#[cfg(test)]
735mod tests {
736 use super::*;
737
738 fn one_token(spec: TokenSpec) -> TokenStore {
739 let mut store = TokenStore::new(None, "");
740 store.push(spec).expect("test token should fit");
741 store
742 }
743
744 #[test]
745 fn token_view_display_matches_antlr_shape() {
746 let store = one_token(
747 TokenSpec::explicit(7, "abc")
748 .with_span(2, 4)
749 .with_position(3, 9),
750 );
751 assert_eq!(
752 store.view(TokenId(0)).expect("token").to_string(),
753 "[@0,2:4='abc',<7>,3:9]"
754 );
755 }
756
757 #[test]
758 fn synthetic_token_display_uses_antlr_negative_index() {
759 let store = one_token(
760 TokenSpec::explicit(7, "<missing X>")
761 .with_span(usize::MAX, usize::MAX)
762 .with_byte_span(0, 0)
763 .with_position(3, 9),
764 );
765 assert_eq!(
766 store.view(TokenId(0)).expect("token").to_string(),
767 "[@-1,-1:-1='<missing X>',<7>,3:9]"
768 );
769 }
770
771 #[test]
772 fn source_backed_token_exposes_utf8_byte_span() {
773 let mut store = TokenStore::new(Some(Rc::from("éβz")), "");
774 let id = store
775 .push(TokenSpec {
776 token_type: 1,
777 channel: DEFAULT_CHANNEL,
778 start: 1,
779 stop: 1,
780 start_byte: 2,
781 stop_byte: 4,
782 line: 1,
783 column: 1,
784 text: None,
785 source_backed: true,
786 })
787 .expect("token should fit");
788 let token = TokenView { store: &store, id };
789
790 assert_eq!(token.start(), 1);
791 assert_eq!(token.stop(), 1);
792 assert_eq!(token.byte_span(), 2..4);
793 assert_eq!(token.text(), "β");
794 }
795
796 #[test]
797 fn source_backed_token_rejects_non_utf8_boundaries() {
798 for (start_byte, stop_byte) in [(1, 2), (0, 1)] {
799 let mut store = TokenStore::new(Some(Rc::from("éz")), "");
800 let error = store
801 .push(TokenSpec {
802 token_type: 1,
803 channel: DEFAULT_CHANNEL,
804 start: 0,
805 stop: 0,
806 start_byte,
807 stop_byte,
808 line: 1,
809 column: 0,
810 text: None,
811 source_backed: true,
812 })
813 .expect_err("spans that split UTF-8 code points must fail");
814
815 assert!(error.to_string().contains("UTF-8 character boundary"));
816 assert!(store.is_empty());
817 }
818 }
819
820 #[test]
821 fn overlarge_offset_is_rejected() {
822 let mut store = TokenStore::new(None, "");
823 let error = store
824 .push(TokenSpec::explicit(1, "x").with_span(MAX_TOKEN_OFFSET + 1, 0))
825 .expect_err("overlarge offsets must fail");
826 assert!(error.to_string().contains("supported limit"));
827 }
828}