use logos::Logos;
use nom::combinator::map;
use nom::error::ErrorKind;
use nom::error::ParseError;
use nom::Parser;
use nom::{IResult, Needed};
use nom_rule::rule;
use std::iter::{Cloned, Enumerate};
use std::slice::Iter;
use TokenKind::*;
#[derive(Logos, Clone, Copy, Debug, PartialEq)]
#[allow(clippy::upper_case_acronyms)]
enum TokenKind {
#[regex(r"[ \t\n\f]+", logos::skip)]
Whitespace,
#[token("CREATE", ignore(case))]
CREATE,
#[token("TABLE", ignore(case))]
TABLE,
#[token("(")]
LParen,
#[token(")")]
RParen,
#[token(";")]
Semicolon,
#[token(",")]
Comma,
#[regex("[a-zA-Z][a-zA-Z0-9]*")]
Ident,
}
#[derive(Clone, Debug, PartialEq)]
struct Token<'a> {
kind: TokenKind,
text: &'a str,
span: std::ops::Range<usize>,
}
#[derive(Debug, Clone)]
struct Input<'a>(&'a [Token<'a>]);
impl<'a> nom::Input for Input<'a> {
type Item = Token<'a>;
type Iter = Cloned<Iter<'a, Token<'a>>>;
type IterIndices = Enumerate<Self::Iter>;
fn input_len(&self) -> usize {
self.0.len()
}
fn take(&self, index: usize) -> Self {
Input(&self.0[0..index])
}
fn take_from(&self, index: usize) -> Self {
Input(&self.0[index..])
}
fn take_split(&self, index: usize) -> (Self, Self) {
let (prefix, suffix) = self.0.split_at(index);
(Input(suffix), Input(prefix))
}
fn position<P>(&self, predicate: P) -> Option<usize>
where
P: Fn(Self::Item) -> bool,
{
self.0.iter().position(|b| predicate(b.clone()))
}
fn iter_elements(&self) -> Self::Iter {
self.0.iter().cloned()
}
fn iter_indices(&self) -> Self::IterIndices {
self.iter_elements().enumerate()
}
fn slice_index(&self, count: usize) -> Result<usize, Needed> {
if self.0.len() >= count {
Ok(count)
} else {
Err(Needed::new(count - self.0.len()))
}
}
}
fn tokenise(input: &str) -> Vec<Token<'_>> {
let mut lex = TokenKind::lexer(input);
let mut tokens = Vec::new();
while let Some(Ok(kind)) = lex.next() {
tokens.push(Token {
kind,
text: lex.slice(),
span: lex.span(),
})
}
tokens
}
fn match_text<'a, Error: ParseError<Input<'a>>>(
text: &'a str,
) -> impl FnMut(Input<'a>) -> IResult<Input<'a>, &'a Token<'a>, Error> {
move |i| satisfy(|token: &Token<'a>| token.text == text)(i)
}
fn match_token<'a, Error: ParseError<Input<'a>>>(
kind: TokenKind,
) -> impl FnMut(Input<'a>) -> IResult<Input<'a>, &'a Token<'a>, Error> {
move |i| satisfy(|token: &Token<'a>| token.kind == kind)(i)
}
fn ident<'a, Error: ParseError<Input<'a>>>(i: Input<'a>) -> IResult<Input<'a>, &'a str, Error> {
map(satisfy(|token| token.kind == Ident), |token| token.text).parse(i)
}
fn satisfy<'a, F, Error: ParseError<Input<'a>>>(
cond: F,
) -> impl Fn(Input<'a>) -> IResult<Input<'a>, &'a Token<'a>, Error>
where
F: Fn(&Token<'a>) -> bool,
{
move |i| match i.0.first().map(|t| {
let b = cond(t);
(t, b)
}) {
Some((t, true)) => Ok((Input(&i.0[1..]), t)),
_ => Err(nom::Err::Error(Error::from_error_kind(
i,
ErrorKind::Satisfy,
))),
}
}
#[derive(Debug)]
#[allow(dead_code)]
struct CreateTableStmt {
name: String,
columns: Vec<(String, String)>,
}
fn main() {
let tokens = tokenise("CREATE TABLE users (id INT, name VARCHAR);");
let mut create_table = map(
rule! {
CREATE ~ TABLE ~ #ident ~ ^"(" ~ (#ident ~ #ident ~ ","?)* ~ ")" ~ ";"
: "CREATE TABLE statement"
},
|(_create, _table, name, _lparan, columns, _rparan, _semicolon)| CreateTableStmt {
name: name.to_string(),
columns: columns
.iter()
.map(|(name, ty, _comma)| (name.to_string(), ty.to_string()))
.collect(),
},
);
let result: IResult<Input, CreateTableStmt> = create_table.parse(Input(&tokens));
let (rest, stmt) = result.unwrap();
println!("{stmt:?}");
assert!(rest.0.is_empty());
assert_eq!(stmt.name, "users");
assert_eq!(
&stmt.columns,
&[
("id".to_string(), "INT".to_string()),
("name".to_string(), "VARCHAR".to_string())
]
);
}