neek-core 1.0.0

Core fuzzy matching and scoring engine
Documentation
//! Scoring constants and algorithm configuration.

/// Per-character penalty for unmatched characters **before** the first match.
pub const SCORE_GAP_LEADING: f64 = -0.005;

/// Per-character penalty for unmatched characters **after** the last match.
pub const SCORE_GAP_TRAILING: f64 = -0.005;

/// Per-character penalty for unmatched characters **between** two matches.
pub const SCORE_GAP_INNER: f64 = -0.01;

/// Bonus for a match that immediately follows the previous match.
pub const SCORE_MATCH_CONSECUTIVE: f64 = 1.0;

/// Bonus for a match on a character immediately after `/`.
pub const SCORE_MATCH_SLASH: f64 = 0.9;

/// Bonus for a match on a character that starts a word
/// (preceded by `-`, `_`, or ` `).
pub const SCORE_MATCH_WORD: f64 = 0.8;

/// Bonus for a match on a lowercase letter that follows an uppercase letter
/// (CamelCase boundary).
pub const SCORE_MATCH_CAPITAL: f64 = 0.7;

/// Bonus for a match on a character immediately after `.`.
pub const SCORE_MATCH_DOT: f64 = 0.6;

/// Leading-gap penalty used by [`Prefer::Prefix`].
///
/// Heavy enough to outweigh [`SCORE_MATCH_CONSECUTIVE`] after two skipped
/// characters, so any match at position 0 beats a consecutive match at
/// position 2 or later.
pub const SCORE_PREFIX_GAP_LEADING: f64 = -0.5;

/// Trailing-gap penalty used by [`Prefer::Suffix`].
///
/// Symmetric to [`SCORE_PREFIX_GAP_LEADING`]: a match ending two characters
/// before the haystack end is penalised more than a consecutive bonus is worth.
pub const SCORE_SUFFIX_GAP_TRAILING: f64 = -0.5;

/// Controls how the scoring algorithm weighs structural match patterns.
///
/// Each variant selects a different set of gap-penalty constants for the
/// Gotoh DP alignment kernel.  The matching pass ([`has_match`]) is
/// unaffected; only relative ordering of scores changes.
///
/// [`has_match`]: crate::has_match
///
/// # Examples
///
/// ```rust
/// use neek_core::{Scorer, config::Prefer};
///
/// let scorer = Scorer::new(Prefer::Prefix);
/// // "build" starts with 'b'; "table" has a contiguous "bl" mid-string.
/// // Prefix-biased scoring ranks "build" higher.
/// assert!(scorer.score(b"bl", b"build") > scorer.score(b"bl", b"table"));
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Prefer {
  /// Default: rewards contiguous character runs and word-boundary matches.
  ///
  /// Balanced gap penalties mean a run of adjacent matched characters can
  /// outscore a match starting at position 0.  For example, matching `bl`
  /// against `build bundle table` ranks `table` highest because `b` and `l`
  /// are adjacent there.
  #[default]
  Contiguous,

  /// Prefix-biased: rewards matches that start as early as possible.
  ///
  /// Applies a heavy per-character penalty ([`SCORE_PREFIX_GAP_LEADING`])
  /// for every haystack byte that precedes the first matched needle character.
  /// Matching `bl` against `build bundle table` ranks `build` and `bundle`
  /// above `table` because their `b` appears at position 0.
  Prefix,

  /// Suffix-biased: rewards matches that end as late as possible.
  ///
  /// Applies a heavy per-character penalty ([`SCORE_SUFFIX_GAP_TRAILING`])
  /// for every haystack byte that follows the last matched needle character.
  /// Useful for queries targeting file extensions, version suffixes, or
  /// trailing tokens.
  Suffix,
}

/// Gap-penalty triplet passed into the DP kernel.
///
/// This is an implementation detail; callers use [`Prefer`] and [`Scorer`]
/// instead.
///
/// [`Scorer`]: crate::Scorer
#[derive(Clone, Copy)]
pub(crate) struct GapConfig {
  pub(crate) leading:  f64,
  pub(crate) inner:    f64,
  pub(crate) trailing: f64,
}

impl GapConfig {
  pub(crate) const CONTIGUOUS: Self = Self {
    leading:  SCORE_GAP_LEADING,
    inner:    SCORE_GAP_INNER,
    trailing: SCORE_GAP_TRAILING,
  };

  pub(crate) const PREFIX: Self = Self {
    leading:  SCORE_PREFIX_GAP_LEADING,
    inner:    SCORE_GAP_INNER,
    trailing: SCORE_GAP_TRAILING,
  };

  pub(crate) const SUFFIX: Self = Self {
    leading:  SCORE_GAP_LEADING,
    inner:    SCORE_GAP_INNER,
    trailing: SCORE_SUFFIX_GAP_TRAILING,
  };

  #[inline(always)]
  pub(crate) fn from_prefer(prefer: Prefer) -> Self {
    match prefer {
      Prefer::Contiguous => Self::CONTIGUOUS,
      Prefer::Prefix => Self::PREFIX,
      Prefer::Suffix => Self::SUFFIX,
    }
  }
}