use std::fmt::Display;
use nom::{
branch::alt,
bytes::complete::{tag, tag_no_case},
character::complete::{alpha1, alphanumeric1, digit1},
combinator::{map, recognize},
error::{ErrorKind, ParseError},
multi::many0_count,
sequence::{delimited, pair},
IResult, InputLength,
};
use crate::reserved::parse_cql_keyword;
#[derive(Debug, PartialEq)]
pub enum Column {
Identifier(String),
Asterisk,
}
#[derive(Debug, PartialEq)]
pub enum Variable {
Placeholder,
NamedVariable(String),
}
impl Display for Variable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Variable::Placeholder => write!(f, "?"),
Variable::NamedVariable(ident) => write!(f, ":{}", ident),
}
}
}
pub fn parse_variable(input: &str) -> IResult<&str, Variable> {
alt((
map(parse_placeholder, |_| Variable::Placeholder),
map(parse_named_variable, |ident| {
if parse_cql_keyword(ident).is_ok() {
panic!("variable `{ident}` is a reserved keyword");
}
Variable::NamedVariable(ident.to_string())
}),
))(input)
}
fn parse_named_variable(input: &str) -> IResult<&str, &str> {
let (input, _) = tag(":")(input)?;
parse_identifier(input)
}
#[derive(Debug, PartialEq)]
pub enum Value {
Variable(Variable),
Literal(String),
Number(usize),
Boolean(bool),
}
pub fn parse_value(input: &str) -> IResult<&str, Value> {
alt((
map(parse_boolean, Value::Boolean),
map(parse_variable, Value::Variable),
map(parse_number, Value::Number),
map(parse_string, Value::Literal), ))(input)
}
fn parse_string(input: &str) -> IResult<&str, String> {
let (input, alpha) = alt((
map(parse_escaped, |x| format!("\"{x}\"")),
map(alpha1, |x: &str| x.to_string()),
))(input)?;
Ok((input, alpha.clone()))
}
pub fn parse_escaped(input: &str) -> IResult<&str, String> {
let (input, alpha) = delimited(tag("\""), alpha1, tag("\""))(input)?;
Ok((input, alpha.to_string()))
}
fn parse_number(input: &str) -> IResult<&str, usize> {
let (input, number) = digit1(input)?;
Ok((input, number.parse().unwrap()))
}
fn parse_boolean(input: &str) -> IResult<&str, bool> {
let (input, boolean) = alt((
map(tag_no_case("true"), |_| true),
map(tag_no_case("false"), |_| false),
))(input)?;
Ok((input, boolean))
}
pub fn parse_string_escaped_rust_flavored_variable(input: &str) -> IResult<&str, String> {
let (input, alpha) = delimited(tag("\""), parse_rust_flavored_variable, tag("\""))(input)?;
Ok((input, alpha.to_string()))
}
pub fn parse_rust_flavored_variable(input: &str) -> IResult<&str, &str> {
recognize(pair(
alt((alpha1, tag("_"))),
many0_count(alt((alphanumeric1, tag("_")))),
))(input)
}
pub fn parse_identifier(input: &str) -> IResult<&str, &str> {
parse_rust_flavored_variable(input)
}
fn parse_placeholder(input: &str) -> IResult<&str, String> {
let (input, _) = tag("?")(input)?;
Ok((input, "?".to_string()))
}
pub fn parse_limit_clause(input: &str) -> IResult<&str, Value> {
let (input, _) = tag_no_case("limit ")(input)?;
let (input, limit) = parse_value(input)?;
Ok((input, limit))
}
pub(crate) fn eof<I: Copy + InputLength, E: ParseError<I>>(input: I) -> IResult<I, I, E> {
if input.input_len() == 0 {
Ok((input, input))
} else {
Err(nom::Err::Error(E::from_error_kind(input, ErrorKind::Eof)))
}
}
#[cfg(test)]
mod test {
#[test]
fn test_regular_literal() {
assert_eq!(super::parse_string("foo"), Ok(("", "foo".to_string())));
}
#[test]
fn test_escaped_literal() {
assert_eq!(
super::parse_string(r#""foo""#),
Ok(("", r#""foo""#.to_string()))
);
}
}