scyllax_parser/
common.rs

1//! common parsing functions
2use std::fmt::Display;
3
4use nom::{
5    branch::alt,
6    bytes::complete::{tag, tag_no_case},
7    character::complete::{alpha1, alphanumeric1, digit1},
8    combinator::{map, recognize},
9    error::{ErrorKind, ParseError},
10    multi::many0_count,
11    sequence::{delimited, pair},
12    IResult, InputLength,
13};
14
15use crate::reserved::parse_cql_keyword;
16
17/// Represents a column
18#[derive(Debug, PartialEq)]
19pub enum Column {
20    /// The column being quried has a name that's a string.
21    /// Note: this can include double-quote escaped identifiers.
22    Identifier(String),
23    /// The column being queried is an asterisk
24    Asterisk,
25}
26
27/// Represents a query variable.
28#[derive(Debug, PartialEq)]
29pub enum Variable {
30    /// The variable is a question mark
31    Placeholder,
32    /// The variable is a named variable
33    NamedVariable(String),
34}
35
36impl Display for Variable {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        match self {
39            Variable::Placeholder => write!(f, "?"),
40            Variable::NamedVariable(ident) => write!(f, ":{}", ident),
41        }
42    }
43}
44
45/// Parses a [`Variable`]
46pub fn parse_variable(input: &str) -> IResult<&str, Variable> {
47    alt((
48        map(parse_placeholder, |_| Variable::Placeholder),
49        map(parse_named_variable, |ident| {
50            // check if the variable is reserved. if it is, throw an error
51            if parse_cql_keyword(ident).is_ok() {
52                panic!("variable `{ident}` is a reserved keyword");
53            }
54
55            Variable::NamedVariable(ident.to_string())
56        }),
57    ))(input)
58}
59
60/// Parses a named variable in the format `:identifier`
61fn parse_named_variable(input: &str) -> IResult<&str, &str> {
62    let (input, _) = tag(":")(input)?;
63    parse_identifier(input)
64}
65
66/// Represents a query value -- either a variable or a literal
67#[derive(Debug, PartialEq)]
68pub enum Value {
69    /// The value is a variable
70    Variable(Variable),
71    /// The value is a literal
72    Literal(String),
73    /// The value is a number
74    Number(usize),
75    /// The value is a boolean
76    Boolean(bool),
77}
78
79/// Parses a [`Value`]
80pub fn parse_value(input: &str) -> IResult<&str, Value> {
81    alt((
82        map(parse_boolean, Value::Boolean),
83        map(parse_variable, Value::Variable),
84        map(parse_number, Value::Number),
85        map(parse_string, Value::Literal), // must be last!
86    ))(input)
87}
88
89/// Parses a [`Value::Literal`].
90/// If there are any escaped quotes, they should be included in the output.
91/// e.g. `\"` should be parsed as `\"`
92/// - `foo` -> `foo`
93fn parse_string(input: &str) -> IResult<&str, String> {
94    let (input, alpha) = alt((
95        // barf
96        map(parse_escaped, |x| format!("\"{x}\"")),
97        map(alpha1, |x: &str| x.to_string()),
98    ))(input)?;
99
100    Ok((input, alpha.clone()))
101}
102
103/// Parses an alpha string that's escaped with double quotes
104pub fn parse_escaped(input: &str) -> IResult<&str, String> {
105    let (input, alpha) = delimited(tag("\""), alpha1, tag("\""))(input)?;
106    Ok((input, alpha.to_string()))
107}
108
109/// Parses a [`Value::Number`]
110fn parse_number(input: &str) -> IResult<&str, usize> {
111    let (input, number) = digit1(input)?;
112    Ok((input, number.parse().unwrap()))
113}
114
115/// Parses a [`Value::Boolean`]
116fn parse_boolean(input: &str) -> IResult<&str, bool> {
117    let (input, boolean) = alt((
118        map(tag_no_case("true"), |_| true),
119        map(tag_no_case("false"), |_| false),
120    ))(input)?;
121
122    Ok((input, boolean))
123}
124
125/// Parses a Rust flavored variable wrapped in double quotes
126pub fn parse_string_escaped_rust_flavored_variable(input: &str) -> IResult<&str, String> {
127    let (input, alpha) = delimited(tag("\""), parse_rust_flavored_variable, tag("\""))(input)?;
128    Ok((input, alpha.to_string()))
129}
130
131/// Parses a Rust flavored variable
132pub fn parse_rust_flavored_variable(input: &str) -> IResult<&str, &str> {
133    recognize(pair(
134        alt((alpha1, tag("_"))),
135        many0_count(alt((alphanumeric1, tag("_")))),
136    ))(input)
137}
138
139/// Parses an identifier on.. idk tbd
140pub fn parse_identifier(input: &str) -> IResult<&str, &str> {
141    parse_rust_flavored_variable(input)
142}
143
144/// Parses a [`Variable::Placeholder`]
145fn parse_placeholder(input: &str) -> IResult<&str, String> {
146    let (input, _) = tag("?")(input)?;
147    Ok((input, "?".to_string()))
148}
149
150/// Parses a limit clause
151pub fn parse_limit_clause(input: &str) -> IResult<&str, Value> {
152    let (input, _) = tag_no_case("limit ")(input)?;
153    let (input, limit) = parse_value(input)?;
154
155    Ok((input, limit))
156}
157
158/// Indicates that the input is at the end of the file
159pub(crate) fn eof<I: Copy + InputLength, E: ParseError<I>>(input: I) -> IResult<I, I, E> {
160    if input.input_len() == 0 {
161        Ok((input, input))
162    } else {
163        Err(nom::Err::Error(E::from_error_kind(input, ErrorKind::Eof)))
164    }
165}
166
167#[cfg(test)]
168mod test {
169    #[test]
170    fn test_regular_literal() {
171        assert_eq!(super::parse_string("foo"), Ok(("", "foo".to_string())));
172    }
173
174    // FIXME: this is broken
175    #[test]
176    fn test_escaped_literal() {
177        assert_eq!(
178            super::parse_string(r#""foo""#),
179            Ok(("", r#""foo""#.to_string()))
180        );
181    }
182}