use crate::ast::{AccessSegment, BinaryOp, Expr, UnaryOp};
use crate::lexer::{Token, TokenKind, lex};
use anyhow::{Result, bail};
pub fn parse_expression(input: &str) -> Result<Expr> {
let unwrapped = unwrap_expression(input);
let tokens = lex(&unwrapped)?;
let mut parser = Parser { tokens, pos: 0 };
let expr = parser.parse_or()?;
parser.expect_eof()?;
Ok(expr)
}
pub fn unwrap_expression(input: &str) -> String {
let trimmed = input.trim();
if let Some(inner) = trimmed
.strip_prefix("${{")
.and_then(|value| value.strip_suffix("}}"))
{
inner.trim().to_owned()
} else {
trimmed.to_owned()
}
}
struct Parser {
tokens: Vec<Token>,
pos: usize,
}
impl Parser {
fn parse_or(&mut self) -> Result<Expr> {
let mut expr = self.parse_and()?;
while self.matches(&TokenKind::OrOr) {
let right = self.parse_and()?;
expr = Expr::Binary {
op: BinaryOp::Or,
left: Box::new(expr),
right: Box::new(right),
};
}
Ok(expr)
}
fn parse_and(&mut self) -> Result<Expr> {
let mut expr = self.parse_equality()?;
while self.matches(&TokenKind::AndAnd) {
let right = self.parse_equality()?;
expr = Expr::Binary {
op: BinaryOp::And,
left: Box::new(expr),
right: Box::new(right),
};
}
Ok(expr)
}
fn parse_equality(&mut self) -> Result<Expr> {
let mut expr = self.parse_relational()?;
loop {
let op = if self.matches(&TokenKind::EqEq) {
Some(BinaryOp::Eq)
} else if self.matches(&TokenKind::Ne) {
Some(BinaryOp::Ne)
} else {
None
};
let Some(op) = op else { break };
let right = self.parse_relational()?;
expr = Expr::Binary {
op,
left: Box::new(expr),
right: Box::new(right),
};
}
Ok(expr)
}
fn parse_relational(&mut self) -> Result<Expr> {
let mut expr = self.parse_unary()?;
loop {
let op = if self.matches(&TokenKind::Lt) {
Some(BinaryOp::Lt)
} else if self.matches(&TokenKind::Le) {
Some(BinaryOp::Le)
} else if self.matches(&TokenKind::Gt) {
Some(BinaryOp::Gt)
} else if self.matches(&TokenKind::Ge) {
Some(BinaryOp::Ge)
} else {
None
};
let Some(op) = op else { break };
let right = self.parse_unary()?;
expr = Expr::Binary {
op,
left: Box::new(expr),
right: Box::new(right),
};
}
Ok(expr)
}
fn parse_unary(&mut self) -> Result<Expr> {
if self.matches(&TokenKind::Bang) {
return Ok(Expr::Unary {
op: UnaryOp::Not,
expr: Box::new(self.parse_unary()?),
});
}
self.parse_postfix()
}
fn parse_postfix(&mut self) -> Result<Expr> {
let mut expr = self.parse_primary()?;
loop {
if self.matches(&TokenKind::Dot) {
if self.matches(&TokenKind::Star) {
expr = Expr::Access {
base: Box::new(expr),
segment: AccessSegment::Wildcard,
};
} else {
let name = self.expect_ident("expected property name after `.`")?;
expr = Expr::Access {
base: Box::new(expr),
segment: AccessSegment::Property(name),
};
}
} else if self.matches(&TokenKind::LBracket) {
if self.matches(&TokenKind::Star) {
self.expect(&TokenKind::RBracket, "expected `]` after wildcard")?;
expr = Expr::Access {
base: Box::new(expr),
segment: AccessSegment::Wildcard,
};
} else {
let index = self.parse_or()?;
self.expect(&TokenKind::RBracket, "expected `]` after index")?;
expr = Expr::Access {
base: Box::new(expr),
segment: AccessSegment::Index(Box::new(index)),
};
}
} else {
break;
}
}
Ok(expr)
}
fn parse_primary(&mut self) -> Result<Expr> {
match self.advance().kind.clone() {
TokenKind::Literal(value) => Ok(Expr::Literal(value)),
TokenKind::Ident(name) => {
if self.matches(&TokenKind::LParen) {
let mut args = Vec::new();
if !self.check(&TokenKind::RParen) {
loop {
args.push(self.parse_or()?);
if !self.matches(&TokenKind::Comma) {
break;
}
}
}
self.expect(&TokenKind::RParen, "expected `)` after function arguments")?;
Ok(Expr::Call { name, args })
} else {
Ok(Expr::Variable(name))
}
}
TokenKind::LParen => {
let expr = self.parse_or()?;
self.expect(&TokenKind::RParen, "expected `)` after grouped expression")?;
Ok(expr)
}
other => bail!("expected expression, found {other:?}"),
}
}
fn expect_eof(&mut self) -> Result<()> {
if self.check(&TokenKind::Eof) {
Ok(())
} else {
bail!("unexpected token {:?} after expression", self.peek().kind)
}
}
fn expect_ident(&mut self, message: &str) -> Result<String> {
match self.advance().kind.clone() {
TokenKind::Ident(name) => Ok(name),
_ => bail!("{message}"),
}
}
fn expect(&mut self, kind: &TokenKind, message: &str) -> Result<()> {
if self.matches(kind) {
Ok(())
} else {
bail!("{message}")
}
}
fn matches(&mut self, kind: &TokenKind) -> bool {
if self.check(kind) {
self.pos += 1;
true
} else {
false
}
}
fn check(&self, kind: &TokenKind) -> bool {
std::mem::discriminant(&self.peek().kind) == std::mem::discriminant(kind)
}
fn advance(&mut self) -> &Token {
let index = self.pos;
self.pos += 1;
&self.tokens[index]
}
fn peek(&self) -> &Token {
&self.tokens[self.pos]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_object_filter() {
let expr = parse_expression("github.event.issue.labels.*.name").unwrap();
let mut roots = Vec::new();
expr.collect_roots(&mut roots);
assert_eq!(roots, vec!["github"]);
}
#[test]
fn rejects_double_quoted_strings() {
assert!(parse_expression("github.ref == \"main\"").is_err());
}
}