1use std::borrow::Cow;
16
17use rupg_common::SqlState;
18
19use crate::error::{Error, Notice};
20use crate::generated::tables::token;
21use crate::lexer::{Lexer, Token, Value, truncate};
22
23#[derive(Debug)]
25pub struct Tokens<'a> {
26 lexer: Lexer<'a>,
27 ahead: Option<Token<'a>>,
28}
29
30impl<'a> Tokens<'a> {
31 pub fn new(text: &'a str) -> Tokens<'a> {
33 Tokens { lexer: Lexer::new(text), ahead: None }
34 }
35
36 pub fn take_notices(&mut self) -> Vec<Notice> {
38 self.lexer.take_notices()
39 }
40
41 pub fn next_token(&mut self) -> Result<Token<'a>, Error> {
43 let mut current = match self.ahead.take() {
44 Some(token) => token,
45 None => self.lexer.next_token()?,
46 };
47 if !matches!(
48 current.kind,
49 token::FORMAT
50 | token::NOT
51 | token::NULLS_P
52 | token::WITH
53 | token::WITHOUT
54 | token::UIDENT
55 | token::USCONST
56 ) {
57 return Ok(current);
58 }
59 let next = self.lexer.next_token()?;
60 current.kind = match (current.kind, next.kind) {
61 (token::FORMAT, token::JSON) => token::FORMAT_LA,
62 (
63 token::NOT,
64 token::BETWEEN | token::IN_P | token::LIKE | token::ILIKE | token::SIMILAR,
65 ) => token::NOT_LA,
66 (token::NULLS_P, token::FIRST_P | token::LAST_P) => token::NULLS_LA,
67 (token::WITH, token::TIME | token::ORDINALITY) => token::WITH_LA,
68 (token::WITHOUT, token::TIME) => token::WITHOUT_LA,
69 (token::UIDENT | token::USCONST, _) => return self.unicode(current, next),
70 (kind, _) => kind,
71 };
72 self.ahead = Some(next);
73 Ok(current)
74 }
75
76 fn unicode(&mut self, mut current: Token<'a>, next: Token<'a>) -> Result<Token<'a>, Error> {
78 let escape = if next.kind == token::UESCAPE {
79 let third = self.lexer.next_token()?;
80 let error = |message| {
81 Error::syntax(self.lexer.text().as_bytes(), message, third.start, third.end)
82 };
83 if third.kind != token::SCONST {
84 return Err(error("UESCAPE must be followed by a simple string literal"));
85 }
86 match third.text().map(str::as_bytes) {
87 Some(&[c]) if is_escape(c) => {
88 current.end = third.end;
90 c
91 }
92 _ => return Err(error("invalid Unicode escape character")),
93 }
94 } else {
95 self.ahead = Some(next);
96 b'\\'
97 };
98 let text = current.text().unwrap_or_default();
99 let text = Cow::Owned(unescape(text, escape, current.start)?);
100 if current.kind == token::UIDENT {
101 current.value = Value::Text(truncate(text, self.lexer.notices()));
102 current.kind = token::IDENT;
103 } else {
104 current.value = Value::Text(text);
105 current.kind = token::SCONST;
106 }
107 Ok(current)
108 }
109}
110
111fn is_escape(c: u8) -> bool {
113 !(c.is_ascii_hexdigit()
114 || matches!(c, b'+' | b'\'' | b'"' | b' ' | b'\t' | b'\n' | b'\r' | 0x0b | 0x0c))
115}
116
117fn unescape(text: &str, escape: u8, position: usize) -> Result<String, Error> {
119 let bytes = text.as_bytes();
120 let byte = |i: usize| bytes.get(i).copied().unwrap_or(0);
121 let hex = |from: usize, length: usize| {
122 let digits = bytes.get(from..from + length)?;
123 digits.iter().all(u8::is_ascii_hexdigit).then(|| {
124 digits.iter().fold(0u32, |v, &d| (v << 4) | char::from(d).to_digit(16).unwrap_or(0))
125 })
126 };
127 let location = |i: usize| position + 3 + i;
128 let pair =
129 |i: usize| Error::at(SqlState::SYNTAX_ERROR, "invalid Unicode surrogate pair", location(i));
130 let mut out = String::with_capacity(text.len());
131 let mut first = 0;
132 let mut i = 0;
133 while i < bytes.len() {
134 if bytes[i] != escape {
135 if first != 0 {
136 return Err(pair(i));
137 }
138 let length = text[i..].chars().next().map_or(1, char::len_utf8);
139 out.push_str(&text[i..i + length]);
140 i += length;
141 continue;
142 }
143 let (c, length) = if byte(i + 1) == escape {
144 if first != 0 {
145 return Err(pair(i));
146 }
147 out.push(char::from(escape));
148 i += 2;
149 continue;
150 } else if let Some(c) = hex(i + 1, 4) {
151 (c, 5)
152 } else if byte(i + 1) == b'+'
153 && let Some(c) = hex(i + 2, 6)
154 {
155 (c, 8)
156 } else {
157 return Err(Error {
158 code: SqlState::SYNTAX_ERROR,
159 message: "invalid Unicode escape".to_owned(),
160 detail: None,
161 hint: Some("Unicode escapes must be \\XXXX or \\+XXXXXX."),
162 location: Some(location(i)),
163 });
164 };
165 if c == 0 || c > 0x10ffff {
166 return Err(Error::at(
167 SqlState::SYNTAX_ERROR,
168 "invalid Unicode escape value",
169 location(i),
170 ));
171 }
172 let c = if first != 0 {
173 if !(0xdc00..=0xdfff).contains(&c) {
174 return Err(pair(i));
175 }
176 let c = ((first & 0x3ff) << 10) + (c & 0x3ff) + 0x10000;
177 first = 0;
178 c
179 } else if (0xdc00..=0xdfff).contains(&c) {
180 return Err(pair(i));
181 } else {
182 c
183 };
184 if (0xd800..=0xdbff).contains(&c) {
185 first = c;
186 } else if let Some(c) = char::from_u32(c) {
187 out.push(c);
188 }
189 i += length;
190 }
191 if first != 0 {
192 return Err(pair(i));
193 }
194 Ok(out)
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 fn kinds(text: &str) -> Vec<u16> {
202 let mut tokens = Tokens::new(text);
203 let mut kinds = Vec::new();
204 loop {
205 match tokens.next_token().unwrap().kind {
206 0 => return kinds,
207 kind => kinds.push(kind),
208 }
209 }
210 }
211
212 #[test]
213 fn lookahead() {
214 use token::*;
215 assert_eq!(kinds("NOT IN NOT x"), [NOT_LA, IN_P, NOT, IDENT]);
216 assert_eq!(
217 kinds("NOT BETWEEN NOT LIKE NOT ILIKE NOT SIMILAR"),
218 [NOT_LA, BETWEEN, NOT_LA, LIKE, NOT_LA, ILIKE, NOT_LA, SIMILAR]
219 );
220 assert_eq!(
221 kinds("NULLS FIRST NULLS LAST NULLS x"),
222 [NULLS_LA, FIRST_P, NULLS_LA, LAST_P, NULLS_P, IDENT]
223 );
224 assert_eq!(
225 kinds("WITH TIME WITH ORDINALITY WITH"),
226 [WITH_LA, TIME, WITH_LA, ORDINALITY, WITH]
227 );
228 assert_eq!(kinds("WITHOUT TIME WITHOUT"), [WITHOUT_LA, TIME, WITHOUT]);
229 assert_eq!(kinds("FORMAT JSON FORMAT"), [FORMAT_LA, JSON, FORMAT]);
230 assert_eq!(kinds("NOT NOT IN"), [NOT, NOT_LA, IN_P]);
232 }
233
234 #[test]
235 fn unicode() {
236 let mut tokens =
237 Tokens::new("U&'\\0041\\+01F600\\\\' U&'!0042' UESCAPE '!' U&\"\\00e9\" 1");
238 let a = tokens.next_token().unwrap();
239 assert_eq!((a.kind, a.text()), (token::SCONST, Some("A😀\\")));
240 let b = tokens.next_token().unwrap();
241 assert_eq!((b.kind, b.text()), (token::SCONST, Some("B")));
242 assert_eq!(&tokens.lexer.text()[b.start..b.end], "U&'!0042' UESCAPE '!'");
243 let c = tokens.next_token().unwrap();
244 assert_eq!((c.kind, c.text()), (token::IDENT, Some("é")));
245 assert_eq!(tokens.next_token().unwrap().kind, token::ICONST);
246 assert!(!is_escape(b'a') && !is_escape(b'+') && !is_escape(b' ') && is_escape(b'!'));
247 }
248}