Skip to main content

genomicframe_core/formats/bam/
expression.rs

1use crate::error::{Error, Result};
2use crate::expression::{extract_string, extract_u8, ExprToFilter};
3use crate::expression::{
4    extract_u32, CompiledAndFilter, CompiledNotFilter, CompiledOrFilter, Expr,
5};
6use crate::filters::RecordFilter;
7use crate::formats::bam::filters::*;
8use crate::formats::bam::BamRecord;
9
10impl ExprToFilter<BamRecord> for Expr {
11    fn compile(&self) -> Result<Box<dyn RecordFilter<BamRecord>>> {
12        match self {
13            // Chromosome filter - equality check
14            // Support both "chrom" (common name) and "rname" (BAM field name)
15            Expr::Eq(left, right) if matches!(**left, Expr::Column(ref name) if name == "chrom" || name == "rname" || name == "refname" || name == "refid") =>
16            {
17                let chrom = extract_string(right)?;
18                Ok(Box::new(ChromosomeFilter::new(chrom)))
19            }
20
21            // Strand filter
22            Expr::Eq(left, right) if matches!(**left, Expr::Column(ref name) if name == "strand") =>
23            {
24                let strand_str = extract_string(right)?;
25                let strand = if strand_str == "+" {
26                    crate::formats::bam::filters::Strand::Forward
27                } else if strand_str == "-" {
28                    crate::formats::bam::filters::Strand::Reverse
29                } else {
30                    return Err(Error::invalid_input("Strand must be '+' or '-'"));
31                };
32                Ok(Box::new(StrandFilter { strand }))
33            }
34
35            // Mapping quality
36            Expr::Gt(left, right) | Expr::Gte(left, right) if matches!(**left, Expr::Column(ref name) if name == "mapq") =>
37            {
38                let min_mapq = extract_u8(right)?;
39                Ok(Box::new(MappingQualityFilter { min_mapq }))
40            }
41
42            // Read length
43            Expr::Gt(left, right) | Expr::Gte(left, right) if matches!(**left, Expr::Column(ref name) if name == "read_length") =>
44            {
45                let min_length = extract_u32(right)?;
46                Ok(Box::new(ReadLengthFilter {
47                    min_length: min_length as usize,
48                    max_length: None,
49                }))
50            }
51
52            // Boolean filters
53            Expr::Column(name) if name == "proper_pair" => Ok(Box::new(ProperPairFilter)),
54            Expr::Column(name) if name == "primary" => Ok(Box::new(PrimaryAlignmentFilter)),
55            Expr::Column(name) if name == "mapped" => Ok(Box::new(MappedOnlyFilter)),
56            Expr::Column(name) if name == "qc_pass" => Ok(Box::new(QcPassFilter)),
57            Expr::Column(name) if name == "first_in_pair" => Ok(Box::new(PairPositionFilter {
58                position: PairPosition::First,
59            })),
60            Expr::Column(name) if name == "second_in_pair" => Ok(Box::new(PairPositionFilter {
61                position: PairPosition::Second,
62            })),
63
64            // Negated boolean filters
65            Expr::Not(inner) if matches!(**inner, Expr::Column(ref name) if name == "duplicate") => {
66                Ok(Box::new(NoDuplicatesFilter))
67            }
68
69            // Boolean logic
70            Expr::And(exprs) => {
71                if exprs.is_empty() {
72                    return Err(Error::invalid_input("Empty AND expression"));
73                }
74
75                let mut result = exprs[0].compile()?;
76                for expr in &exprs[1..] {
77                    let next = expr.compile()?;
78                    result = Box::new(CompiledAndFilter {
79                        left: result,
80                        right: next,
81                    });
82                }
83                Ok(result)
84            }
85
86            Expr::Or(exprs) => {
87                if exprs.is_empty() {
88                    return Err(Error::invalid_input("Empty OR expression"));
89                }
90
91                let mut result = exprs[0].compile()?;
92                for expr in &exprs[1..] {
93                    let next = expr.compile()?;
94                    result = Box::new(CompiledOrFilter {
95                        left: result,
96                        right: next,
97                    });
98                }
99                Ok(result)
100            }
101
102            Expr::Not(expr) => {
103                let inner = expr.compile()?;
104                Ok(Box::new(CompiledNotFilter { inner }))
105            }
106
107            _ => Err(Error::invalid_input(format!(
108                "Expression not supported for BAM: {}",
109                self
110            ))),
111        }
112    }
113}