1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
//! common parsing functions
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;

/// Represents a column
#[derive(Debug, PartialEq)]
pub enum Column {
    /// The column being quried has a name that's a string.
    /// Note: this can include double-quote escaped identifiers.
    Identifier(String),
    /// The column being queried is an asterisk
    Asterisk,
}

/// Represents a query variable.
#[derive(Debug, PartialEq)]
pub enum Variable {
    /// The variable is a question mark
    Placeholder,
    /// The variable is a named variable
    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),
        }
    }
}

/// Parses a [`Variable`]
pub fn parse_variable(input: &str) -> IResult<&str, Variable> {
    alt((
        map(parse_placeholder, |_| Variable::Placeholder),
        map(parse_named_variable, |ident| {
            // check if the variable is reserved. if it is, throw an error
            if parse_cql_keyword(ident).is_ok() {
                panic!("variable `{ident}` is a reserved keyword");
            }

            Variable::NamedVariable(ident.to_string())
        }),
    ))(input)
}

/// Parses a named variable in the format `:identifier`
fn parse_named_variable(input: &str) -> IResult<&str, &str> {
    let (input, _) = tag(":")(input)?;
    parse_identifier(input)
}

/// Represents a query value -- either a variable or a literal
#[derive(Debug, PartialEq)]
pub enum Value {
    /// The value is a variable
    Variable(Variable),
    /// The value is a literal
    Literal(String),
    /// The value is a number
    Number(usize),
    /// The value is a boolean
    Boolean(bool),
}

/// Parses a [`Value`]
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), // must be last!
    ))(input)
}

/// Parses a [`Value::Literal`].
/// If there are any escaped quotes, they should be included in the output.
/// e.g. `\"` should be parsed as `\"`
/// - `foo` -> `foo`
fn parse_string(input: &str) -> IResult<&str, String> {
    let (input, alpha) = alt((
        // barf
        map(parse_escaped, |x| format!("\"{x}\"")),
        map(alpha1, |x: &str| x.to_string()),
    ))(input)?;

    Ok((input, alpha.clone()))
}

/// Parses an alpha string that's escaped with double quotes
pub fn parse_escaped(input: &str) -> IResult<&str, String> {
    let (input, alpha) = delimited(tag("\""), alpha1, tag("\""))(input)?;
    Ok((input, alpha.to_string()))
}

/// Parses a [`Value::Number`]
fn parse_number(input: &str) -> IResult<&str, usize> {
    let (input, number) = digit1(input)?;
    Ok((input, number.parse().unwrap()))
}

/// Parses a [`Value::Boolean`]
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))
}

/// Parses a Rust flavored variable wrapped in double quotes
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()))
}

/// Parses a Rust flavored variable
pub fn parse_rust_flavored_variable(input: &str) -> IResult<&str, &str> {
    recognize(pair(
        alt((alpha1, tag("_"))),
        many0_count(alt((alphanumeric1, tag("_")))),
    ))(input)
}

/// Parses an identifier on.. idk tbd
pub fn parse_identifier(input: &str) -> IResult<&str, &str> {
    parse_rust_flavored_variable(input)
}

/// Parses a [`Variable::Placeholder`]
fn parse_placeholder(input: &str) -> IResult<&str, String> {
    let (input, _) = tag("?")(input)?;
    Ok((input, "?".to_string()))
}

/// Parses a limit clause
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))
}

/// Indicates that the input is at the end of the file
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())));
    }

    // FIXME: this is broken
    #[test]
    fn test_escaped_literal() {
        assert_eq!(
            super::parse_string(r#""foo""#),
            Ok(("", r#""foo""#.to_string()))
        );
    }
}