use super::ast::{AssignedTarget, Filter};
use super::error::{FilterError, FilterResult};
use super::lexer::{FilterToken, Lexer, PositionedToken};
pub struct FilterParser {
tokens: Vec<PositionedToken>,
position: usize,
input_len: usize,
}
impl FilterParser {
pub fn parse(input: &str) -> FilterResult<Filter> {
let trimmed = input.trim();
if trimmed.is_empty() {
return Err(FilterError::EmptyExpression);
}
let lexer_result = Lexer::new(trimmed).tokenize_with_errors();
if !lexer_result.errors.is_empty() {
return Err(FilterError::UnknownCharacters {
errors: lexer_result.errors,
});
}
let tokens = lexer_result.tokens;
if tokens.is_empty() {
return Err(FilterError::EmptyExpression);
}
let mut parser = Self {
tokens,
position: 0,
input_len: trimmed.len(),
};
let filter = parser.parse_expression()?;
if parser.position < parser.tokens.len() {
let remaining = &parser.tokens[parser.position];
return Err(FilterError::unexpected_token(
format!("{:?}", remaining.token),
remaining.position,
));
}
Ok(filter)
}
fn peek(&self) -> Option<&PositionedToken> {
self.tokens.get(self.position)
}
fn advance(&mut self) -> Option<&PositionedToken> {
let token = self.tokens.get(self.position);
if token.is_some() {
self.position += 1;
}
token
}
fn check(&self, expected: &FilterToken) -> bool {
self.peek().map(|pt| &pt.token) == Some(expected)
}
fn parse_expression(&mut self) -> FilterResult<Filter> {
self.parse_or_expr()
}
fn parse_or_expr(&mut self) -> FilterResult<Filter> {
let mut left = self.parse_and_expr()?;
while self.check(&FilterToken::Or) {
self.advance(); let right = self.parse_and_expr()?;
left = Filter::or(left, right);
}
Ok(left)
}
fn parse_and_expr(&mut self) -> FilterResult<Filter> {
let mut left = self.parse_unary_expr()?;
while self.check(&FilterToken::And) {
self.advance(); let right = self.parse_unary_expr()?;
left = Filter::and(left, right);
}
Ok(left)
}
fn parse_unary_expr(&mut self) -> FilterResult<Filter> {
if self.check(&FilterToken::Not) {
self.advance(); let inner = self.parse_unary_expr()?;
return Ok(Filter::negate(inner));
}
self.parse_primary()
}
fn parse_primary(&mut self) -> FilterResult<Filter> {
let input_len = self.input_len;
let positioned_token = self
.advance()
.ok_or_else(|| FilterError::unexpected_end_of_input(input_len))?;
let token = positioned_token.token.clone();
let position = positioned_token.position;
match token {
FilterToken::OpenParen => {
let open_paren_pos = position;
let inner = self.parse_expression()?;
if !self.check(&FilterToken::CloseParen) {
return Err(FilterError::unclosed_parenthesis(open_paren_pos));
}
self.advance(); Ok(inner)
}
FilterToken::Today => Ok(Filter::Today),
FilterToken::Tomorrow => Ok(Filter::Tomorrow),
FilterToken::Overdue => Ok(Filter::Overdue),
FilterToken::NoDate => Ok(Filter::NoDate),
FilterToken::Next7Days => Ok(Filter::Next7Days),
FilterToken::SpecificDate { month, day } => Ok(Filter::SpecificDate { month, day }),
FilterToken::NoLabels => Ok(Filter::NoLabels),
FilterToken::Priority(level) => match level {
1 => Ok(Filter::Priority1),
2 => Ok(Filter::Priority2),
3 => Ok(Filter::Priority3),
4 => Ok(Filter::Priority4),
_ => Err(FilterError::invalid_priority(level.to_string(), position)),
},
FilterToken::Label(name) => Ok(Filter::Label(name)),
FilterToken::Project(name) => Ok(Filter::Project(name)),
FilterToken::ProjectWithSubprojects(name) => Ok(Filter::ProjectWithSubprojects(name)),
FilterToken::Section(name) => Ok(Filter::Section(name)),
FilterToken::AssignedTo(target) => {
Ok(Filter::AssignedTo(parse_assigned_target(&target)))
}
FilterToken::AssignedBy(target) => {
Ok(Filter::AssignedBy(parse_assigned_target(&target)))
}
FilterToken::Assigned => Ok(Filter::Assigned),
FilterToken::NoAssignee => Ok(Filter::NoAssignee),
FilterToken::And => Err(FilterError::unexpected_token("&", position)),
FilterToken::Or => Err(FilterError::unexpected_token("|", position)),
FilterToken::CloseParen => Err(FilterError::unexpected_token(")", position)),
FilterToken::Not => Err(FilterError::unexpected_token("!", position)),
}
}
}
fn parse_assigned_target(target: &str) -> AssignedTarget {
match target.to_lowercase().as_str() {
"me" => AssignedTarget::Me,
"others" => AssignedTarget::Others,
_ => AssignedTarget::User(target.to_string()),
}
}