use pretty_assertions::assert_eq;
use super::*;
impl<'s> From<(TokenType<'s>, (u32, u32))> for Token<'s> {
fn from((ttype, (line, col)): (TokenType<'s>, (u32, u32))) -> Self {
Self {
ttype,
loc: Location { line, col },
}
}
}
impl From<(ErrorType, (u32, u32))> for Error {
fn from((etype, loc): (ErrorType, (u32, u32))) -> Self {
Self {
etype,
loc: loc.into(),
}
}
}
fn scan(s: &str) -> Vec<Result<Token<'_>>> {
Scanner::new(s).collect()
}
#[test]
fn test_position_empty() {
let mut s = Scanner::new("");
assert_eq!(0, s.position());
assert_eq!(None, s.next());
assert_eq!(None, s.next());
assert_eq!(None, s.next());
assert_eq!(0, s.position());
}
#[test]
fn test_position_unclosed_comment() {
let mut s = Scanner::new("/* unclosed comment");
assert_eq!(0, s.position());
assert_eq!(
Some(Err(Error {
etype: ErrorType::UnexpectedEof { expected: None },
loc: (1, 20).into()
})),
s.next()
);
assert_eq!(19, s.position());
}
#[test]
fn test_position_regular() {
let mut s = Scanner::new("123 SELECT /* asdf */\n2").peekable();
assert_eq!(0, s.position());
assert_eq!(
Some(&Ok((TokenType::Integer("123"), (1, 1)).into())),
s.peek()
);
assert_eq!(3, s.position());
let _ = s.next();
assert_eq!(
Some(Ok((TokenType::Keyword(Keyword::SELECT), (1, 5)).into())),
s.next()
);
assert_eq!(10, s.position());
assert_eq!(
Some(&Ok((
TokenType::Comment(Comment(" asdf ", CommentStyle::Block)),
(1, 12)
)
.into())),
s.peek()
);
assert_eq!(21, s.position());
let _ = s.next();
assert_eq!(
Some(&Ok((TokenType::Integer("2"), (2, 1)).into())),
s.peek()
);
assert_eq!(23, s.position());
let _ = s.next();
assert_eq!(None, s.next());
assert_eq!(23, s.position());
}
#[test]
fn test_next_and_peek_intermix() {
let mut s = Scanner::new("123 ( asdf )").peekable();
assert_eq!(
Some(Ok((TokenType::Integer("123"), (1, 1)).into())),
s.next()
);
assert_eq!(Some(&Ok((TokenType::LeftParen, (1, 5)).into())), s.peek());
assert_eq!(Some(&Ok((TokenType::LeftParen, (1, 5)).into())), s.peek());
assert_eq!(Some(Ok((TokenType::LeftParen, (1, 5)).into())), s.next());
assert_eq!(
Some(Ok((
TokenType::Identifier(Ident("asdf", QuoteStyle::None), None),
(1, 7)
)
.into())),
s.next()
);
assert_eq!(Some(Ok((TokenType::RightParen, (1, 12)).into())), s.next());
assert_eq!(None, s.peek());
assert_eq!(None, s.next());
assert_eq!(None, s.peek());
}
#[test]
fn test_integers() {
let s = r#"123 +456 + -7890"#;
assert_eq!(
scan(s),
vec![
Ok((TokenType::Integer("123"), (1, 1)).into()),
Ok((TokenType::Plus, (1, 5)).into()),
Ok((TokenType::Integer("456"), (1, 6)).into()),
Ok((TokenType::Plus, (1, 10)).into()),
Ok((TokenType::Minus, (1, 12)).into()),
Ok((TokenType::Integer("7890"), (1, 13)).into()),
]
);
}
#[test]
fn test_floats() {
let s = r#"
1. 1.0 1.2 1.23 .123
1.23e10 1.23E+10
1.23e-8 1.E6 9.e-5
1.f 1.0f 1.d 1.0d
1.2e3f 1.23e-4d
.1e+2d
"#;
assert_eq!(
scan(s),
vec![
Ok((TokenType::Float("1."), (2, 1)).into()),
Ok((TokenType::Float("1.0"), (2, 4)).into()),
Ok((TokenType::Float("1.2"), (2, 8)).into()),
Ok((TokenType::Float("1.23"), (2, 12)).into()),
Ok((TokenType::Float(".123"), (2, 17)).into()),
Ok((TokenType::Float("1.23e10"), (3, 1)).into()),
Ok((TokenType::Float("1.23E+10"), (3, 10)).into()),
Ok((TokenType::Float("1.23e-8"), (4, 1)).into()),
Ok((TokenType::Float("1.E6"), (4, 10)).into()),
Ok((TokenType::Float("9.e-5"), (4, 20)).into()),
Ok((TokenType::Float("1.f"), (5, 1)).into()),
Ok((TokenType::Float("1.0f"), (5, 5)).into()),
Ok((TokenType::Float("1.d"), (5, 10)).into()),
Ok((TokenType::Float("1.0d"), (5, 14)).into()),
Ok((TokenType::Float("1.2e3f"), (6, 1)).into()),
Ok((TokenType::Float("1.23e-4d"), (6, 10)).into()),
Ok((TokenType::Float(".1e+2d"), (7, 1)).into()),
]
);
}
#[test]
fn test_line_comments() {
let s = r#"
123 -- abc
-- qwerty qwerty
1-3--asdf"#;
#[rustfmt::skip]
assert_eq!(
scan(s),
vec![
Ok((TokenType::Integer("123"), (2, 1)).into()),
Ok((TokenType::Comment(Comment(" abc", CommentStyle::Line)), (2, 5)).into()),
Ok((TokenType::Comment(Comment(" qwerty qwerty", CommentStyle::Line)), (3, 1)).into()),
Ok((TokenType::Integer("1"), (4, 1)).into()),
Ok((TokenType::Minus, (4, 2)).into()),
Ok((TokenType::Integer("3"), (4, 3)).into()),
Ok((TokenType::Comment(Comment("asdf", CommentStyle::Line)), (4, 4)).into()),
]
);
}
#[test]
fn test_block_comments() {
let s = r#"/*
123
-- abc
*/
0/1* 3/*--as/*df*/
"#;
#[rustfmt::skip]
assert_eq!(
scan(s),
vec![
Ok((TokenType::Comment(Comment("\n123\n -- abc\n", CommentStyle::Block)), (1, 1)).into()),
Ok((TokenType::Integer("0"), (5, 1)).into()),
Ok((TokenType::Slash, (5, 2)).into()),
Ok((TokenType::Integer("1"), (5, 3)).into()),
Ok((TokenType::Star, (5, 4)).into()),
Ok((TokenType::Integer("3"), (5, 6)).into()),
Ok((TokenType::Comment(Comment("--as/*df", CommentStyle::Block)), (5, 7)).into()),
]
);
}
#[test]
fn test_text() {
macro_rules! assert_borrowed_text {
($t:expr) => {{
let t = $t;
if !matches!(
t,
Ok(Token {
ttype: TokenType::Text(Text::Regular(Cow::Borrowed(_)), _),
loc: _
})
) {
panic!("Expected borrowed string, but got: {:?}", t);
}
}};
}
let tokens = scan(r#"''"#);
#[rustfmt::skip]
assert_eq!(
&tokens,
&[Ok((TokenType::Text(Text::Regular("".into()), NationalStyle::None), (1, 1)).into())]
);
assert_borrowed_text!(&tokens[0]);
let tokens = scan(r#" 'hello, world' N'how are you?'"#);
#[rustfmt::skip]
assert_eq!(
&tokens,
&[
Ok((TokenType::Text(Text::Regular("hello, world".into()), NationalStyle::None), (1, 2)).into()),
Ok((TokenType::Text(Text::Regular("how are you?".into()), NationalStyle::National), (1, 17)).into())
]
);
assert_borrowed_text!(&tokens[0]);
assert_borrowed_text!(&tokens[1]);
let tokens = scan(
r#" n'hello,
world' 'how are
you?'"#,
);
#[rustfmt::skip]
assert_eq!(
&tokens,
&[
Ok((TokenType::Text(Text::Regular("hello,\nworld".into()), NationalStyle::National), (1, 2)).into()),
Ok((TokenType::Text(Text::Regular("how are\nyou?".into()), NationalStyle::None), (2, 8)).into())
]
);
assert_borrowed_text!(&tokens[0]);
assert_borrowed_text!(&tokens[1]);
let tokens = scan(r#" ''''"#);
#[rustfmt::skip]
assert_eq!(
&tokens,
&[Ok((TokenType::Text(Text::Regular("'".into()), NationalStyle::None), (1, 3)).into())]
);
let tokens = scan(r#" 'raison d''etre'"#);
#[rustfmt::skip]
assert_eq!(
&tokens,
&[Ok((TokenType::Text(Text::Regular("raison d'etre".into()), NationalStyle::None), (1, 2)).into())]
);
let tokens = scan("'abc''\n' N'brian\no''brien'");
#[rustfmt::skip]
assert_eq!(
&tokens,
&[
Ok((TokenType::Text(Text::Regular("abc'\n".into()), NationalStyle::None), (1, 1)).into()),
Ok((TokenType::Text(Text::Regular("brian\no'brien".into()), NationalStyle::National), (2, 3)).into())
]
);
}
#[test]
fn test_unterminated_text() {
assert_eq!(
scan("'asdf"),
&[Err((ErrorType::UnterminatedString, (1, 1)).into())],
);
assert_eq!(
scan("'asdf''"),
&[Err((ErrorType::UnterminatedString, (1, 1)).into())],
);
assert_eq!(
scan("N'asdf''"),
&[Err((ErrorType::UnterminatedString, (1, 1)).into())],
);
assert_eq!(
scan(" 'asdf''qwer''''"),
&[Err((ErrorType::UnterminatedString, (1, 2)).into())],
);
}
#[test]
fn test_quoted_text() {
fn qt(s: &str, loc: Location, enc: NationalStyle) -> Token<'_> {
Token {
ttype: TokenType::Text(Text::Quoted(s.try_into().unwrap()), enc),
loc,
}
}
assert_eq!(
scan(r#"q'{asdf}' Q'{asdf}}' Q'{asdf}b}'"#),
vec![
Ok(qt("{asdf}", (1, 1).into(), NationalStyle::None)),
Ok(qt("{asdf}}", (1, 13).into(), NationalStyle::None)),
Ok(qt("{asdf}b}", (1, 26).into(), NationalStyle::None))
]
);
assert_eq!(
scan(r#"q'{as'df}' q'[as[df]'"#),
vec![
Ok(qt("{as'df}", (1, 1).into(), NationalStyle::None)),
Ok(qt("[as[df]", (1, 12).into(), NationalStyle::None))
]
);
assert_eq!(
scan(r#"Q'<asdf>' q'(a))sdf)'"#),
vec![
Ok(qt("<asdf>", (1, 1).into(), NationalStyle::None)),
Ok(qt("(a))sdf)", (1, 12).into(), NationalStyle::None))
]
);
assert_eq!(
scan(r#"Q'|as'''df|'"#),
vec![Ok(qt("|as'''df|", (1, 1).into(), NationalStyle::None))]
);
#[rustfmt::skip]
assert_eq!(
scan(
r#"
q'%
asdf
asdf
%'"#
),
vec![Ok(qt("%\n asdf\n asdf\n%", (2, 1).into(), NationalStyle::None))]
);
#[rustfmt::skip]
assert_eq!(
scan(
r#" Nq'{nat}' nQ'()' NQ'.a.b.c.'
nq'%
asdf
asdf
%'"#
),
vec![
Ok(qt("{nat}", (1, 2).into(), NationalStyle::National)),
Ok(qt("()", (1, 12).into(), NationalStyle::National)),
Ok(qt(".a.b.c.", (1, 19).into(), NationalStyle::National)),
Ok(qt("%\n asdf\n asdf\n%",(2, 1).into(), NationalStyle::National))
]
);
}
#[test]
fn test_invalid_quoted_text() {
assert_eq!(
scan("q' asdf ' 123"),
vec![
Err(Error {
etype: ErrorType::InvalidChar {
char: ' ',
expected: Some("a non-whitespace character"),
},
loc: (1, 3).into()
}),
Ok((TokenType::Integer("123"), (1, 11)).into())
]
);
}
#[test]
fn test_pipepipe() {
let s = "12||34";
#[rustfmt::skip]
assert_eq!(
scan(s),
vec![
Ok((TokenType::Integer("12"), (1, 1)).into()),
Ok((TokenType::PipePipe, (1, 3)).into()),
Ok((TokenType::Integer("34"), (1, 5)).into()),
]
);
}
#[test]
fn test_comparison() {
#[rustfmt::skip]
assert_eq!(
scan(r#"= != <> < > <= >= ^="#),
vec![
Ok((TokenType::Equal, (1, 1)).into()),
Ok((TokenType::BangEqual, (1, 3)).into()),
Ok((TokenType::LessGreater, (1, 6)).into()),
Ok((TokenType::Less, (1, 9)).into()),
Ok((TokenType::Greater, (1, 11)).into()),
Ok((TokenType::LessEqual, (1, 13)).into()),
Ok((TokenType::GreaterEqual, (1, 16)).into()),
Ok((TokenType::CaretEqual, (1, 19)).into()),
]
);
}
#[test]
fn test_equal_greater() {
#[rustfmt::skip]
assert_eq!(
scan("foo => 123"),
vec![
Ok((TokenType::Identifier(Ident("foo", QuoteStyle::None), None), (1, 1)).into()),
Ok((TokenType::EqualGreater, (1, 5)).into()),
Ok((TokenType::Integer("123"), (1, 8)).into()),
]
);
}
#[test]
fn test_invalid_bang_equals() {
#[rustfmt::skip]
assert_eq!(
scan(r#"! ="#),
vec![
Err((ErrorType::InvalidChar {char: ' ',expected: Some("a '='")},(1, 2)).into()),
Ok((TokenType::Equal, (1, 3)).into()),
]
);
}
#[test]
fn test_quoted_identifiers() {
#[rustfmt::skip]
assert_eq!(
scan(
r#""foo bar" "qu
ux"
"with'single'quotes"
"#
),
vec![
Ok((TokenType::Identifier(Ident("foo bar", QuoteStyle::Quoted), None), (1, 1)).into()),
Ok((TokenType::Identifier(Ident("qu\nux", QuoteStyle::Quoted), None), (1, 11)).into()),
Ok((TokenType::Identifier(Ident("with'single'quotes", QuoteStyle::Quoted), None), (3, 1)).into())
],
);
}
#[test]
fn test_invalid_quoted_identifiers() {
#[rustfmt::skip]
assert_eq!(
scan("\"\" \"foo\0bar\" \"quux\""),
vec![
Err((ErrorType::EmptyIdent, (1, 1)).into()),
Err((ErrorType::InvalidChar { char: '\0', expected: None, }, (1, 8)).into()),
Ok((TokenType::Identifier(Ident("quux", QuoteStyle::Quoted), None), (1, 14)).into())
]
);
}
#[test]
fn test_identifiers_and_keywords() {
#[rustfmt::skip]
assert_eq!(
scan("select 'abc' from dual t;"),
vec![
Ok((TokenType::Keyword(Keyword::SELECT), (1, 1)).into()),
Ok((TokenType::Text(Text::Regular("abc".into()), NationalStyle::None), (1, 8)).into()),
Ok((TokenType::Keyword(Keyword::FROM), (1, 14)).into()),
Ok((TokenType::Identifier(Ident("dual", QuoteStyle::None), None), (1, 19)).into()),
Ok((TokenType::Identifier(Ident("t", QuoteStyle::None), None), (1, 24)).into()),
Ok((TokenType::Semicolon, (1, 25)).into())
]
);
}
#[test]
fn test_placeholders() {
#[rustfmt::skip]
assert_eq!(
scan(r#"? ?asdf :qwer :"fROm""#),
vec![Ok((TokenType::QuestionMark, (1, 1)).into()),
Ok((TokenType::QuestionMark, (1, 3)).into()),
Ok((TokenType::Identifier(Ident("asdf", QuoteStyle::None), None), (1, 4)).into()),
Ok((TokenType::Placeholder(Ident("qwer", QuoteStyle::None)), (1, 9)).into()),
Ok((TokenType::Placeholder(Ident("fROm", QuoteStyle::Quoted)), (1, 15)).into()),
]
);
}
#[test]
fn test_invalid_placeholders() {
#[rustfmt::skip]
assert_eq!(
scan(r#":SELECT"#),
vec![
Err(((ErrorType::InvalidPlaceholder { details: "placeholder name must not be a keyword" }), (1, 1)).into()),
Ok((TokenType::Keyword(Keyword::SELECT), (1, 2)).into()),
]
);
}
#[test]
fn test_identifiers_start_with_n_and_q() {
#[rustfmt::skip]
assert_eq!(
scan("nova n'ova' N'ova' n "),
vec![
Ok((TokenType::Identifier(Ident("nova", QuoteStyle::None), None), (1, 1)).into()),
Ok((TokenType::Text(Text::Regular("ova".into()), NationalStyle::National), (1, 6)).into()),
Ok((TokenType::Text(Text::Regular("ova".into()), NationalStyle::National), (1, 13)).into()),
Ok((TokenType::Identifier(Ident("n", QuoteStyle::None), None), (1, 20)).into())
]
);
#[rustfmt::skip]
assert_eq!(
scan("qova q'.ova.' Q',ova,' Quux Q q q123"),
vec![
Ok((TokenType::Identifier(Ident("qova", QuoteStyle::None), None), (1, 1)).into()),
Ok((TokenType::Text(Text::Quoted(QuotedText::new_unchecked(".ova.")), NationalStyle::None), (1, 6)).into()),
Ok((TokenType::Text(Text::Quoted(QuotedText::new_unchecked(",ova,")), NationalStyle::None), (1, 15)).into()),
Ok((TokenType::Identifier(Ident("Quux", QuoteStyle::None), None), (1, 24)).into()),
Ok((TokenType::Identifier(Ident("Q", QuoteStyle::None), None), (1, 29)).into()),
Ok((TokenType::Identifier(Ident("q", QuoteStyle::None), None), (1, 31)).into()),
Ok((TokenType::Identifier(Ident("q123", QuoteStyle::None), None), (1, 33)).into()),
]
);
#[rustfmt::skip]
assert_eq!(
scan(
r#"nq nqalada
nq'{asdf}' nQ'<asdf>',
Nq'(asdf)' NQ'[asdf]',
NQ NQa nQa NqA"#
),
vec![
Ok((TokenType::Identifier(Ident("nq", QuoteStyle::None), None), (1, 1)).into()),
Ok((TokenType::Identifier(Ident("nqalada", QuoteStyle::None), None), (1, 4)).into()),
Ok((TokenType::Text(Text::Quoted(QuotedText::new_unchecked("{asdf}")), NationalStyle::National), (2, 2)).into()),
Ok((TokenType::Text(Text::Quoted(QuotedText::new_unchecked("<asdf>")), NationalStyle::National), (2, 13)).into()),
Ok((TokenType::Comma, (2, 23)).into()),
Ok((TokenType::Text(Text::Quoted(QuotedText::new_unchecked("(asdf)")), NationalStyle::National), (3, 2)).into()),
Ok((TokenType::Text(Text::Quoted(QuotedText::new_unchecked("[asdf]")), NationalStyle::National), (3, 13)).into()),
Ok((TokenType::Comma, (3, 23)).into()),
Ok((TokenType::Identifier(Ident("NQ", QuoteStyle::None), None), (4, 2)).into()),
Ok((TokenType::Identifier(Ident("NQa", QuoteStyle::None), None), (4, 5)).into()),
Ok((TokenType::Identifier(Ident("nQa", QuoteStyle::None), None), (4, 9)).into()),
Ok((TokenType::Identifier(Ident("NqA", QuoteStyle::None), None), (4, 13)).into())
]
);
}
#[test]
fn test_reserved_identifiers() {
assert_eq!(
scan(r#"wait "wait" outer outery"#),
vec![
Ok((
TokenType::Identifier(Ident("wait", QuoteStyle::None), Some(Reserved::WAIT)),
(1, 1)
)
.into()),
Ok((
TokenType::Identifier(Ident("wait", QuoteStyle::Quoted), None),
(1, 6)
)
.into()),
Ok((
TokenType::Identifier(Ident("outer", QuoteStyle::None), Some(Reserved::OUTER)),
(1, 13)
)
.into()),
Ok((
TokenType::Identifier(Ident("outery", QuoteStyle::None), None),
(1, 19)
)
.into()),
]
);
}