use bstringify::bstringify;
use std::{mem::MaybeUninit, str::FromStr};
use super::{Ident, QuoteStyle};
macro_rules! keywords {
(@__enum $(#[$meta:meta])* $vis:vis $enum_name:ident { $( $variant:ident ),* $(,)? } ) => {
$(#[$meta])*
$vis enum $enum_name {
$( $variant ),*
}
};
(@__match_bytes $enum_name:ident $fn_name:ident $($variant:ident = $variant_name:expr),* ) => {
impl $enum_name {
#[inline]
fn $fn_name(s: &[u8]) -> Result<$enum_name, ()> {
Ok(match s {
$( $variant_name => $enum_name::$variant, )*
_ => return Err(()),
})
}
}
};
(@__as_str $enum_name:ident $($variant:ident = $variant_name:expr),*) => {
impl $enum_name {
pub fn as_str(&self) -> &'static str {
match self {
$( $enum_name::$variant => $variant_name, )*
}
}
}
};
(@__max_len $enum_name:ident $var_name:ident $($a:expr),*) => {
impl $enum_name {
const $var_name: usize = {
const fn __max_len<const N: usize>(xs: [&[u8]; N]) -> usize {
let mut max = 0;
let mut i = 0;
while i < N {
let x = xs[i];
max = if max > x.len() { max } else { x.len() };
i += 1;
}
max
}
__max_len([$($a),*])
};
}
};
(
$(#[$meta:meta])*
$vis:vis enum $enum_name:ident {
$( $variant:ident ),*
$(,)?
}
) => {
keywords!(@__enum
$(#[$meta])*
$vis $enum_name {
$( $variant ),*
});
keywords!(@__as_str $enum_name $( $variant = stringify!($variant) ),*);
impl std::fmt::Display for $enum_name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
keywords!(@__max_len $enum_name MAX_LEN $( bstringify!($variant) ),*);
keywords!(@__match_bytes $enum_name lookup_bytes $( $variant = bstringify!($variant) ),*);
impl $enum_name {
fn lookup_str(s: &str) -> Result<$enum_name, ()> {
let len = s.len();
if len > Self::MAX_LEN {
Err(())
} else {
let mut upper = const { [MaybeUninit::<u8>::uninit(); Self::MAX_LEN] };
for (t, s) in upper.iter_mut().zip(s.as_bytes().iter().copied()) {
t.write(s.to_ascii_uppercase());
}
let upper = unsafe {
std::mem::transmute::<[MaybeUninit<u8>; Self::MAX_LEN], [u8; Self::MAX_LEN]>(
upper,
)
};
Self::lookup_bytes(&upper[..len])
}
}
}
};
}
impl FromStr for Keyword {
type Err = ();
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::lookup_str(s)
}
}
keywords! {
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[allow(clippy::upper_case_acronyms)]
pub enum Keyword {
ALL,
ALTER,
AND,
ANY,
AS,
ASC,
BETWEEN,
BY,
CHAR,
CHECK,
CLUSTER,
COMPRESS,
CONNECT,
CREATE,
DATE,
DECIMAL,
DEFAULT,
DELETE,
DESC,
DISTINCT,
DROP,
ELSE,
EXCEPT,
EXCLUSIVE,
EXISTS,
FLOAT,
FOR,
FROM,
GRANT,
GROUP,
HAVING,
IDENTIFIED,
IN,
INDEX,
INSERT,
INTEGER,
INTERSECT,
INTO,
IS,
LIKE,
LOCK,
LONG,
MINUS,
MODE,
NOCOMPRESS,
NOT,
NOWAIT,
NULL,
NUMBER,
OF,
ON,
OPTION,
OR,
ORDER,
PCTFREE,
PRIOR,
PUBLIC,
RAW,
RENAME,
RESOURCE,
REVOKE,
SELECT,
SET,
SHARE,
SIZE,
SMALLINT,
START,
SYNONYM,
TABLE,
THEN,
TO,
TRIGGER,
UNION,
UNIQUE,
UPDATE,
VALUES,
VARCHAR,
VARCHAR2,
VIEW,
WHERE,
WITH,
}
}
keywords! {
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[allow(clippy::upper_case_acronyms, non_camel_case_types)]
pub enum Reserved {
APPLY,
CASE,
CHAR_CS,
CONTAINERS,
COUNT,
CROSS,
CURRENT,
DAY,
END,
ERROR,
ESCAPE,
EXCLUDE,
FETCH,
FIRST,
FOLLOWING,
FULL,
GROUPS,
HOUR,
IGNORE,
INFINITE,
INNER,
JOIN,
LAST,
LATERAL,
LEFT,
LIKE2,
LIKE4,
LIKEC,
LOCKED,
MINUTE,
MONTH,
NAN,
NATURAL,
NCHAR_CS,
NEXT,
NO,
NULLS,
OFFSET,
ONLY,
OTHERS,
OUTER,
OVER,
OVERFLOW,
PARTITION,
PERCENT,
PRECEDING,
RANGE,
REGEXP_LIKE,
RESPECT,
RIGHT,
ROW,
ROWS,
SECOND,
SHARDS,
SIBLINGS,
SKIP,
SOME,
TIES,
TIMEZONE_ABBR,
TIMEZONE_HOUR,
TIMEZONE_MINUTE,
TIMEZONE_REGION,
TRUNCATE,
UNBOUNDED,
USING,
WAIT,
WHEN,
WINDOW,
WITHIN,
WITHOUT,
YEAR,
}
}
impl Reserved {
pub(crate) fn matches(&self, ident: &Ident<'_>) -> bool {
Self::lookup_ident(ident).as_ref() == Ok(self)
}
pub(crate) fn lookup_ident(ident: &Ident<'_>) -> Result<Reserved, ()> {
match ident.1 {
QuoteStyle::None => Self::lookup_str(ident.0),
QuoteStyle::Quoted => Err(()),
}
}
}
keywords! {
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[allow(clippy::upper_case_acronyms, non_camel_case_types)]
pub(crate) enum BuiltIn {
ABS,
ACOS,
ADD_MONTHS,
ANY_VALUE,
ASCII,
ASCIISTR,
ASIN,
ATAN,
ATAN2,
AVG,
BIN_TO_NUM,
BITAND,
BITMAP_BIT_POSITION,
BITMAP_BUCKET_NUMBER,
BITMAP_CONSTRUCT_AGG,
BITMAP_COUNT,
BITMAP_OR_AGG,
BIT_AND_AGG,
BIT_OR_AGG,
BIT_XOR_AGG,
CARDINALITY,
CEIL,
CHARTOROWID,
CHECKSUM,
CHR,
COALESCE,
COLLATION,
COMPOSE,
CONCAT,
CONVERT,
CORR,
CORR_K,
CORR_S,
COS,
COSH,
COUNT,
COVAR_POP,
COVAR_SAMP,
CURRENT_TIMESTAMP,
DECODE,
EXP,
EXTRACT,
FIRST_VALUE,
FLOOR,
GREATEST,
LAG,
LAST_DAY,
LAST_VALUE,
LEAD,
LEAST,
LISTAGG,
LOCALTIMESTAMP,
LOG,
LOWER,
LN,
MAX,
MEDIAN,
MIN,
MOD,
NTH_VALUE,
RANK,
STDDEV,
STDDEV_POP,
STDDEV_SAMP,
SUM,
TRANSLATE,
UNISTR,
UPPER,
VARIANCE,
VAR_POP,
VAR_SAMP,
VSIZE,
WIDTH_BUCKET,
}
}
impl BuiltIn {
pub(crate) fn lookup_ident(ident: &Ident<'_>) -> Result<BuiltIn, ()> {
match ident.1 {
QuoteStyle::None => Self::lookup_str(ident.0),
QuoteStyle::Quoted => Self::lookup_bytes(ident.0.as_bytes()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_str() {
assert_eq!(Keyword::from_str(""), Err(()));
assert_eq!(Keyword::from_str("AND"), Ok(Keyword::AND));
assert_eq!(Keyword::from_str("🐂AND"), Err(()));
assert_eq!(Keyword::from_str("and"), Ok(Keyword::AND));
assert_eq!(Keyword::from_str("aNd"), Ok(Keyword::AND));
assert_eq!(
Keyword::from_str("asdfasdsfasdfasdfdfasdfasdfdsfdsfad"),
Err(())
);
}
}