blob: 9c9f2745933a22b58f857cf42196141704919568 [file] [log] [blame]
use std::fmt;
use std::hash::{Hash, Hasher};
use proc_macro2::{self, Literal, TokenKind};
use {Span, TokenTree};
#[derive(Clone)]
pub struct Lit {
pub value: LitKind,
pub span: Span,
}
#[derive(Clone)]
pub enum LitKind {
Bool(bool),
Other(Literal),
}
impl Lit {
pub fn into_token_tree(self) -> TokenTree {
let kind = match self.value {
LitKind::Bool(true) => TokenKind::Word("true".into()),
LitKind::Bool(false) => TokenKind::Word("false".into()),
LitKind::Other(l) => TokenKind::Literal(l),
};
TokenTree(proc_macro2::TokenTree {
span: self.span.0,
kind: kind,
})
}
}
impl fmt::Display for Lit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.value, f)
}
}
impl fmt::Debug for Lit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.value, f)
}
}
impl PartialEq for Lit {
fn eq(&self, other: &Lit) -> bool {
self.value == other.value
}
}
impl Eq for Lit {}
impl Hash for Lit {
fn hash<H: Hasher>(&self, hasher: &mut H) {
self.value.hash(hasher)
}
}
impl fmt::Display for LitKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
LitKind::Bool(b) => b.fmt(f),
LitKind::Other(ref l) => l.fmt(f),
}
}
}
impl fmt::Debug for LitKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
LitKind::Bool(b) => b.fmt(f),
LitKind::Other(ref l) => fmt::Display::fmt(l, f),
}
}
}
impl PartialEq for LitKind {
fn eq(&self, other: &LitKind) -> bool {
match (self, other) {
(&LitKind::Bool(b1), &LitKind::Bool(b2)) => b1 == b2,
(&LitKind::Other(ref l1), &LitKind::Other(ref l2)) => {
l1.to_string() == l2.to_string()
}
_ => false,
}
}
}
impl Eq for LitKind {}
impl Hash for LitKind {
fn hash<H: Hasher>(&self, hasher: &mut H) {
match *self {
LitKind::Bool(b) => (0u8, b).hash(hasher),
LitKind::Other(ref l) => (1u8, l.to_string()).hash(hasher),
}
}
}
#[cfg(feature = "parsing")]
pub mod parsing {
use super::*;
use escape::{cooked_byte, cooked_byte_string, cooked_char, cooked_string, raw_string};
use proc_macro2::Literal;
use synom::IResult;
use synom::space::skip_whitespace;
use unicode_xid::UnicodeXID;
fn l<T: Into<Literal>>(t: T) -> Lit {
Lit {
value: LitKind::Other(t.into()),
span: Default::default(),
}
}
named!(pub lit -> Lit, alt!(
string
|
byte_string
|
byte
|
character
|
float
|
int
|
boolean
));
named!(pub string -> Lit, alt!(
quoted_string => { |s: String| l(&s[..]) }
|
preceded!(
punct!("r"),
raw_string
) => { |(s, n): (String, _)| l(Literal::raw_string(&s[..], n)) }
));
named!(pub quoted_string -> String, delimited!(
punct!("\""),
cooked_string,
tag!("\"")
));
named!(pub byte_string -> Lit, alt!(
delimited!(
punct!("b\""),
cooked_byte_string,
tag!("\"")
) => { |vec: Vec<u8>| l(Literal::byte_string(&vec)) }
|
preceded!(
punct!("br"),
raw_string
) => { |(s, n): (String, _)| l(Literal::raw_byte_string(&s, n)) }
));
named!(pub byte -> Lit, do_parse!(
punct!("b") >>
tag!("'") >>
b: cooked_byte >>
tag!("'") >>
(l(Literal::byte_char(b)))
));
named!(pub character -> Lit, do_parse!(
punct!("'") >>
ch: cooked_char >>
tag!("'") >>
(l(ch))
));
named!(pub float -> Lit, do_parse!(
value: float_string >>
suffix: alt!(
tag!("f32")
|
tag!("f64")
|
epsilon!() => { |_| "" }
) >>
(l(Literal::float(&format!("{}{}", value, suffix))))
));
named!(pub int -> Lit, do_parse!(
value: digits >>
suffix: alt!(
tag!("isize")
|
tag!("i8")
|
tag!("i16")
|
tag!("i32")
|
tag!("i64")
|
tag!("usize")
|
tag!("u8")
|
tag!("u16")
|
tag!("u32")
|
tag!("u64")
|
epsilon!() => { |_| "" }
) >>
(l(Literal::integer(&format!("{}{}", value, suffix))))
));
named!(pub boolean -> Lit, alt!(
keyword!("true") => { |_| Lit {
span: Span::default(),
value: LitKind::Bool(true),
} }
|
keyword!("false") => { |_| Lit {
span: Span::default(),
value: LitKind::Bool(false),
} }
));
fn float_string(mut input: &str) -> IResult<&str, String> {
input = skip_whitespace(input);
let mut chars = input.chars().peekable();
match chars.next() {
Some(ch) if ch >= '0' && ch <= '9' => {}
_ => return IResult::Error,
}
let mut len = 1;
let mut has_dot = false;
let mut has_exp = false;
while let Some(&ch) = chars.peek() {
match ch {
'0'...'9' | '_' => {
chars.next();
len += 1;
}
'.' => {
if has_dot {
break;
}
chars.next();
if chars.peek()
.map(|&ch| ch == '.' || UnicodeXID::is_xid_start(ch))
.unwrap_or(false) {
return IResult::Error;
}
len += 1;
has_dot = true;
}
'e' | 'E' => {
chars.next();
len += 1;
has_exp = true;
break;
}
_ => break,
}
}
let rest = &input[len..];
if !(has_dot || has_exp || rest.starts_with("f32") || rest.starts_with("f64")) {
return IResult::Error;
}
if has_exp {
let mut has_exp_value = false;
while let Some(&ch) = chars.peek() {
match ch {
'+' | '-' => {
if has_exp_value {
break;
}
chars.next();
len += 1;
}
'0'...'9' => {
chars.next();
len += 1;
has_exp_value = true;
}
'_' => {
chars.next();
len += 1;
}
_ => break,
}
}
if !has_exp_value {
return IResult::Error;
}
}
IResult::Done(&input[len..], input[..len].replace("_", ""))
}
pub fn digits(mut input: &str) -> IResult<&str, &str> {
input = skip_whitespace(input);
let base = if input.starts_with("0x") {
16
} else if input.starts_with("0o") {
8
} else if input.starts_with("0b") {
2
} else {
10
};
let mut value = 0u64;
let mut len = if base == 10 {0} else {2};
let mut empty = true;
for b in input[len..].bytes() {
let digit = match b {
b'0'...b'9' => (b - b'0') as u64,
b'a'...b'f' => 10 + (b - b'a') as u64,
b'A'...b'F' => 10 + (b - b'A') as u64,
b'_' => {
if empty && base == 10 {
return IResult::Error;
}
len += 1;
continue;
}
_ => break,
};
if digit >= base {
return IResult::Error;
}
value = match value.checked_mul(base) {
Some(value) => value,
None => return IResult::Error,
};
value = match value.checked_add(digit) {
Some(value) => value,
None => return IResult::Error,
};
len += 1;
empty = false;
}
if empty {
IResult::Error
} else {
IResult::Done(&input[len..], &input[..len])
}
}
}
#[cfg(feature = "printing")]
mod printing {
use super::*;
use quote::{Tokens, ToTokens};
use proc_macro2::{TokenTree, TokenKind};
impl ToTokens for Lit {
fn to_tokens(&self, tokens: &mut Tokens) {
let kind = match self.value {
LitKind::Bool(true) => TokenKind::Word("true".into()),
LitKind::Bool(false) => TokenKind::Word("false".into()),
LitKind::Other(ref l) => TokenKind::Literal(l.clone()),
};
tokens.append(TokenTree {
span: self.span.0,
kind: kind,
});
}
}
}