use std::fmt::Display;
use nom::{
bytes::complete::tag,
character::complete::{char, multispace0},
error::context,
multi::many0,
sequence::tuple,
};
use crate::xpath::{
grammar::{
recipes::Res,
terminal_symbols::symbol_separator,
whitespace_recipes::{sep, ws},
},
xpath_item_set::XpathItemSet,
ExpressionApplyError, XpathExpressionContext,
};
use super::{
expr_single,
primary_expressions::variable_references::{var_name, VarName},
ExprSingle,
};
pub fn let_expr(input: &str) -> Res<&str, LetExpr> {
context(
"let_expr",
sep((simple_let_clause, tag("return"), expr_single)),
)(input)
.map(|(next_input, res)| {
(
next_input,
LetExpr {
clause: res.0,
expr: res.2,
},
)
})
}
#[derive(PartialEq, Debug, Clone)]
pub struct LetExpr {
pub clause: SimpleLetClause,
pub expr: ExprSingle,
}
impl LetExpr {
pub(crate) fn eval<'tree>(
&self,
context: &XpathExpressionContext<'tree>,
) -> Result<XpathItemSet<'tree>, ExpressionApplyError> {
let mut bindings = vec![&self.clause.binding];
bindings.extend(self.clause.extras.iter());
Self::eval_bindings(context, &bindings, &self.expr)
}
fn eval_bindings<'tree>(
context: &XpathExpressionContext<'tree>,
bindings: &[&SimpleLetBinding],
return_expr: &ExprSingle,
) -> Result<XpathItemSet<'tree>, ExpressionApplyError> {
let (binding, rest) = match bindings.split_first() {
Some(pair) => pair,
None => {
return return_expr.eval(context);
}
};
let value = binding.expr.eval(context)?;
let var_name = binding.var.to_string();
let inner_context = context.with_variable(var_name, value);
Self::eval_bindings(&inner_context, rest, return_expr)
}
}
impl Display for LetExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} return {}", self.clause, self.expr)
}
}
fn simple_let_clause(input: &str) -> Res<&str, SimpleLetClause> {
context(
"simple_let_clause",
tuple((
tag("let"),
symbol_separator,
simple_let_binding,
many0(tuple((char(','), multispace0, simple_let_binding))),
)),
)(input)
.map(|(next_input, res)| {
let extras = res.3.into_iter().map(|(_, _, binding)| binding).collect();
(
next_input,
SimpleLetClause {
binding: res.2,
extras,
},
)
})
}
#[derive(PartialEq, Debug, Clone)]
pub struct SimpleLetClause {
pub binding: SimpleLetBinding,
pub extras: Vec<SimpleLetBinding>,
}
impl Display for SimpleLetClause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "let {}", self.binding)?;
for binding in &self.extras {
write!(f, ", {}", binding)?;
}
Ok(())
}
}
fn simple_let_binding(input: &str) -> Res<&str, SimpleLetBinding> {
context(
"simple_let_binding",
ws((char('$'), var_name, tag(":="), expr_single)),
)(input)
.map(|(next_input, res)| {
(
next_input,
SimpleLetBinding {
var: res.1,
expr: res.3,
},
)
})
}
#[derive(PartialEq, Debug, Clone)]
pub struct SimpleLetBinding {
pub var: VarName,
pub expr: ExprSingle,
}
impl Display for SimpleLetBinding {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "${} := {}", self.var, self.expr)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn let_expr_should_parse() {
let input = r#"let $x:=4,$y:=3 return $x+$y"#;
let (next_input, res) = let_expr(input).unwrap();
assert_eq!(next_input, "");
assert_eq!(res.to_string(), r#"let $x := 4, $y := 3 return $x + $y"#);
}
#[test]
fn let_expr_should_parse_whitespace() {
let input = r#"let $x := 4, $y := 3
return $x + $y"#;
let (next_input, res) = let_expr(input).unwrap();
assert_eq!(next_input, "");
assert_eq!(res.to_string(), r#"let $x := 4, $y := 3 return $x + $y"#);
}
}