litrs/char/
mod.rs

1use std::fmt;
2
3use crate::{
4    err::{perr, ParseErrorKind::*},
5    escape::unescape,
6    parse::{check_suffix, first_byte_or_empty},
7    Buffer, ParseError,
8};
9
10
11/// A character literal, e.g. `'g'` or `'🦊'`.
12///
13/// See [the reference][ref] for more information.
14///
15/// [ref]: https://doc.rust-lang.org/reference/tokens.html#character-literals
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct CharLit<B: Buffer> {
18    raw: B,
19    /// Start index of the suffix or `raw.len()` if there is no suffix.
20    start_suffix: usize,
21    value: char,
22}
23
24impl<B: Buffer> CharLit<B> {
25    /// Parses the input as a character literal. Returns an error if the input
26    /// is invalid or represents a different kind of literal.
27    pub fn parse(input: B) -> Result<Self, ParseError> {
28        match first_byte_or_empty(&input)? {
29            b'\'' => {
30                let (value, start_suffix) = parse_impl(&input)?;
31                Ok(Self { raw: input, value, start_suffix })
32            }
33            _ => Err(perr(0, DoesNotStartWithQuote)),
34        }
35    }
36
37    /// Returns the character value that this literal represents.
38    pub fn value(&self) -> char {
39        self.value
40    }
41
42    /// The optional suffix. Returns `""` if the suffix is empty/does not exist.
43    pub fn suffix(&self) -> &str {
44        &(*self.raw)[self.start_suffix..]
45    }
46
47    /// Returns the raw input that was passed to `parse`.
48    pub fn raw_input(&self) -> &str {
49        &self.raw
50    }
51
52    /// Returns the raw input that was passed to `parse`, potentially owned.
53    pub fn into_raw_input(self) -> B {
54        self.raw
55    }
56}
57
58impl CharLit<&str> {
59    /// Makes a copy of the underlying buffer and returns the owned version of
60    /// `Self`.
61    pub fn to_owned(&self) -> CharLit<String> {
62        CharLit {
63            raw: self.raw.to_owned(),
64            start_suffix: self.start_suffix,
65            value: self.value,
66        }
67    }
68}
69
70impl<B: Buffer> fmt::Display for CharLit<B> {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        f.pad(&self.raw)
73    }
74}
75
76/// Precondition: first character in input must be `'`.
77#[inline(never)]
78pub(crate) fn parse_impl(input: &str) -> Result<(char, usize), ParseError> {
79    let first = input.chars().nth(1).ok_or(perr(None, UnterminatedCharLiteral))?;
80    let (c, len) = match first {
81        '\'' if input.chars().nth(2) == Some('\'') => return Err(perr(1, UnescapedSingleQuote)),
82        '\'' => return Err(perr(None, EmptyCharLiteral)),
83        '\n' | '\t' | '\r' => return Err(perr(1, UnescapedSpecialWhitespace)),
84
85        '\\' => {
86            let (v, len) = unescape(&input[1..], true, false, true).map_err(|e| e.offset_span(1))?;
87            (v.unwrap_char(), len)
88        }
89        other => (other, other.len_utf8()),
90    };
91
92    match input[1 + len..].find('\'') {
93        Some(0) => {}
94        Some(_) => return Err(perr(None, OverlongCharLiteral)),
95        None => return Err(perr(None, UnterminatedCharLiteral)),
96    }
97
98    let start_suffix = 1 + len + 1;
99    let suffix = &input[start_suffix..];
100    check_suffix(suffix).map_err(|kind| perr(start_suffix, kind))?;
101
102    Ok((c, start_suffix))
103}
104
105#[cfg(test)]
106mod tests;