chore: reformat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
pub mod parser;
|
||||
pub mod ast;
|
||||
pub mod parser;
|
||||
|
||||
pub use ast::{AggFunc, BinOp, Expr, Formula};
|
||||
pub use parser::parse_formula;
|
||||
|
||||
@ -8,7 +8,9 @@ pub fn parse_formula(raw: &str, target_category: &str) -> Result<Formula> {
|
||||
let raw = raw.trim();
|
||||
|
||||
// Split on first `=` to get target = expression
|
||||
let eq_pos = raw.find('=').ok_or_else(|| anyhow!("Formula must contain '=': {raw}"))?;
|
||||
let eq_pos = raw
|
||||
.find('=')
|
||||
.ok_or_else(|| anyhow!("Formula must contain '=': {raw}"))?;
|
||||
let target = raw[..eq_pos].trim().to_string();
|
||||
let rest = raw[eq_pos + 1..].trim();
|
||||
|
||||
@ -54,7 +56,9 @@ fn split_where(s: &str) -> (&str, Option<&str>) {
|
||||
|
||||
fn parse_where(s: &str) -> Result<Filter> {
|
||||
// Format: Category = "Item" or Category = Item
|
||||
let eq_pos = s.find('=').ok_or_else(|| anyhow!("WHERE clause must contain '=': {s}"))?;
|
||||
let eq_pos = s
|
||||
.find('=')
|
||||
.ok_or_else(|| anyhow!("WHERE clause must contain '=': {s}"))?;
|
||||
let category = s[..eq_pos].trim().to_string();
|
||||
let item_raw = s[eq_pos + 1..].trim();
|
||||
let item = item_raw.trim_matches('"').to_string();
|
||||
@ -67,7 +71,10 @@ pub fn parse_expr(s: &str) -> Result<Expr> {
|
||||
let mut pos = 0;
|
||||
let expr = parse_add_sub(&tokens, &mut pos)?;
|
||||
if pos < tokens.len() {
|
||||
return Err(anyhow!("Unexpected token at position {pos}: {:?}", tokens[pos]));
|
||||
return Err(anyhow!(
|
||||
"Unexpected token at position {pos}: {:?}",
|
||||
tokens[pos]
|
||||
));
|
||||
}
|
||||
Ok(expr)
|
||||
}
|
||||
@ -101,20 +108,62 @@ fn tokenize(s: &str) -> Result<Vec<Token>> {
|
||||
while i < chars.len() {
|
||||
match chars[i] {
|
||||
' ' | '\t' | '\n' => i += 1,
|
||||
'+' => { tokens.push(Token::Plus); i += 1; }
|
||||
'-' => { tokens.push(Token::Minus); i += 1; }
|
||||
'*' => { tokens.push(Token::Star); i += 1; }
|
||||
'/' => { tokens.push(Token::Slash); i += 1; }
|
||||
'^' => { tokens.push(Token::Caret); i += 1; }
|
||||
'(' => { tokens.push(Token::LParen); i += 1; }
|
||||
')' => { tokens.push(Token::RParen); i += 1; }
|
||||
',' => { tokens.push(Token::Comma); i += 1; }
|
||||
'!' if chars.get(i+1) == Some(&'=') => { tokens.push(Token::Ne); i += 2; }
|
||||
'<' if chars.get(i+1) == Some(&'=') => { tokens.push(Token::Le); i += 2; }
|
||||
'>' if chars.get(i+1) == Some(&'=') => { tokens.push(Token::Ge); i += 2; }
|
||||
'<' => { tokens.push(Token::Lt); i += 1; }
|
||||
'>' => { tokens.push(Token::Gt); i += 1; }
|
||||
'=' => { tokens.push(Token::Eq); i += 1; }
|
||||
'+' => {
|
||||
tokens.push(Token::Plus);
|
||||
i += 1;
|
||||
}
|
||||
'-' => {
|
||||
tokens.push(Token::Minus);
|
||||
i += 1;
|
||||
}
|
||||
'*' => {
|
||||
tokens.push(Token::Star);
|
||||
i += 1;
|
||||
}
|
||||
'/' => {
|
||||
tokens.push(Token::Slash);
|
||||
i += 1;
|
||||
}
|
||||
'^' => {
|
||||
tokens.push(Token::Caret);
|
||||
i += 1;
|
||||
}
|
||||
'(' => {
|
||||
tokens.push(Token::LParen);
|
||||
i += 1;
|
||||
}
|
||||
')' => {
|
||||
tokens.push(Token::RParen);
|
||||
i += 1;
|
||||
}
|
||||
',' => {
|
||||
tokens.push(Token::Comma);
|
||||
i += 1;
|
||||
}
|
||||
'!' if chars.get(i + 1) == Some(&'=') => {
|
||||
tokens.push(Token::Ne);
|
||||
i += 2;
|
||||
}
|
||||
'<' if chars.get(i + 1) == Some(&'=') => {
|
||||
tokens.push(Token::Le);
|
||||
i += 2;
|
||||
}
|
||||
'>' if chars.get(i + 1) == Some(&'=') => {
|
||||
tokens.push(Token::Ge);
|
||||
i += 2;
|
||||
}
|
||||
'<' => {
|
||||
tokens.push(Token::Lt);
|
||||
i += 1;
|
||||
}
|
||||
'>' => {
|
||||
tokens.push(Token::Gt);
|
||||
i += 1;
|
||||
}
|
||||
'=' => {
|
||||
tokens.push(Token::Eq);
|
||||
i += 1;
|
||||
}
|
||||
'"' => {
|
||||
i += 1;
|
||||
let mut s = String::new();
|
||||
@ -122,7 +171,9 @@ fn tokenize(s: &str) -> Result<Vec<Token>> {
|
||||
s.push(chars[i]);
|
||||
i += 1;
|
||||
}
|
||||
if i < chars.len() { i += 1; }
|
||||
if i < chars.len() {
|
||||
i += 1;
|
||||
}
|
||||
tokens.push(Token::Str(s));
|
||||
}
|
||||
c if c.is_ascii_digit() || c == '.' => {
|
||||
@ -135,13 +186,25 @@ fn tokenize(s: &str) -> Result<Vec<Token>> {
|
||||
}
|
||||
c if c.is_alphabetic() || c == '_' => {
|
||||
let mut ident = String::new();
|
||||
while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_' || chars[i] == ' ') {
|
||||
while i < chars.len()
|
||||
&& (chars[i].is_alphanumeric() || chars[i] == '_' || chars[i] == ' ')
|
||||
{
|
||||
// Don't consume trailing spaces if next non-space is operator
|
||||
if chars[i] == ' ' {
|
||||
// Peek ahead
|
||||
let j = i + 1;
|
||||
let next_nonspace = chars[j..].iter().find(|&&c| c != ' ');
|
||||
if matches!(next_nonspace, Some('+') | Some('-') | Some('*') | Some('/') | Some('^') | Some(')') | Some(',') | None) {
|
||||
if matches!(
|
||||
next_nonspace,
|
||||
Some('+')
|
||||
| Some('-')
|
||||
| Some('*')
|
||||
| Some('/')
|
||||
| Some('^')
|
||||
| Some(')')
|
||||
| Some(',')
|
||||
| None
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -161,7 +224,7 @@ fn parse_add_sub(tokens: &[Token], pos: &mut usize) -> Result<Expr> {
|
||||
let mut left = parse_mul_div(tokens, pos)?;
|
||||
while *pos < tokens.len() {
|
||||
let op = match &tokens[*pos] {
|
||||
Token::Plus => BinOp::Add,
|
||||
Token::Plus => BinOp::Add,
|
||||
Token::Minus => BinOp::Sub,
|
||||
_ => break,
|
||||
};
|
||||
@ -176,7 +239,7 @@ fn parse_mul_div(tokens: &[Token], pos: &mut usize) -> Result<Expr> {
|
||||
let mut left = parse_pow(tokens, pos)?;
|
||||
while *pos < tokens.len() {
|
||||
let op = match &tokens[*pos] {
|
||||
Token::Star => BinOp::Mul,
|
||||
Token::Star => BinOp::Mul,
|
||||
Token::Slash => BinOp::Div,
|
||||
_ => break,
|
||||
};
|
||||
@ -239,19 +302,42 @@ fn parse_primary(tokens: &[Token], pos: &mut usize) -> Result<Expr> {
|
||||
if kw.to_ascii_uppercase() == "WHERE" {
|
||||
*pos += 1;
|
||||
let cat = match &tokens[*pos] {
|
||||
Token::Ident(s) => { let s = s.clone(); *pos += 1; s }
|
||||
t => return Err(anyhow!("Expected category name, got {t:?}")),
|
||||
Token::Ident(s) => {
|
||||
let s = s.clone();
|
||||
*pos += 1;
|
||||
s
|
||||
}
|
||||
t => {
|
||||
return Err(anyhow!(
|
||||
"Expected category name, got {t:?}"
|
||||
))
|
||||
}
|
||||
};
|
||||
// expect =
|
||||
if *pos < tokens.len() && tokens[*pos] == Token::Eq { *pos += 1; }
|
||||
if *pos < tokens.len() && tokens[*pos] == Token::Eq {
|
||||
*pos += 1;
|
||||
}
|
||||
let item = match &tokens[*pos] {
|
||||
Token::Str(s) | Token::Ident(s) => { let s = s.clone(); *pos += 1; s }
|
||||
Token::Str(s) | Token::Ident(s) => {
|
||||
let s = s.clone();
|
||||
*pos += 1;
|
||||
s
|
||||
}
|
||||
t => return Err(anyhow!("Expected item name, got {t:?}")),
|
||||
};
|
||||
Some(Filter { category: cat, item })
|
||||
} else { None }
|
||||
} else { None }
|
||||
} else { None };
|
||||
Some(Filter {
|
||||
category: cat,
|
||||
item,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// expect )
|
||||
if *pos < tokens.len() && tokens[*pos] == Token::RParen {
|
||||
*pos += 1;
|
||||
@ -266,9 +352,13 @@ fn parse_primary(tokens: &[Token], pos: &mut usize) -> Result<Expr> {
|
||||
if *pos < tokens.len() && tokens[*pos] == Token::LParen {
|
||||
*pos += 1;
|
||||
let cond = parse_comparison(tokens, pos)?;
|
||||
if *pos < tokens.len() && tokens[*pos] == Token::Comma { *pos += 1; }
|
||||
if *pos < tokens.len() && tokens[*pos] == Token::Comma {
|
||||
*pos += 1;
|
||||
}
|
||||
let then = parse_add_sub(tokens, pos)?;
|
||||
if *pos < tokens.len() && tokens[*pos] == Token::Comma { *pos += 1; }
|
||||
if *pos < tokens.len() && tokens[*pos] == Token::Comma {
|
||||
*pos += 1;
|
||||
}
|
||||
let else_ = parse_add_sub(tokens, pos)?;
|
||||
if *pos < tokens.len() && tokens[*pos] == Token::RParen {
|
||||
*pos += 1;
|
||||
@ -296,7 +386,9 @@ fn parse_primary(tokens: &[Token], pos: &mut usize) -> Result<Expr> {
|
||||
|
||||
fn parse_comparison(tokens: &[Token], pos: &mut usize) -> Result<Expr> {
|
||||
let left = parse_add_sub(tokens, pos)?;
|
||||
if *pos >= tokens.len() { return Ok(left); }
|
||||
if *pos >= tokens.len() {
|
||||
return Ok(left);
|
||||
}
|
||||
let op = match &tokens[*pos] {
|
||||
Token::Eq => BinOp::Eq,
|
||||
Token::Ne => BinOp::Ne,
|
||||
|
||||
Reference in New Issue
Block a user