use crate::ast::Spanned;
use crate::parser::{ParseError, Parser};
use crate::template::{self, StringTemplate};
use crate::token::TokenKind;
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
Str(StringTemplate),
Bool(bool),
Var(Spanned<String>),
Call {
name: Spanned<String>,
args: Vec<Expr>,
},
Binary {
op: BinOp,
lhs: Box<Expr>,
rhs: Box<Expr>,
},
If {
cond: Box<Expr>,
then: Box<Expr>,
otherwise: Box<Expr>,
},
Field {
object: Spanned<String>,
field: Spanned<String>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinOp {
Eq,
NotEq,
And,
Or,
Concat,
}
impl<'a> Parser<'a> {
pub(crate) fn parse_expr(&mut self) -> Result<Expr, ParseError> {
self.parse_or()
}
fn parse_or(&mut self) -> Result<Expr, ParseError> {
let mut lhs = self.parse_and()?;
while self.check(&TokenKind::OrOr) {
self.advance();
let rhs = self.parse_and()?;
lhs = Expr::Binary {
op: BinOp::Or,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
};
}
Ok(lhs)
}
fn parse_and(&mut self) -> Result<Expr, ParseError> {
let mut lhs = self.parse_equality()?;
while self.check(&TokenKind::AndAnd) {
self.advance();
let rhs = self.parse_equality()?;
lhs = Expr::Binary {
op: BinOp::And,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
};
}
Ok(lhs)
}
fn parse_equality(&mut self) -> Result<Expr, ParseError> {
let mut lhs = self.parse_additive()?;
loop {
let op = if self.check(&TokenKind::EqEq) {
BinOp::Eq
} else if self.check(&TokenKind::NotEq) {
BinOp::NotEq
} else {
break;
};
self.advance();
let rhs = self.parse_additive()?;
lhs = Expr::Binary {
op,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
};
}
Ok(lhs)
}
fn parse_additive(&mut self) -> Result<Expr, ParseError> {
let mut lhs = self.parse_atom()?;
while self.check(&TokenKind::Plus) {
self.advance();
let rhs = self.parse_atom()?;
lhs = Expr::Binary {
op: BinOp::Concat,
lhs: Box::new(lhs),
rhs: Box::new(rhs),
};
}
Ok(lhs)
}
fn parse_atom(&mut self) -> Result<Expr, ParseError> {
match self.peek().kind.clone() {
TokenKind::KwIf => self.parse_if(),
TokenKind::Str(_) => {
let tok = self.advance();
let template = template::parse_template_at(self.source(), tok.span)?;
Ok(Expr::Str(template))
}
TokenKind::KwTrue => {
self.advance();
Ok(Expr::Bool(true))
}
TokenKind::KwFalse => {
self.advance();
Ok(Expr::Bool(false))
}
TokenKind::Ident(name) => {
let name_span = self.advance().span;
if self.check(&TokenKind::Dot) {
self.advance();
let field = self.eat_ident()?;
return Ok(Expr::Field {
object: Spanned::new(name, name_span),
field,
});
}
if self.check(&TokenKind::LParen) {
self.advance();
let mut args = Vec::new();
if !self.check(&TokenKind::RParen) {
loop {
args.push(self.parse_expr()?);
if self.check(&TokenKind::Comma) {
self.advance();
} else {
break;
}
}
}
self.expect(TokenKind::RParen, "`)`")?;
Ok(Expr::Call {
name: Spanned::new(name, name_span),
args,
})
} else {
Ok(Expr::Var(Spanned::new(name, name_span)))
}
}
_ => Err(self.unexpected("an expression")),
}
}
fn parse_if(&mut self) -> Result<Expr, ParseError> {
self.expect(TokenKind::KwIf, "`if`")?;
let cond = self.parse_expr()?;
self.expect(TokenKind::KwThen, "`then`")?;
let then = self.parse_expr()?;
self.expect(TokenKind::KwElse, "`else`")?;
let otherwise = self.parse_expr()?;
Ok(Expr::If {
cond: Box::new(cond),
then: Box::new(then),
otherwise: Box::new(otherwise),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::tokenize;
fn parse_expr_str(source: &str) -> Expr {
let tokens = tokenize(source).expect("tokenize failed");
let mut parser = Parser::new(tokens, source);
let expr = parser.parse_expr().expect("parse failed");
assert!(parser.is_eof(), "trailing tokens after expression");
expr
}
#[test]
fn parses_if_then_else_and_precedence() {
let e = parse_expr_str(r#"if a == "x" && b then "yes" else "no" + suffix"#);
insta::assert_debug_snapshot!(e);
}
}