use crate::token::{CallArg, Expr, Node, TokenKey};
#[derive(Debug)]
pub enum FusedPattern<'a> {
RangeSum {
start: Option<&'a Node>,
end: &'a Node,
},
}
pub fn recognize_fused(expr: &Expr) -> Option<FusedPattern<'_>> {
match_range_sum(expr)
}
fn match_range_sum(expr: &Expr) -> Option<FusedPattern<'_>> {
let Expr::FnCall { path, args } = expr else {
return None;
};
if path.len() != 2 {
return None;
}
if !matches!(&path[0], TokenKey::String(s, _, _) if s == "list") {
return None;
}
if !matches!(&path[1], TokenKey::String(s, _, _) if s == "sum") {
return None;
}
if args.len() != 1 || args[0].name.is_some() {
return None;
}
let (start, end) = match_bare_range(&args[0].value.expr)?;
Some(FusedPattern::RangeSum { start, end })
}
fn match_bare_range(expr: &Expr) -> Option<(Option<&Node>, &Node)> {
let Expr::FnCall { path, args } = expr else {
return None;
};
if path.len() != 1 {
return None;
}
if !matches!(&path[0], TokenKey::String(s, _, _) if s == "range") {
return None;
}
if args.iter().any(arg_is_named) {
return None;
}
match args.len() {
1 => Some((None, &args[0].value)),
2 => Some((Some(&args[0].value), &args[1].value)),
_ => None,
}
}
fn arg_is_named(a: &CallArg) -> bool {
a.name.is_some()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parse_document;
fn body_expr(src: &str) -> Node {
parse_document(src).expect("parse")
}
fn find_fncall(node: &Node) -> Option<Node> {
if matches!(node.expr.as_ref(), Expr::FnCall { .. }) {
return Some(node.clone());
}
for child in crate::child_nodes(node) {
if let Some(found) = find_fncall(child) {
return Some(found);
}
}
None
}
#[test]
fn matches_range_end() {
let doc = body_expr("#main(Int n) -> Int\nlist.sum(range(n))");
let call = find_fncall(&doc).expect("fncall");
let pat = recognize_fused(&call.expr).expect("match");
match pat {
FusedPattern::RangeSum { start, end: _ } => assert!(start.is_none()),
}
}
#[test]
fn matches_range_start_end() {
let doc = body_expr("#main(Int n) -> Int\nlist.sum(range(5, n))");
let call = find_fncall(&doc).expect("fncall");
let pat = recognize_fused(&call.expr).expect("match");
match pat {
FusedPattern::RangeSum { start, end: _ } => assert!(start.is_some()),
}
}
#[test]
fn rejects_three_arg_range() {
let doc = body_expr("#main(Int n) -> Int\nlist.sum(range(1, n, 2))");
let call = find_fncall(&doc).expect("fncall");
assert!(recognize_fused(&call.expr).is_none());
}
#[test]
fn rejects_map_chain() {
let doc = body_expr("#main(Int n) -> Int\nlist.sum(range(n).map((i) => i))");
let call = find_fncall(&doc).expect("fncall");
assert!(recognize_fused(&call.expr).is_none());
}
#[test]
fn rejects_other_calls() {
let doc = body_expr("#main(Int n) -> Int\nlist.max([1, 2, 3])");
let call = find_fncall(&doc).expect("fncall");
assert!(recognize_fused(&call.expr).is_none());
}
}