use combine::*;
use combine::char::char;
use combine::range::{range, take, take_while};
use combine::primitives::{Consumed, RangeStream};
use decor::InternalString;
use parser::trivia::{newline, ws, ws_newlines};
use parser::errors::CustomError;
use std::char;
parse!(string() -> InternalString, {
choice((
ml_basic_string(),
basic_string(),
ml_literal_string(),
literal_string().map(|s: &'a str| s.into()),
))
});
#[inline]
fn is_basic_unescaped(c: char) -> bool {
match c {
'\u{20}'...'\u{21}' | '\u{23}'...'\u{5B}' | '\u{5D}'...'\u{10FFFF}' => true,
_ => false,
}
}
#[inline]
fn is_escape_char(c: char) -> bool {
match c {
'\\' | '"' | 'b' | '/' | 'f' | 'n' | 'r' | 't' | 'u' | 'U' => true,
_ => false,
}
}
parse!(escape() -> char, {
satisfy(is_escape_char)
.message("While parsing escape sequence")
.then(|c| {
parser(move |input| {
match c {
'b' => Ok(('\u{8}', Consumed::Empty(input))),
'f' => Ok(('\u{c}', Consumed::Empty(input))),
'n' => Ok(('\n', Consumed::Empty(input))),
'r' => Ok(('\r', Consumed::Empty(input))),
't' => Ok(('\t', Consumed::Empty(input))),
'u' => hexescape(4).parse_stream(input),
'U' => hexescape(8).parse_stream(input),
_ => Ok((c, Consumed::Empty(input))),
}
})
})
});
parse!(hexescape(n: usize) -> char, {
take(*n)
.and_then(|s| u32::from_str_radix(s, 16))
.and_then(|h| char::from_u32(h).ok_or_else(|| CustomError::InvalidHexEscape(h)))
});
const ESCAPE: char = '\\';
parse!(basic_char() -> char, {
satisfy(|c| is_basic_unescaped(c) || c == ESCAPE)
.then(|c| parser(move |input| {
match c {
ESCAPE => escape().parse_stream(input),
_ => Ok((c, Consumed::Empty(input))),
}
}))
});
const QUOTATION_MARK: char = '"';
parse!(basic_string() -> InternalString, {
between(char(QUOTATION_MARK), char(QUOTATION_MARK),
many(basic_char()))
.message("While parsing a Basic String")
});
#[inline]
fn is_ml_basic_unescaped(c: char) -> bool {
match c {
'\u{20}'...'\u{5B}' | '\u{5D}'...'\u{10FFFF}' => true,
_ => false,
}
}
const ML_BASIC_STRING_DELIM: &str = "\"\"\"";
parse!(ml_basic_char() -> char, {
satisfy(|c| is_ml_basic_unescaped(c) || c == ESCAPE)
.then(|c| parser(move |input| {
match c {
ESCAPE => escape().parse_stream(input),
_ => Ok((c, Consumed::Empty(input))),
}
}))
});
parse!(try_eat_escaped_newline() -> (), {
skip_many(try((
char(ESCAPE),
ws(),
ws_newlines(),
)))
});
parse!(ml_basic_body() -> InternalString, {
optional(newline())
.skip(try_eat_escaped_newline())
.with(
many(
not_followed_by(range(ML_BASIC_STRING_DELIM))
.with(
choice((
newline(),
ml_basic_char(),
))
)
.skip(try_eat_escaped_newline())
)
)
});
parse!(ml_basic_string() -> InternalString, {
between(range(ML_BASIC_STRING_DELIM), range(ML_BASIC_STRING_DELIM),
ml_basic_body())
.message("While parsing a Multiline Basic String")
});
const APOSTROPHE: char = '\'';
#[inline]
fn is_literal_char(c: char) -> bool {
match c {
'\u{09}' | '\u{20}'...'\u{26}' | '\u{28}'...'\u{10FFFF}' => true,
_ => false,
}
}
parse!(literal_string() -> &'a str, {
between(char(APOSTROPHE), char(APOSTROPHE),
take_while(is_literal_char))
.message("While parsing a Literal String")
});
const ML_LITERAL_STRING_DELIM: &str = "'''";
#[inline]
fn is_ml_literal_char(c: char) -> bool {
match c {
'\u{09}' | '\u{20}'...'\u{10FFFF}' => true,
_ => false,
}
}
parse!(ml_literal_body() -> InternalString, {
optional(newline())
.with(
many(
not_followed_by(range(ML_LITERAL_STRING_DELIM))
.with(
choice((
newline(),
satisfy(is_ml_literal_char),
))
)
)
)
});
parse!(ml_literal_string() -> InternalString, {
between(range(ML_LITERAL_STRING_DELIM), range(ML_LITERAL_STRING_DELIM),
ml_literal_body())
.message("While parsing a Multiline Literal String")
});