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 }