genomicframe-core 0.2.0

High-performance genomics I/O and interoperability layer
Documentation
use crate::error::{Error, Result};
use crate::expression::{extract_string, extract_u8, ExprToFilter};
use crate::expression::{
    extract_u32, CompiledAndFilter, CompiledNotFilter, CompiledOrFilter, Expr,
};
use crate::filters::RecordFilter;
use crate::formats::bam::filters::*;
use crate::formats::bam::BamRecord;

impl ExprToFilter<BamRecord> for Expr {
    fn compile(&self) -> Result<Box<dyn RecordFilter<BamRecord>>> {
        match self {
            // Chromosome filter - equality check
            // Support both "chrom" (common name) and "rname" (BAM field name)
            Expr::Eq(left, right) if matches!(**left, Expr::Column(ref name) if name == "chrom" || name == "rname" || name == "refname" || name == "refid") =>
            {
                let chrom = extract_string(right)?;
                Ok(Box::new(ChromosomeFilter::new(chrom)))
            }

            // Strand filter
            Expr::Eq(left, right) if matches!(**left, Expr::Column(ref name) if name == "strand") =>
            {
                let strand_str = extract_string(right)?;
                let strand = if strand_str == "+" {
                    crate::formats::bam::filters::Strand::Forward
                } else if strand_str == "-" {
                    crate::formats::bam::filters::Strand::Reverse
                } else {
                    return Err(Error::invalid_input("Strand must be '+' or '-'"));
                };
                Ok(Box::new(StrandFilter { strand }))
            }

            // Mapping quality
            Expr::Gt(left, right) | Expr::Gte(left, right) if matches!(**left, Expr::Column(ref name) if name == "mapq") =>
            {
                let min_mapq = extract_u8(right)?;
                Ok(Box::new(MappingQualityFilter { min_mapq }))
            }

            // Read length
            Expr::Gt(left, right) | Expr::Gte(left, right) if matches!(**left, Expr::Column(ref name) if name == "read_length") =>
            {
                let min_length = extract_u32(right)?;
                Ok(Box::new(ReadLengthFilter {
                    min_length: min_length as usize,
                    max_length: None,
                }))
            }

            // Boolean filters
            Expr::Column(name) if name == "proper_pair" => Ok(Box::new(ProperPairFilter)),
            Expr::Column(name) if name == "primary" => Ok(Box::new(PrimaryAlignmentFilter)),
            Expr::Column(name) if name == "mapped" => Ok(Box::new(MappedOnlyFilter)),
            Expr::Column(name) if name == "qc_pass" => Ok(Box::new(QcPassFilter)),
            Expr::Column(name) if name == "first_in_pair" => Ok(Box::new(PairPositionFilter {
                position: PairPosition::First,
            })),
            Expr::Column(name) if name == "second_in_pair" => Ok(Box::new(PairPositionFilter {
                position: PairPosition::Second,
            })),

            // Negated boolean filters
            Expr::Not(inner) if matches!(**inner, Expr::Column(ref name) if name == "duplicate") => {
                Ok(Box::new(NoDuplicatesFilter))
            }

            // Boolean logic
            Expr::And(exprs) => {
                if exprs.is_empty() {
                    return Err(Error::invalid_input("Empty AND expression"));
                }

                let mut result = exprs[0].compile()?;
                for expr in &exprs[1..] {
                    let next = expr.compile()?;
                    result = Box::new(CompiledAndFilter {
                        left: result,
                        right: next,
                    });
                }
                Ok(result)
            }

            Expr::Or(exprs) => {
                if exprs.is_empty() {
                    return Err(Error::invalid_input("Empty OR expression"));
                }

                let mut result = exprs[0].compile()?;
                for expr in &exprs[1..] {
                    let next = expr.compile()?;
                    result = Box::new(CompiledOrFilter {
                        left: result,
                        right: next,
                    });
                }
                Ok(result)
            }

            Expr::Not(expr) => {
                let inner = expr.compile()?;
                Ok(Box::new(CompiledNotFilter { inner }))
            }

            _ => Err(Error::invalid_input(format!(
                "Expression not supported for BAM: {}",
                self
            ))),
        }
    }
}