/ cab / syntax / token.rs
token.rs
  1  //! Typed [`Token`] definitions.
  2  //!
  3  //! [`Token`]: crate::Token
  4  use std::{
  5     fmt,
  6     ptr,
  7  };
  8  
  9  use derive_more::Deref;
 10  use num::{
 11     Num as _,
 12     bigint as num_bigint,
 13     traits as num_traits,
 14  };
 15  
 16  use crate::{
 17     Kind::*,
 18     red,
 19  };
 20  
 21  macro_rules! token {
 22     (
 23        #[from($kind:ident)]
 24        $(#[$attribute:meta])*
 25        struct $name:ident;
 26     ) => {
 27        $(#[$attribute])*
 28        #[derive(Deref, Debug, Clone, PartialEq, Eq, Hash)]
 29        #[repr(transparent)]
 30        pub struct $name(red::Token);
 31  
 32        impl fmt::Display for $name {
 33           fn fmt(&self, writer: &mut fmt::Formatter<'_>) -> fmt::Result {
 34              (**self).fmt(writer)
 35           }
 36        }
 37  
 38        impl<'a> TryFrom<&'a red::Token> for &'a $name {
 39           type Error = ();
 40  
 41           fn try_from(token: &'a red::Token) -> Result<Self, ()> {
 42              if token.kind() != $kind {
 43                 return Err(());
 44              }
 45  
 46              // SAFETY: token is &red::Token and we are casting it to &$name.
 47              // $name holds red::Token with #[repr(transparent)], so the layout
 48              // is the exact same for &red::Token and &$name.
 49              Ok(unsafe { &*ptr::from_ref(token).cast::<$name>() })
 50           }
 51        }
 52  
 53        impl TryFrom<red::Token> for $name {
 54           type Error = ();
 55  
 56           fn try_from(token: red::Token) -> Result<Self, ()> {
 57              if token.kind() != $kind {
 58                 return Err(());
 59              }
 60  
 61              Ok(Self(token))
 62           }
 63        }
 64     };
 65  }
 66  
 67  // SPACE
 68  
 69  token! {
 70     #[from(TOKEN_SPACE)]
 71     /// Space. Anything that matches [`char::is_whitespace`].
 72     struct Space;
 73  }
 74  
 75  impl Space {
 76     /// Returns the amount of lines this space.
 77     #[must_use]
 78     pub fn line_count(&self) -> usize {
 79        self.text().bytes().filter(|&c| c == b'\n').count() + 1
 80     }
 81  }
 82  
 83  // COMMENT
 84  
 85  token! {
 86     #[from(TOKEN_COMMENT)]
 87     /// A multiline or singleline comment.
 88     struct Comment;
 89  }
 90  
 91  impl Comment {
 92     const START_HASHTAG_LEN: usize = '#'.len_utf8();
 93  
 94     /// Returns the starting delimiter of this comment.
 95     #[must_use]
 96     pub fn start_delimiter(&self) -> &str {
 97        let text = self.text();
 98  
 99        let content_start_index = text
100           .bytes()
101           .skip(Self::START_HASHTAG_LEN)
102           .take_while(|&c| c == b'=')
103           .count()
104           + Self::START_HASHTAG_LEN;
105  
106        &text[..content_start_index]
107     }
108  
109     /// Whether this comment has the capability to span multiple
110     /// lines.
111     #[must_use]
112     pub fn is_multiline(&self) -> bool {
113        self
114           .text()
115           .as_bytes()
116           .get(Self::START_HASHTAG_LEN)
117           .copied()
118           .is_some_and(|c| c == b'=')
119     }
120  }
121  
122  // IDENTIFIER
123  
124  token! {
125     #[from(TOKEN_IDENTIFIER)]
126     /// A plain identifier.
127     struct Identifier;
128  }
129  
130  // CONTENT
131  
132  token! {
133     #[from(TOKEN_CONTENT)]
134     struct Content;
135  }
136  
137  // INTEGER
138  
139  token! {
140     #[from(TOKEN_INTEGER)]
141     /// An integer.
142     struct Integer;
143  }
144  
145  impl Integer {
146     /// Returns the value of this integer, after resolving binary,
147     /// octadecimal and hexadecimal notation if it exists.
148     #[rustfmt::skip]
149     pub fn value(&self) -> Result<num::BigInt, num_bigint::ParseBigIntError> {
150        let text = self.text();
151  
152        match text.as_bytes().get(1).copied() {
153           // Remove leading `_` because that makes num_bigint's parser see the literal as `_BEEF` when we have `0x_BEEF`.
154           Some(b'b' | b'B') => num::BigInt::from_str_radix(text["0b".len()..].trim_start_matches('_'), 2),
155           Some(b'o' | b'O') => num::BigInt::from_str_radix(text["0o".len()..].trim_start_matches('_'), 8),
156           Some(b'x' | b'X') => num::BigInt::from_str_radix(text["0x".len()..].trim_start_matches('_'), 16),
157           _ => num::BigInt::from_str_radix(text, 10),
158        }
159     }
160  }
161  
162  // FLOAT
163  
164  token! {
165     #[from(TOKEN_FLOAT)]
166     /// A float.
167     struct Float;
168  }
169  
170  impl Float {
171     /// Returns the value of the float by parsing the underlying slice.
172     pub fn value(&self) -> Result<f64, num_traits::ParseFloatError> {
173        let text = self.text();
174  
175        match text.as_bytes().get(1).copied() {
176           // Remove leading `_` because that makes num_bigint's parser see the literal as `_BEEF`
177           // when we have `0x_BE.EF`.
178           Some(b'b' | b'B') => f64::from_str_radix(text["0b".len()..].trim_start_matches('_'), 2),
179           Some(b'o' | b'O') => f64::from_str_radix(text["0o".len()..].trim_start_matches('_'), 8),
180           Some(b'x' | b'X') => f64::from_str_radix(text["0x".len()..].trim_start_matches('_'), 16),
181           _ => f64::from_str_radix(text, 10),
182        }
183     }
184  }