1#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
11pub enum Category {
12 Cho,
14 Jung,
16 Jong,
18}
19
20#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
22pub struct Jamo {
23 pub category: Category,
24 pub cp: u32,
26}
27
28impl Jamo {
29 pub fn new(category: Category, cp: u32) -> Self {
30 Self { category, cp }
31 }
32
33 pub fn default_compat(&self) -> Option<u32> {
36 match self.category {
37 Category::Cho => hanmo::cho_compat(self.cp),
38 Category::Jung => hanmo::jung_compat(self.cp),
39 Category::Jong => hanmo::jong_compat(self.cp),
40 }
41 }
42}
43
44#[derive(Clone, Copy, PartialEq, Eq, Debug)]
46pub enum Unit {
47 Jamo(Jamo),
49 Toggle,
51 Virtual(u32),
53}
54
55pub const TOGGLE: u32 = 500;
57
58fn mnemonic_to_compat(core: &str) -> Option<u32> {
60 Some(match core {
61 "G" => 0x3131,
63 "N" => 0x3134,
64 "D" => 0x3137,
65 "R" | "L" => 0x3139,
66 "M" => 0x3141,
67 "B" => 0x3142,
68 "S" => 0x3145,
69 "Q" | "NG" => 0x3147,
70 "J" => 0x3148,
71 "C" => 0x314A,
72 "K" => 0x314B,
73 "T" => 0x314C,
74 "P" => 0x314D,
75 "H" => 0x314E,
76 "GG" => 0x3132,
78 "GS" => 0x3133,
79 "NJ" => 0x3135,
80 "NH" => 0x3136,
81 "DD" => 0x3138,
82 "RG" => 0x313A,
83 "RM" => 0x313B,
84 "RB" => 0x313C,
85 "RS" => 0x313D,
86 "RT" => 0x313E,
87 "RP" => 0x313F,
88 "RH" => 0x3140,
89 "BB" => 0x3143,
90 "BS" => 0x3144,
91 "SS" => 0x3146,
92 "JJ" => 0x3149,
93 "A" => 0x314F,
95 "AE" => 0x3150,
96 "YA" => 0x3151,
97 "YAE" => 0x3152,
98 "EO" => 0x3153,
99 "E" => 0x3154,
100 "YEO" => 0x3155,
101 "YE" => 0x3156,
102 "O" => 0x3157,
103 "WA" => 0x3158,
104 "WAE" => 0x3159,
105 "OI" => 0x315A,
106 "YO" => 0x315B,
107 "U" => 0x315C,
108 "UEO" => 0x315D,
109 "WE" => 0x315E,
110 "WI" => 0x315F,
111 "YU" => 0x3160,
112 "EU" => 0x3161,
113 "EUI" => 0x3162,
114 "I" => 0x3163,
115 _ => return None,
116 })
117}
118
119pub fn resolve_mnemonic(s: &str, ctx: Option<Category>) -> Option<Unit> {
122 let (core, pos_category): (&str, Option<Category>) = if let Some(rest) = s.strip_prefix('_') {
123 (rest, Some(Category::Jong))
124 } else if let Some(rest) = s.strip_suffix('_') {
125 (rest, None) } else {
127 (s, None)
128 };
129 let compat = mnemonic_to_compat(core)?;
130 let category = ctx.or(pos_category).unwrap_or_else(|| {
131 if hanmo::is_vowel_compat(compat) {
132 Category::Jung
133 } else {
134 Category::Cho
135 }
136 });
137 let cp = match category {
138 Category::Cho => hanmo::cho_cp_for_compat(compat)?,
139 Category::Jung => hanmo::jung_cp_for_compat(compat)?,
140 Category::Jong => hanmo::jong_cp_for_compat(compat)?,
141 };
142 Some(Unit::Jamo(Jamo::new(category, cp)))
143}
144
145pub fn resolve_numeric(n: u32) -> Option<Unit> {
150 if n == TOGGLE {
151 return Some(Unit::Toggle);
152 }
153 if n & 0xFFFF == 0 && n >> 16 != 0 {
154 return Some(Unit::Virtual(n >> 16));
155 }
156 category_of_codepoint(n).map(|cat| Unit::Jamo(Jamo::new(cat, n)))
157}
158
159pub fn category_of_codepoint(cp: u32) -> Option<Category> {
161 match cp {
162 0x1100..=0x115F => Some(Category::Cho), 0x1160..=0x11A7 => Some(Category::Jung), 0x11A8..=0x11FF => Some(Category::Jong), 0xA960..=0xA97F => Some(Category::Cho), 0xD7B0..=0xD7CA => Some(Category::Jung), 0xD7CB..=0xD7FF => Some(Category::Jong), _ => None,
169 }
170}
171
172pub fn resolve_operand(s: &str, ctx: Option<Category>) -> Option<Unit> {
174 let s = s.trim();
175 if let Some(n) = parse_int(s) {
176 resolve_numeric(n)
177 } else {
178 resolve_mnemonic(s, ctx)
179 }
180}
181
182pub fn parse_int(s: &str) -> Option<u32> {
184 let s = s.trim();
185 if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
186 u32::from_str_radix(hex, 16).ok()
187 } else {
188 s.parse::<u32>().ok()
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 fn jamo_of(u: Unit) -> Jamo {
197 match u {
198 Unit::Jamo(j) => j,
199 other => panic!("expected jamo, got {other:?}"),
200 }
201 }
202
203 #[test]
204 fn keytable_mnemonics() {
205 assert_eq!(
207 jamo_of(resolve_mnemonic("G_", None).unwrap()),
208 Jamo::new(Category::Cho, 0x1100)
209 );
210 assert_eq!(
212 jamo_of(resolve_mnemonic("_G", None).unwrap()),
213 Jamo::new(Category::Jong, 0x11A8)
214 );
215 assert_eq!(
217 jamo_of(resolve_mnemonic("A_", None).unwrap()),
218 Jamo::new(Category::Jung, 0x1161)
219 );
220 assert_eq!(
221 jamo_of(resolve_mnemonic("O_", None).unwrap()),
222 Jamo::new(Category::Jung, 0x1169)
223 );
224 assert_eq!(
226 jamo_of(resolve_mnemonic("EUI", None).unwrap()),
227 Jamo::new(Category::Jung, 0x1174)
228 );
229 assert_eq!(
231 jamo_of(resolve_mnemonic("_RG", None).unwrap()),
232 Jamo::new(Category::Jong, 0x11B0)
233 );
234 assert_eq!(
236 jamo_of(resolve_mnemonic("Q_", None).unwrap()),
237 Jamo::new(Category::Cho, 0x110B)
238 );
239 assert_eq!(
241 jamo_of(resolve_mnemonic("_Q", None).unwrap()),
242 Jamo::new(Category::Jong, 0x11BC)
243 );
244 }
245
246 #[test]
247 fn unitmix_context_mnemonics() {
248 assert_eq!(
250 jamo_of(resolve_mnemonic("R_", Some(Category::Jong)).unwrap()),
251 Jamo::new(Category::Jong, 0x11AF) );
253 assert_eq!(
254 jamo_of(resolve_mnemonic("S_", Some(Category::Jong)).unwrap()),
255 Jamo::new(Category::Jong, 0x11BA) );
257 assert_eq!(
258 jamo_of(resolve_mnemonic("RS", Some(Category::Jong)).unwrap()),
259 Jamo::new(Category::Jong, 0x11B3) );
261 assert_eq!(
263 jamo_of(resolve_mnemonic("GG", Some(Category::Cho)).unwrap()),
264 Jamo::new(Category::Cho, 0x1101)
265 );
266 assert_eq!(
268 jamo_of(resolve_mnemonic("WA", Some(Category::Jung)).unwrap()),
269 Jamo::new(Category::Jung, 0x116A)
270 );
271 }
272
273 #[test]
274 fn numeric_operands() {
275 assert_eq!(resolve_operand("0x1F4", None), Some(Unit::Toggle));
277 assert_eq!(resolve_operand("500", None), Some(Unit::Toggle));
278 assert_eq!(resolve_operand("0x800000", None), Some(Unit::Virtual(128)));
280 assert_eq!(resolve_operand("0x810000", None), Some(Unit::Virtual(129)));
281 assert_eq!(resolve_operand("0x820000", None), Some(Unit::Virtual(130)));
282 }
283
284 #[test]
285 fn raw_old_hangul_codepoint() {
286 assert_eq!(
288 resolve_operand("0x114C", None),
289 Some(Unit::Jamo(Jamo::new(Category::Cho, 0x114C)))
290 );
291 assert_eq!(
293 resolve_operand("0x119E", None),
294 Some(Unit::Jamo(Jamo::new(Category::Jung, 0x119E)))
295 );
296 }
297
298 #[test]
299 fn default_compat_roundtrip() {
300 assert_eq!(
301 Jamo::new(Category::Cho, 0x1100).default_compat(),
302 Some(0x3131)
303 );
304 assert_eq!(
305 Jamo::new(Category::Jong, 0x11A8).default_compat(),
306 Some(0x3131)
307 );
308 assert_eq!(
309 Jamo::new(Category::Jung, 0x1161).default_compat(),
310 Some(0x314F)
311 );
312 assert_eq!(
313 Jamo::new(Category::Jong, 0x11B0).default_compat(),
314 Some(0x313A)
315 );
316 }
317}