Skip to main content

msime_engine/stroke/
mod.rs

1//! 笔画输入:按笔顺键入横竖撇点折,读 `msime-stroke.db`(`language_dictionary`)给出单字候选。只读,不学习,不做繁简转换。
2//!
3//! 键位:`h` 横、`s` 竖、`p` 撇、`n` 点(捺)、`z` 折,`x` 是匹配任意一笔的通配符。全部是小写字母,所以不占用任何标点键,也不需要宿主把它们当拼写符号转发。
4
5pub mod scheme;
6
7pub use scheme::{StrokeCandidate, StrokeScheme};
8
9/// 五种笔画的键,按横竖撇点折的顺序。
10pub const STROKES: &str = "hspnz";
11
12/// 匹配任意一笔的通配键。空组合时不开始组合。
13pub const WILDCARD: u8 = b'x';
14
15/// 一个组合最多的笔画数(通配符也算一笔);更多的键被吞掉。
16pub const MAX_STROKES: usize = 64;
17
18/// `key` 是五种笔画之一。
19pub const fn is_stroke(key: u8) -> bool {
20    matches!(key, b'h' | b's' | b'p' | b'n' | b'z')
21}
22
23/// `key` 是笔画或通配符,也就是组合里能出现的键。
24pub const fn is_key(key: u8) -> bool {
25    is_stroke(key) || key == WILDCARD
26}
27
28/// 预编辑里显示的笔画字形:h→一 s→丨 p→丿 n→丶 z→乛 x→*。其他键没有字形。
29pub const fn glyph(key: u8) -> Option<char> {
30    match key {
31        b'h' => Some('一'),
32        b's' => Some('丨'),
33        b'p' => Some('丿'),
34        b'n' => Some('丶'),
35        b'z' => Some('乛'),
36        WILDCARD => Some('*'),
37        _ => None,
38    }
39}
40
41/// 字形回到键,`glyph` 的反函数;宿主编辑时可能送回字形。
42pub const fn key_of_glyph(glyph: char) -> Option<u8> {
43    match glyph {
44        '一' => Some(b'h'),
45        '丨' => Some(b's'),
46        '丿' => Some(b'p'),
47        '丶' => Some(b'n'),
48        '乛' => Some(b'z'),
49        '*' => Some(WILDCARD),
50        _ => None,
51    }
52}
53
54/// 测试用的合成 `msime-stroke.db`:共用 `language_dictionary::SCHEMA`,权重是虚构的。
55#[cfg(test)]
56pub(crate) mod fixture {
57    use std::path::Path;
58
59    use rusqlite::Connection;
60
61    use crate::language_dictionary::{
62        FORMAT_VERSION, METADATA_FORMAT_VERSION, METADATA_LICENSE, METADATA_SOURCE_COMMIT, SCHEMA,
63    };
64
65    /// `土` 有两个笔画码,用来检查同一个字只列一次;`乚` 没有字频,用来检查它排在有字频的补全后面。
66    pub(crate) const ENTRIES: [(&str, &str, i64); 12] = [
67        ("h", "一", 9000),
68        ("hh", "二", 5000),
69        ("hhh", "三", 4000),
70        ("hs", "十", 4500),
71        ("hsh", "土", 2000),
72        ("hshh", "土", 10),
73        ("hhsh", "王", 2500),
74        ("hhs", "干", 1500),
75        ("hpn", "大", 5500),
76        ("pn", "人", 6000),
77        ("szh", "口", 3500),
78        ("sz", "乚", 0),
79    ];
80
81    pub(crate) fn build(path: &Path) {
82        let connection = Connection::open(path).unwrap();
83        connection.execute_batch(SCHEMA).unwrap();
84        for (name, value) in [
85            (METADATA_FORMAT_VERSION, FORMAT_VERSION.to_string()),
86            (
87                METADATA_SOURCE_COMMIT,
88                "0000000000000000000000000000000000000000".to_owned(),
89            ),
90            (METADATA_LICENSE, "LGPL-3.0-only".to_owned()),
91        ] {
92            connection
93                .execute("INSERT INTO metadata VALUES (?1, ?2)", (name, value))
94                .unwrap();
95        }
96        for stroke in super::STROKES.chars() {
97            connection
98                .execute("INSERT INTO syllables VALUES (?1)", (stroke.to_string(),))
99                .unwrap();
100        }
101        for (key, text, weight) in ENTRIES {
102            connection
103                .execute(
104                    "INSERT INTO entries VALUES (?1, ?2, ?3)",
105                    (key, text, weight),
106                )
107                .unwrap();
108        }
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn every_key_has_a_glyph_that_maps_back() {
118        for key in STROKES.bytes().chain([WILDCARD]) {
119            assert!(is_key(key));
120            let glyph = glyph(key).unwrap();
121            assert_eq!(key_of_glyph(glyph), Some(key));
122        }
123        assert!(!is_stroke(WILDCARD));
124        for key in *b"aH1'*" {
125            assert!(!is_key(key));
126            assert_eq!(glyph(key), None);
127        }
128        assert_eq!(key_of_glyph('a'), None);
129    }
130}