Skip to main content

skey_engine/
lib.rs

1//! skey-engine — Stateless Vietnamese input method engine.
2//!
3//! Letter-based accumulator model for Vietnamese input.
4//! Key features:
5//!   - Stateless: input string → output string
6//!   - Letter struct with variant + tone tracking
7//!   - Modifier keys mutate the last vowel in place
8//!   - Precomputed UTF-8 tables for output
9//!   - Correct tone placement for all diphthong/triphthong cases
10
11pub mod tables;
12pub mod engine;
13pub mod spelling;
14
15use std::ffi::{CStr, CString};
16use std::os::raw::c_char;
17
18/// Input method.
19#[derive(Clone, Copy, PartialEq, Eq)]
20pub enum Method { Telex, Vni, TeipVni, Viqr }
21
22/// Opaque engine handle — holds only configuration.
23pub struct SkeyEngine {
24    method: Method,
25    modern: bool,
26    short_w: bool,
27}
28
29impl SkeyEngine {
30    pub fn new(method: Method) -> Self {
31        SkeyEngine { method, modern: true, short_w: false }
32    }
33
34    pub fn set_method(&mut self, method: Method) { self.method = method; }
35    pub fn set_modern(&mut self, v: bool) { self.modern = v; }
36    pub fn set_short_w(&mut self, v: bool) { self.short_w = v; }
37
38    pub fn transform(&self, input: &str) -> String {
39        match self.method {
40            Method::Telex => engine::convert_telex(input, self.modern, self.short_w),
41            Method::Vni => engine::convert_vni(input, self.modern, self.short_w),
42            Method::TeipVni => engine::convert_teip_vni(input, self.modern, self.short_w),
43            Method::Viqr => engine::convert_viqr(input),
44        }
45    }
46
47    pub fn is_valid(s: &str) -> bool {
48        spelling::is_valid_cvc(s)
49    }
50}
51
52// ── C FFI ──────────────────────────────────────────────────────────
53
54#[unsafe(no_mangle)]
55pub unsafe extern "C" fn skey_engine_new(method: i32) -> *mut SkeyEngine {
56    let m = match method {
57        0 => Method::Telex,    // SKEY_METHOD_TELEX
58        1 => Method::Vni,      // SKEY_METHOD_VNI
59        2 => Method::Viqr,     // SKEY_METHOD_VIQR
60        3 => Method::TeipVni,  // SKEY_METHOD_TEIP_VNI
61        _ => Method::Telex,
62    };
63    Box::into_raw(Box::new(SkeyEngine::new(m)))
64}
65
66#[unsafe(no_mangle)]
67pub unsafe extern "C" fn skey_engine_free(engine: *mut SkeyEngine) {
68    if !engine.is_null() { unsafe { drop(Box::from_raw(engine)); } }
69}
70
71#[unsafe(no_mangle)]
72pub unsafe extern "C" fn skey_engine_set_method(engine: *mut SkeyEngine, method: i32) {
73    if engine.is_null() { return; }
74    let m = match method {
75        0 => Method::Telex,
76        1 => Method::Vni,
77        2 => Method::Viqr,
78        3 => Method::TeipVni,
79        _ => Method::Telex,
80    };
81    unsafe { (*engine).set_method(m); }
82}
83
84/// tone_style: 0=traditional (hoà, thuỷ), 1=modern (hòa, thủy). Default modern.
85#[unsafe(no_mangle)]
86pub unsafe extern "C" fn skey_engine_set_tone_style(e: *mut SkeyEngine, v: i32) {
87    if !e.is_null() { unsafe { (*e).set_modern(v != 0); } }
88}
89#[unsafe(no_mangle)]
90pub unsafe extern "C" fn skey_engine_set_free_marking(_e: *mut SkeyEngine, _v: i32) {}
91#[unsafe(no_mangle)]
92pub unsafe extern "C" fn skey_engine_set_short_w(e: *mut SkeyEngine, v: i32) {
93    if !e.is_null() { unsafe { (*e).set_short_w(v != 0); } }
94}
95#[unsafe(no_mangle)]
96pub unsafe extern "C" fn skey_engine_set_bracket_uo(_e: *mut SkeyEngine, _v: i32) {}
97
98#[unsafe(no_mangle)]
99pub unsafe extern "C" fn skey_engine_transform(
100    engine: *const SkeyEngine,
101    input: *const c_char,
102) -> *mut c_char {
103    if engine.is_null() || input.is_null() { return std::ptr::null_mut(); }
104    let e = unsafe { &*engine };
105    let s = match unsafe { CStr::from_ptr(input) }.to_str() {
106        Ok(s) => s, Err(_) => return std::ptr::null_mut(),
107    };
108    CString::new(e.transform(s)).unwrap_or_default().into_raw()
109}
110
111#[unsafe(no_mangle)]
112pub unsafe extern "C" fn skey_engine_is_valid(s: *const c_char) -> i32 {
113    if s.is_null() { return 0; }
114    let s = match unsafe { CStr::from_ptr(s) }.to_str() {
115        Ok(s) => s, Err(_) => return 0,
116    };
117    if SkeyEngine::is_valid(s) { 1 } else { 0 }
118}
119
120#[unsafe(no_mangle)]
121pub unsafe extern "C" fn skey_free_string(s: *mut c_char) {
122    if !s.is_null() { unsafe { drop(CString::from_raw(s)); } }
123}