1use std::borrow::{Borrow, Cow};
5use std::cmp::Ordering;
6use std::ffi::OsStr;
7use std::fmt::{Debug, Display, Error, Formatter};
8use std::hash::Hash;
9use std::mem::ManuallyDrop;
10use std::ops::{Bound, Range, RangeBounds};
11use std::path::{Path, PathBuf};
12use std::slice::SliceIndex;
13
14use bumpalo::Bump;
15
16use crate::date::Date;
17use crate::fileset::{FileEntry, FileKind};
18use crate::macros::{MACRO_MAP, MacroMapIndex};
19use crate::pathtable::{PathTable, PathTableIndex};
20use crate::report::{ErrorKey, err, untidy};
21
22#[derive(Clone, Copy, Eq, PartialEq, Hash)]
23pub struct Loc {
24 pub(crate) idx: PathTableIndex,
25 pub kind: FileKind,
26 pub line: u32,
28 pub column: u32,
29 pub link_idx: Option<MacroMapIndex>,
32}
33
34impl Loc {
35 #[must_use]
36 pub(crate) fn for_file(pathname: PathBuf, kind: FileKind, fullpath: PathBuf) -> Self {
37 let idx = PathTable::store(pathname, fullpath);
38 Loc { idx, kind, line: 0, column: 0, link_idx: None }
39 }
40
41 pub fn filename(self) -> Cow<'static, str> {
42 PathTable::lookup_path(self.idx)
43 .file_name()
44 .unwrap_or_else(|| OsStr::new(""))
45 .to_string_lossy()
46 }
47
48 pub fn pathname(self) -> &'static Path {
49 PathTable::lookup_path(self.idx)
50 }
51
52 pub fn fullpath(self) -> &'static Path {
53 PathTable::lookup_fullpath(self.idx)
54 }
55
56 #[inline]
57 pub fn same_file(self, other: Loc) -> bool {
58 self.idx == other.idx
59 }
60}
61
62impl PartialOrd for Loc {
63 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
64 Some(self.cmp(other))
65 }
66}
67
68impl Ord for Loc {
69 fn cmp(&self, other: &Self) -> Ordering {
70 self.idx
71 .cmp(&other.idx)
72 .then(self.line.cmp(&other.line))
73 .then(self.column.cmp(&other.column))
74 .then(
75 self.link_idx
76 .map(|link| MACRO_MAP.get_loc(link))
77 .cmp(&other.link_idx.map(|link| MACRO_MAP.get_loc(link))),
78 )
79 }
80}
81
82impl From<&FileEntry> for Loc {
83 fn from(entry: &FileEntry) -> Self {
84 if let Some(idx) = entry.path_idx() {
85 Loc { idx, kind: entry.kind(), line: 0, column: 0, link_idx: None }
86 } else {
87 Self::for_file(entry.path().to_path_buf(), entry.kind(), entry.fullpath().to_path_buf())
88 }
89 }
90}
91
92impl From<&mut FileEntry> for Loc {
93 fn from(entry: &mut FileEntry) -> Self {
94 (&*entry).into()
95 }
96}
97
98impl From<FileEntry> for Loc {
99 fn from(entry: FileEntry) -> Self {
100 (&entry).into()
101 }
102}
103
104impl Debug for Loc {
105 fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
107 f.debug_struct("Loc")
108 .field("pathindex", &self.idx)
109 .field("pathname", &self.pathname())
110 .field("fullpath", &self.fullpath())
111 .field("kind", &self.kind)
112 .field("line", &self.line)
113 .field("column", &self.column)
114 .field("linkindex", &self.link_idx)
115 .finish()
116 }
117}
118
119thread_local!(static STR_BUMP: ManuallyDrop<Bump> = ManuallyDrop::new(Bump::new()));
120
121pub(crate) fn bump(s: &str) -> &'static str {
126 STR_BUMP.with(|bump| {
127 let s = bump.alloc_str(s);
128 unsafe {
129 let s_ptr: *const str = s;
130 &*s_ptr
131 }
132 })
133}
134
135#[allow(missing_copy_implementations)]
137#[derive(Clone, Debug)]
138pub struct Token {
139 s: &'static str,
140 pub loc: Loc,
141}
142
143impl Token {
144 #[must_use]
145 pub fn new(s: &str, loc: Loc) -> Self {
146 Token { s: bump(s), loc }
147 }
148
149 #[must_use]
150 pub fn from_static_str(s: &'static str, loc: Loc) -> Self {
151 Token { s, loc }
152 }
153
154 #[must_use]
156 pub fn subtoken<R>(&self, range: R, loc: Loc) -> Token
157 where
158 R: RangeBounds<usize> + SliceIndex<str, Output = str>,
159 {
160 Token { s: &self.s[range], loc }
161 }
162
163 #[must_use]
166 pub fn subtoken_stripped(&self, mut range: Range<usize>, mut loc: Loc) -> Token {
167 let mut start = match range.start_bound() {
168 Bound::Included(&i) => i,
169 Bound::Excluded(&i) => i + 1,
170 Bound::Unbounded => 0,
171 };
172 let mut end = match range.end_bound() {
173 Bound::Included(&i) => i + 1,
174 Bound::Excluded(&i) => i,
175 Bound::Unbounded => self.s.len(),
176 };
177 for (i, c) in self.s[range.clone()].char_indices() {
178 if !c.is_whitespace() {
179 start += i;
180 range = start..end;
181 break;
182 }
183 loc.column += 1;
184 }
185 for (i, c) in self.s[range.clone()].char_indices().rev() {
186 if !c.is_whitespace() {
187 end = start + i + c.len_utf8();
188 range = start..end;
189 break;
190 }
191 }
192 Token { s: &self.s[range], loc }
193 }
194
195 pub fn as_str(&self) -> &'static str {
196 self.s
197 }
198
199 pub fn is(&self, s: &str) -> bool {
200 self.s == s
201 }
202
203 pub fn lowercase_is(&self, s: &str) -> bool {
204 self.s.to_ascii_lowercase() == s
205 }
206
207 pub fn starts_with(&self, s: &str) -> bool {
208 self.s.starts_with(s)
209 }
210
211 #[must_use]
212 pub fn split(&self, ch: char) -> Vec<Token> {
218 let mut pos = 0;
219 let mut vec = Vec::new();
220 let mut loc = self.loc;
221 let mut lines: u32 = 0;
222 for (cols, (i, c)) in self.s.char_indices().enumerate() {
223 let cols = u32::try_from(cols).expect("internal error: 2^32 columns");
224 if c == ch {
225 vec.push(self.subtoken(pos..i, loc));
226 pos = i + 1;
227 loc.column = self.loc.column + cols + 1;
228 loc.line = self.loc.line + lines;
229 }
230 if c == '\n' {
231 lines += 1;
232 }
233 }
234 vec.push(self.subtoken(pos.., loc));
235 vec
236 }
237
238 #[must_use]
239 pub fn strip_suffix(&self, sfx: &str) -> Option<Token> {
240 self.s.strip_suffix(sfx).map(|pfx| Token::from_static_str(pfx, self.loc))
241 }
242
243 #[must_use]
244 pub fn strip_prefix(&self, pfx: &str) -> Option<Token> {
245 #[allow(clippy::cast_possible_truncation)]
246 self.s.strip_prefix(pfx).map(|sfx| {
247 let mut loc = self.loc;
248 loc.column += pfx.chars().count() as u32;
249 Token::from_static_str(sfx, loc)
250 })
251 }
252
253 #[must_use]
254 pub fn split_once(&self, ch: char) -> Option<(Token, Token)> {
261 for (cols, (i, c)) in self.s.char_indices().enumerate() {
262 let cols = u32::try_from(cols).expect("internal error: 2^32 columns");
263 if c == ch {
264 let token1 = self.subtoken(..i, self.loc);
265 let mut loc = self.loc;
266 loc.column += cols + 1;
267 let token2 = self.subtoken(i + 1.., loc);
268 return Some((token1, token2));
269 }
270 }
271 None
272 }
273
274 #[must_use]
281 pub fn split_after(&self, ch: char) -> Option<(Token, Token)> {
282 for (cols, (i, c)) in self.s.char_indices().enumerate() {
283 let cols = u32::try_from(cols).expect("internal error: 2^32 columns");
284 #[allow(clippy::cast_possible_truncation)] if c == ch {
286 let chlen = ch.len_utf8();
287 let token1 = self.subtoken(..i + chlen, self.loc);
288 let mut loc = self.loc;
289 loc.column += cols + chlen as u32;
290 let token2 = self.subtoken(i + chlen.., loc);
291 return Some((token1, token2));
292 }
293 }
294 None
295 }
296
297 pub fn combine(&mut self, other: &Token, c: char) {
299 let mut s = self.s.to_string();
300 s.push(c);
301 s.push_str(other.s);
302 self.s = bump(&s);
303 }
304
305 #[must_use]
306 pub fn trim(&self) -> Token {
312 let mut real_start = None;
313 let mut real_end = self.s.len();
314 for (cols, (i, c)) in self.s.char_indices().enumerate() {
315 let cols = u32::try_from(cols).expect("internal error: 2^32 columns");
316 if c != ' ' {
317 real_start = Some((cols, i));
318 break;
319 }
320 }
321 while real_end > 0 && &self.s[real_end - 1..real_end] == " " {
323 real_end -= 1;
324 }
325 if let Some((cols, i)) = real_start {
326 let mut loc = self.loc;
327 loc.column += cols;
328 self.subtoken(i..real_end, loc)
329 } else {
330 Token::from_static_str("", self.loc)
332 }
333 }
334
335 pub fn expect_number(&self) -> Option<f64> {
336 self.check_number();
337 let s = self.s.trim_end_matches('f');
339 if let Ok(v) = s.parse::<f64>() {
340 Some(v)
341 } else {
342 err(ErrorKey::Validation).msg("expected number").loc(self).push();
343 None
344 }
345 }
346
347 pub fn get_fixed_number(&self) -> Option<i64> {
349 if !self.s.contains('.') {
350 return Some(self.s.parse::<i64>().ok()? * 100_000);
351 }
352
353 let r = self.s.find('.')?;
354 let whole = &self.s[..r];
355 let fraction = &self.s[r + 1..];
356
357 if fraction.len() > 5 {
358 return None;
359 }
360 format!("{whole}{fraction:0<5}").parse::<i64>().ok()
361 }
362
363 pub fn get_number(&self) -> Option<f64> {
364 self.s.parse::<f64>().ok()
365 }
366
367 pub fn is_number(&self) -> bool {
368 self.s.parse::<f64>().is_ok()
369 }
370
371 pub fn check_number(&self) {
372 if let Some(idx) = self.s.find('.') {
373 if self.s.len() - idx > 6 {
374 let msg = "only 5 decimals are supported";
375 let info =
376 "if you give more decimals, you get an error and the number is read as 0";
377 err(ErrorKey::Validation).msg(msg).info(info).loc(self).push();
378 }
379 }
380 }
381
382 pub fn expect_precise_number(&self) -> Option<f64> {
384 let s = if self.s.ends_with("inf") { self.s } else { self.s.trim_end_matches('f') };
386 if let Ok(v) = s.parse::<f64>() {
387 Some(v)
388 } else {
389 err(ErrorKey::Validation).msg("expected number").loc(self).push();
390 None
391 }
392 }
393
394 pub fn expect_integer(&self) -> Option<i64> {
395 if let Ok(v) = self.s.parse::<i64>() {
396 Some(v)
397 } else {
398 err(ErrorKey::Validation).msg("expected integer").loc(self).push();
399 None
400 }
401 }
402
403 pub fn get_integer(&self) -> Option<i64> {
404 self.s.parse::<i64>().ok()
405 }
406
407 pub fn is_integer(&self) -> bool {
408 self.s.parse::<i64>().is_ok()
409 }
410
411 pub fn expect_date(&self) -> Option<Date> {
412 if let Ok(v) = self.s.parse::<Date>() {
413 if self.s.ends_with('.') {
414 untidy(ErrorKey::Validation).msg("trailing dot on date").loc(self).push();
415 }
416 Some(v)
417 } else {
418 err(ErrorKey::Validation).msg("expected date").loc(self).push();
419 None
420 }
421 }
422
423 pub fn get_date(&self) -> Option<Date> {
424 self.s.parse::<Date>().ok()
425 }
426
427 pub fn is_date(&self) -> bool {
428 self.s.parse::<Date>().is_ok()
429 }
430
431 pub fn is_lowercase(&self) -> bool {
433 !self.s.chars().any(char::is_uppercase)
434 }
435
436 #[must_use]
437 pub fn linked(mut self, link_idx: Option<MacroMapIndex>) -> Self {
438 self.loc.link_idx = link_idx;
439 self
440 }
441}
442
443impl From<&Token> for Token {
444 fn from(token: &Token) -> Token {
445 token.clone()
446 }
447}
448
449impl PartialEq for Token {
451 fn eq(&self, other: &Self) -> bool {
452 self.s == other.s
453 }
454}
455
456impl Eq for Token {}
457
458impl Hash for Token {
459 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
460 self.s.hash(state);
461 }
462}
463
464impl Borrow<str> for Token {
465 fn borrow(&self) -> &str {
466 self.s
467 }
468}
469
470impl Borrow<str> for &Token {
471 fn borrow(&self) -> &str {
472 self.s
473 }
474}
475
476impl From<Loc> for Token {
477 fn from(loc: Loc) -> Self {
478 Token { s: "", loc }
479 }
480}
481
482impl Display for Token {
483 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
484 write!(f, "{}", self.s)
485 }
486}