use crate::error::Error;
pub(crate) struct Parser<'a> {
source: &'a str,
pos: usize,
temp_pos: usize,
}
impl Parser<'_> {
pub(crate) fn eof(&self) -> bool {
self.peek_char().is_none()
}
pub(crate) fn new(source: &str) -> Parser<'_> {
Parser {
source,
pos: 0,
temp_pos: 0,
}
}
pub(crate) fn next_char(&mut self) -> Option<char> {
match self.source[self.temp_pos..].chars().next() {
None => None,
Some(ch) => {
self.temp_pos += ch.len_utf8();
Some(ch)
}
}
}
pub(crate) fn next_char_matches(&mut self, match_ch: char) -> bool {
if let Some(ch) = self.next_char() {
ch == match_ch
} else {
false
}
}
pub(crate) fn parse_delimited_text(
&mut self,
start_sep: char,
end_sep: char,
) -> Option<String> {
if self.peek_char_matches(start_sep) {
self.temp_pos += start_sep.len_utf8();
while let Some(ch) = self.next_char() {
if ch == end_sep {
let text = self.save_pos();
return Some(text[1..text.len() - 1].into());
}
}
}
self.restore_pos();
None
}
pub(crate) fn parse_error(&self) -> Error {
let start_line_pos = self.source[..self.temp_pos]
.rfind('\n')
.map_or(0, |i| i + 1);
let end_line_pos = self.source[self.temp_pos..]
.find('\n')
.map_or(self.source.len(), |i| self.temp_pos + i);
let text = self.source[start_line_pos..end_line_pos].to_string();
Error::parse_error(text, self.temp_pos - start_line_pos + 1)
}
pub(crate) fn parse_simple_sql_name(&mut self) -> Option<String> {
if let Some(quoted_value) = self.parse_delimited_text('"', '"') {
if quoted_value.is_empty() || quoted_value.contains('"') {
self.restore_pos();
None
} else {
Some(quoted_value)
}
} else {
let first_ch = self.next_char().unwrap_or(' ');
if !first_ch.is_alphabetic() {
self.restore_pos();
return None;
}
while let Some(ch) = self.peek_char() {
if !ch.is_alphanumeric() && ch != '_' && ch != '$' && ch != '#'
{
break;
}
self.next_char();
}
let text = self.save_pos();
Some(text.into())
}
}
pub(crate) fn parse_token(
&mut self,
f: fn(char) -> bool,
) -> Option<String> {
let mut num_chars = 0;
while let Some(ch) = self.peek_char() {
if !f(ch) {
break;
}
self.temp_pos += ch.len_utf8();
num_chars += 1;
}
if num_chars == 0 {
self.restore_pos();
None
} else {
let text = self.save_pos();
Some(text[text.len() - num_chars..].into())
}
}
pub(crate) fn parse_token_with_sep(
&mut self,
f: fn(char) -> bool,
sep: char,
) -> Option<String> {
if let Some(ch) = self.next_char()
&& ch == sep
{
return self.parse_token(f);
}
self.restore_pos();
None
}
pub(crate) fn peek_char(&self) -> Option<char> {
self.source[self.temp_pos..].chars().next()
}
pub(crate) fn peek_char_matches(&self, match_ch: char) -> bool {
if let Some(ch) = self.peek_char() {
ch == match_ch
} else {
false
}
}
pub(crate) fn restore_pos(&mut self) {
self.temp_pos = self.pos;
}
pub(crate) fn source(&self) -> &str {
self.source
}
pub(crate) fn save_pos(&mut self) -> &str {
let result = &self.source[self.pos..self.temp_pos];
self.pos = self.temp_pos;
result
}
pub(crate) fn skip_to_end_of_line(&mut self) {
while let Some(ch) = self.next_char() {
if ch == '\n' {
break;
}
}
self.pos = self.temp_pos;
}
pub(crate) fn skip_whitespace(&mut self) {
while let Some(ch) = self.peek_char() {
if !ch.is_whitespace() {
break;
}
self.temp_pos += ch.len_utf8();
}
self.pos = self.temp_pos;
}
}