aboutsummaryrefslogtreecommitdiff
path: root/src/parse.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/parse.rs')
-rw-r--r--src/parse.rs81
1 files changed, 81 insertions, 0 deletions
diff --git a/src/parse.rs b/src/parse.rs
new file mode 100644
index 0000000..07026ed
--- /dev/null
+++ b/src/parse.rs
@@ -0,0 +1,81 @@
+use crate::{
+ BoolLit,
+ Buffer,
+ ByteLit,
+ ByteStringLit,
+ CharLit,
+ ParseError,
+ FloatLit,
+ IntegerLit,
+ Literal,
+ StringLit,
+ err::{perr, ParseErrorKind::*},
+};
+
+
+impl<B: Buffer> Literal<B> {
+ /// Parses the given input as a Rust literal.
+ pub fn parse(input: B) -> Result<Self, ParseError> {
+ let first = first_byte_or_empty(&input)?;
+ let second = input.as_bytes().get(1).copied();
+
+ match first {
+ b'f' if &*input == "false" => Ok(Self::Bool(BoolLit::False)),
+ b't' if &*input == "true" => Ok(Self::Bool(BoolLit::True)),
+
+ // A number literal (integer or float).
+ digit @ b'0'..=b'9' => {
+ // To figure out whether this is a float or integer, we do some
+ // quick inspection here. Yes, this is technically duplicate
+ // work with what is happening in the integer/float parse
+ // methods, but it makes the code way easier for now and won't
+ // be a huge performance loss.
+ let end = 1 + end_dec_digits(&input[1..]);
+ match input.as_bytes().get(end) {
+ // Potential chars in integer literals: b, o, x for base; u
+ // and i for type suffix.
+ None | Some(b'b') | Some(b'o') | Some(b'x') | Some(b'u') | Some(b'i')
+ => IntegerLit::parse_impl(input, digit).map(Literal::Integer),
+
+ // Potential chars for float literals: `.` as fractional
+ // period, e and E as exponent start and f as type suffix.
+ Some(b'.') | Some(b'e') | Some(b'E') | Some(b'f')
+ => FloatLit::parse_impl(input).map(Literal::Float),
+
+ _ => Err(perr(end, UnexpectedChar)),
+ }
+ },
+
+ b'\'' => CharLit::parse_impl(input).map(Literal::Char),
+ b'"' | b'r' => StringLit::parse_impl(input).map(Literal::String),
+
+ b'b' if second == Some(b'\'') => ByteLit::parse_impl(input).map(Literal::Byte),
+ b'b' if second == Some(b'r') || second == Some(b'"')
+ => ByteStringLit::parse_impl(input).map(Literal::ByteString),
+
+ _ => Err(perr(None, InvalidLiteral)),
+ }
+ }
+}
+
+
+pub(crate) fn first_byte_or_empty(s: &str) -> Result<u8, ParseError> {
+ s.as_bytes().get(0).copied().ok_or(perr(None, Empty))
+}
+
+/// Returns the index of the first non-underscore, non-decimal digit in `input`,
+/// or the `input.len()` if all characters are decimal digits.
+pub(crate) fn end_dec_digits(input: &str) -> usize {
+ input.bytes()
+ .position(|b| !matches!(b, b'_' | b'0'..=b'9'))
+ .unwrap_or(input.len())
+}
+
+pub(crate) fn hex_digit_value(digit: u8) -> Option<u8> {
+ match digit {
+ b'0'..=b'9' => Some(digit - b'0'),
+ b'a'..=b'f' => Some(digit - b'a' + 10),
+ b'A'..=b'F' => Some(digit - b'A' + 10),
+ _ => None,
+ }
+}