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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#![allow(dead_code)]

//! This library provides auto-correct suggestions that are within 1 edit distance from
//! known English words.

#[macro_use]
extern crate lazy_static;
extern crate threads_pool;
extern crate crossbeam_channel;

mod candidate;
mod common;
mod config;
mod dynamic_mode;

pub mod prelude {
    pub use AutoCorrect;
    pub use config::{AutoCorrectConfig, Config, SupportedLocale};
    pub use candidate::Candidate;
}

use std::sync::{mpsc, Arc};
use candidate::Candidate;
use config::{AutoCorrectConfig, Config, SupportedLocale};
use crossbeam_channel as channel;
use threads_pool::*;

//TODO: define config struct -- 1. memory mode vs. speed mode;
//TODO: customizable score function

pub struct AutoCorrect {
    config: Config,
    pool: Arc<ThreadPool>,
}

impl AutoCorrect {
    pub fn new() -> AutoCorrect {
        AutoCorrect::new_with_config(Config::new())
    }

    pub fn new_with_config(config: Config) -> AutoCorrect {
        let service = AutoCorrect {
            config,
            pool: Arc::new(ThreadPool::new(2)),
        };

        service.refresh_dict();
        service
    }

    pub fn candidates(&self, word: String) -> Vec<Candidate> {
        dynamic_mode::candidate(
            word,
            0,
            &self.config,
            Arc::clone(&self.pool),
            None)
    }

    pub fn candidates_async(&self, word: String, tx: mpsc::Sender<Candidate>) {
        let config_clone = self.config.clone();
        let pool_arc = Arc::clone(&self.pool);

        let (tx_cache, rx_cache) = channel::unbounded();
        self.pool.execute(move || {
            dynamic_mode::candidate(
                word,
                0,
                &config_clone,
                pool_arc,
                Some(tx_cache));
        });

        let mut cache = Vec::new();
        for result in rx_cache {
            if !cache.contains(&result.word) {
                cache.push(result.word.clone());

                // send the result back, if the channel is closed, just return.
                if let Err(_) = tx.send(result) {
                    break;
                }
            }
        }
    }

    fn refresh_dict(&self) {
        //TODO: if speed mode, also load the variation1 (and variation 2 if allowing 2 misses)
        dynamic_mode::initialize(&self);
    }
}

impl AutoCorrectConfig for AutoCorrect {
    #[inline]
    fn set_max_edit(&mut self, max_edit: u8) {
        if max_edit == self.config.get_max_edit() {
            return;
        }

        self.config.set_max_edit(max_edit);
    }

    #[inline]
    fn get_max_edit(&self) -> u8 {
        self.config.get_max_edit()
    }

    #[inline]
    fn set_locale(&mut self, locale: SupportedLocale) {
        if locale == self.config.get_locale() {
            return;
        }

        self.config.set_locale(locale);

        if !self.config.get_override_dict().is_empty() {
            self.refresh_dict();
        }
    }

    #[inline]
    fn get_locale(&self) -> SupportedLocale {
        self.config.get_locale()
    }

    #[inline]
    fn set_override_dict(&mut self, dict_path: &str) {
        if dict_path == self.config.get_dict_path() {
            return;
        }

        self.config.set_override_dict(dict_path);
        self.refresh_dict();
    }

    #[inline]
    fn get_override_dict(&self) -> String {
        self.config.get_override_dict()
    }
}