1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
use crate::{expr::Expr, parser::util::recognize_string_template};
// #todo remove excessive clones.
// #todo find a better, more general name for this stage.
// #insight prune does not err.
// #insight prune strips unnecessary auxiliary exprs not needed for evaluation.
// #todo strip quoting of literals (nops)
// #todo consider only allowing the sigils, and not quot/unquot -> no, we need them to maintain the list/tree abstraction, it has to be syntax-sugar!
// #todo actually we could skip the `unquot`, think about it.
// #insight no need to convert Symbol to KeySymbol, just converting List -> Array works.
pub fn prune_fn(expr: Expr) -> Option<Expr> {
// #todo use `extract` instead of `unpack`.
let (unpacked_expr, annotations) = expr.extract();
match unpacked_expr {
Expr::Comment(..) => {
// #todo move prune elsewhere.
// Prune Comment expressions.
None
}
Expr::TextSeparator => {
// #todo remove TextSeparator.
// #todo move prune elsewhere.
// Prune TextSeparator expressions.
None
}
Expr::String(str) => {
// #insight
// only apply the transformation, error checking happened in the
// parsing stage.
if str.contains("${") {
match recognize_string_template(str) {
Ok(format_expr) => {
// #todo extract a helper function.
if let Some(annotations) = annotations {
Some(Expr::Annotated(Box::new(format_expr), annotations.clone()))
} else {
Some(format_expr)
}
}
Err(_) => {
// #todo what should be done here?
// #insight this state should not be valid.
panic!("invalid state");
}
}
} else {
Some(expr)
}
}
// #todo resolve quoting+interpolation here? i.e. quasiquoting
// #todo maybe even resolve string interpolation here?
_ => Some(expr),
}
}
pub fn prune(expr: Expr) -> Option<Expr> {
expr.filter_transform(&prune_fn)
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use crate::{api::parse_string, expr::Expr, prune::prune};
#[test]
fn prune_removes_comments() {
let input = "(do ; comment\n(let a [1 2 3 4]) ; a comment\n(writeln (+ 2 3)))";
let expr = parse_string(input).unwrap();
let expr = prune(expr).unwrap();
let s = format!("{expr}");
assert!(s.contains("(do (let a (Array 1 2 3 4)) (writeln (+ 2 3)))"));
}
#[test]
fn prune_transforms_template_strings() {
let input = r#"(let m "An amount: $110.00. Here is a number: ${num}, and another: ${another-num}")"#;
let expr = parse_string(input).unwrap();
let expr = prune(expr).unwrap();
let Expr::List(exprs) = expr.unpack() else {
panic!("assertion failed: invalid form")
};
// dbg!(exprs);
let Expr::List(ref exprs) = exprs[2].unpack() else {
panic!("assertion failed: invalid form")
};
assert_matches!(&exprs[0].unpack(), Expr::Symbol(s) if s == "format");
assert_eq!(exprs.len(), 5);
}
}