urbit_ob/
co.rs

1use ibig::UBig;
2use num_traits::{Pow, Zero};
3use once_cell::sync::Lazy;
4use std::{cmp::PartialEq, collections::HashMap, ops::Rem};
5
6use super::ob::{fein, fynd};
7
8/// Parsing and Formatting errors
9#[derive(thiserror::Error, Debug, Clone)]
10pub enum Error {
11    #[error("Value must begin with leader: {0}")]
12    LeaderMissing(String),
13    #[error("Invalid prefix value: {0}")]
14    InvalidPrefix(String),
15    #[error("Invalid suffix value: {0}")]
16    InvalidSuffix(String),
17    #[error("Invalid section: {0}")]
18    InvalidSection(String),
19    #[error("Full six-letter words required")]
20    ZeroPadRequired,
21    #[error("Invalid hex string: {0}")]
22    InvalidHex(String),
23    #[error("Invalid decimal string: {0}")]
24    InvalidDecimal(String),
25    #[error("Invalid separator")]
26    InvalidSeparator,
27    #[error("Integer value out of bounds")]
28    OutOfBounds,
29}
30
31/// Ordered list of all prefixes.  Use to lookup a prefix by numerical value.
32pub const PREFIXES: [&'static str; 256] = [
33    "doz", "mar", "bin", "wan", "sam", "lit", "sig", "hid", "fid", "lis", "sog", "dir", "wac",
34    "sab", "wis", "sib", "rig", "sol", "dop", "mod", "fog", "lid", "hop", "dar", "dor", "lor",
35    "hod", "fol", "rin", "tog", "sil", "mir", "hol", "pas", "lac", "rov", "liv", "dal", "sat",
36    "lib", "tab", "han", "tic", "pid", "tor", "bol", "fos", "dot", "los", "dil", "for", "pil",
37    "ram", "tir", "win", "tad", "bic", "dif", "roc", "wid", "bis", "das", "mid", "lop", "ril",
38    "nar", "dap", "mol", "san", "loc", "nov", "sit", "nid", "tip", "sic", "rop", "wit", "nat",
39    "pan", "min", "rit", "pod", "mot", "tam", "tol", "sav", "pos", "nap", "nop", "som", "fin",
40    "fon", "ban", "mor", "wor", "sip", "ron", "nor", "bot", "wic", "soc", "wat", "dol", "mag",
41    "pic", "dav", "bid", "bal", "tim", "tas", "mal", "lig", "siv", "tag", "pad", "sal", "div",
42    "dac", "tan", "sid", "fab", "tar", "mon", "ran", "nis", "wol", "mis", "pal", "las", "dis",
43    "map", "rab", "tob", "rol", "lat", "lon", "nod", "nav", "fig", "nom", "nib", "pag", "sop",
44    "ral", "bil", "had", "doc", "rid", "moc", "pac", "rav", "rip", "fal", "tod", "til", "tin",
45    "hap", "mic", "fan", "pat", "tac", "lab", "mog", "sim", "son", "pin", "lom", "ric", "tap",
46    "fir", "has", "bos", "bat", "poc", "hac", "tid", "hav", "sap", "lin", "dib", "hos", "dab",
47    "bit", "bar", "rac", "par", "lod", "dos", "bor", "toc", "hil", "mac", "tom", "dig", "fil",
48    "fas", "mit", "hob", "har", "mig", "hin", "rad", "mas", "hal", "rag", "lag", "fad", "top",
49    "mop", "hab", "nil", "nos", "mil", "fop", "fam", "dat", "nol", "din", "hat", "nac", "ris",
50    "fot", "rib", "hoc", "nim", "lar", "fit", "wal", "rap", "sar", "nal", "mos", "lan", "don",
51    "dan", "lad", "dov", "riv", "bac", "pol", "lap", "tal", "pit", "nam", "bon", "ros", "ton",
52    "fod", "pon", "sov", "noc", "sor", "lav", "mat", "mip", "fip",
53];
54
55/// Ordered list of all suffixes.  Use to lookup a suffix by numerical value.
56pub const SUFFIXES: [&'static str; 256] = [
57    "zod", "nec", "bud", "wes", "sev", "per", "sut", "let", "ful", "pen", "syt", "dur", "wep",
58    "ser", "wyl", "sun", "ryp", "syx", "dyr", "nup", "heb", "peg", "lup", "dep", "dys", "put",
59    "lug", "hec", "ryt", "tyv", "syd", "nex", "lun", "mep", "lut", "sep", "pes", "del", "sul",
60    "ped", "tem", "led", "tul", "met", "wen", "byn", "hex", "feb", "pyl", "dul", "het", "mev",
61    "rut", "tyl", "wyd", "tep", "bes", "dex", "sef", "wyc", "bur", "der", "nep", "pur", "rys",
62    "reb", "den", "nut", "sub", "pet", "rul", "syn", "reg", "tyd", "sup", "sem", "wyn", "rec",
63    "meg", "net", "sec", "mul", "nym", "tev", "web", "sum", "mut", "nyx", "rex", "teb", "fus",
64    "hep", "ben", "mus", "wyx", "sym", "sel", "ruc", "dec", "wex", "syr", "wet", "dyl", "myn",
65    "mes", "det", "bet", "bel", "tux", "tug", "myr", "pel", "syp", "ter", "meb", "set", "dut",
66    "deg", "tex", "sur", "fel", "tud", "nux", "rux", "ren", "wyt", "nub", "med", "lyt", "dus",
67    "neb", "rum", "tyn", "seg", "lyx", "pun", "res", "red", "fun", "rev", "ref", "mec", "ted",
68    "rus", "bex", "leb", "dux", "ryn", "num", "pyx", "ryg", "ryx", "fep", "tyr", "tus", "tyc",
69    "leg", "nem", "fer", "mer", "ten", "lus", "nus", "syl", "tec", "mex", "pub", "rym", "tuc",
70    "fyl", "lep", "deb", "ber", "mug", "hut", "tun", "byl", "sud", "pem", "dev", "lur", "def",
71    "bus", "bep", "run", "mel", "pex", "dyt", "byt", "typ", "lev", "myl", "wed", "duc", "fur",
72    "fex", "nul", "luc", "len", "ner", "lex", "rup", "ned", "lec", "ryd", "lyd", "fen", "wel",
73    "nyd", "hus", "rel", "rud", "nes", "hes", "fet", "des", "ret", "dun", "ler", "nyr", "seb",
74    "hul", "ryl", "lud", "rem", "lys", "fyn", "wer", "ryc", "sug", "nys", "nyl", "lyn", "dyn",
75    "dem", "lux", "fed", "sed", "bec", "mun", "lyr", "tes", "mud", "nyt", "byr", "sen", "weg",
76    "fyr", "mur", "tel", "rep", "teg", "pec", "nel", "nev", "fes",
77];
78
79/// Map from prefix name to integer value.  Use to validate a prefix or lookup the numeric value.
80pub static PREFIX_VALUES: Lazy<HashMap<&'static str, u8>> = Lazy::new(|| {
81    let mut h = HashMap::with_capacity(256);
82    for (index, value) in PREFIXES.iter().enumerate() {
83        h.insert(*value, index as u8);
84    }
85    h
86});
87
88/// Map from suffix name to integer value.  Use to validate a suffix or lookup the numeric value.
89pub static SUFFIX_VALUES: Lazy<HashMap<&'static str, u8>> = Lazy::new(|| {
90    let mut h = HashMap::with_capacity(256);
91    for (index, value) in SUFFIXES.iter().enumerate() {
92        h.insert(*value, index as u8);
93    }
94    h
95});
96
97/// Validate the input as a prefix, and return a static copy
98pub fn static_pre(pre: &str) -> Result<&'static str, Error> {
99    PREFIX_VALUES
100        .get_key_value(pre)
101        .ok_or(Error::InvalidPrefix(pre.to_string()))
102        .map(|(k, _)| *k)
103}
104
105/// Validate the input as a suffix, and return a static copy
106pub fn static_suf(suf: &str) -> Result<&'static str, Error> {
107    SUFFIX_VALUES
108        .get_key_value(suf)
109        .ok_or(Error::InvalidSuffix(suf.to_string()))
110        .map(|(k, _)| *k)
111}
112
113/// Get a prefix value with validation
114pub fn prefix_value(pre: &str) -> Result<u8, Error> {
115    PREFIX_VALUES
116        .get(pre)
117        .ok_or(Error::InvalidPrefix(pre.to_string()))
118        .map(|v| *v)
119}
120
121/// Get a suffix value with validation
122pub fn suffix_value(suf: &str) -> Result<u8, Error> {
123    SUFFIX_VALUES
124        .get(suf)
125        .ok_or(Error::InvalidSuffix(suf.to_string()))
126        .map(|v| *v)
127}
128
129fn met(a: usize, b: &UBig) -> usize {
130    let zero = UBig::zero();
131    let mut b = b.clone();
132    let mut c = 0;
133    loop {
134        if b.eq(&zero) {
135            return c;
136        }
137        b = b >> 2.pow(a) as usize;
138        c = c + 1;
139    }
140}
141
142fn end(a: usize, c: &UBig) -> UBig {
143    c.rem(UBig::from(2u64.pow(2u32.pow(a as u32))))
144}
145
146/// Convert an unsigned integer into a @q-encoded string.
147pub fn patq<T>(n: T) -> String
148where
149    UBig: From<T>,
150{
151    big2patq(&UBig::from(n))
152}
153
154/// Convert a bignum (UBig) into a @q-encoded string.
155pub fn big2patq(n: &UBig) -> String {
156    let buf = if n.is_zero() {
157        vec![0]
158    } else {
159        n.to_be_bytes()
160    };
161    buf2patq(&buf)
162}
163
164/// Convert an unsigned integer into a @p-encoded string.
165pub fn patp<T>(n: T) -> String
166where
167    UBig: From<T>,
168{
169    big2patp(&UBig::from(n))
170}
171
172/// Convert a bignum (UBig) to a @p-encoded string.
173pub fn big2patp(n: &UBig) -> String {
174    let n = fein(n);
175    let buf = if n.is_zero() {
176        vec![0]
177    } else {
178        n.to_be_bytes()
179    };
180    buf2patp(&buf)
181}
182
183/// Convert a Buffer into a @q-encoded string.
184pub fn buf2patq(buf: &[u8]) -> String {
185    buf2pat(buf, ".~", false, false)
186}
187
188/// Convert a Buffer into a @p-encoded string.
189pub fn buf2patp(buf: &[u8]) -> String {
190    buf2pat(buf, "~", true, true)
191}
192
193/// General formatter for @p and @q encoded strings.
194pub fn buf2pat(buf: &[u8], leader: &str, zero_pad: bool, quad_sep: bool) -> String {
195    // Galaxies are never zero-padded
196    let zero_pad = buf.len() != 1 && zero_pad;
197
198    // Zero pad the buffer so it is always even length
199    let mut v: Vec<u8>;
200    let bytes: &[u8] = if buf.len() % 2 != 0 {
201        v = Vec::with_capacity(buf.len() + 1);
202        v.push(0);
203        v.extend_from_slice(buf);
204        &v
205    } else {
206        buf
207    };
208
209    let mut timp: usize = bytes.len() / 2;
210    let mut name = String::new();
211    for chunk in bytes.chunks(2) {
212        match chunk {
213            &[pre, suf] => {
214                if name.is_empty() {
215                    if zero_pad || pre != 0 {
216                        name.push_str(PREFIXES[pre as usize]);
217                    }
218                } else {
219                    if quad_sep && timp % 4 == 0 {
220                        name.push_str("--");
221                    } else {
222                        name.push_str("-");
223                    }
224                    name.push_str(PREFIXES[pre as usize]);
225                }
226                name.push_str(SUFFIXES[suf as usize]);
227            }
228            _ => panic!("buf2pat bug!"),
229        }
230        timp = timp - 1;
231    }
232    format!("{}{}", leader, name)
233}
234
235/// Convert a hex-encoded string to a @q-encoded string.
236pub fn hex2patq(hex: &str) -> Result<String, Error> {
237    UBig::from_str_radix(hex, 16)
238        .map(|n| big2patq(&n))
239        .map_err(|_| Error::InvalidHex(hex.to_string()))
240}
241
242/// Convert a hex-encoded string to a @p-encoded string.
243pub fn hex2patp(hex: &str) -> Result<String, Error> {
244    UBig::from_str_radix(hex, 16)
245        .map(|n| big2patp(&n))
246        .map_err(|_| Error::InvalidHex(hex.to_string()))
247}
248
249/// Convert a decimal encoded string to a @q-encoded string.
250pub fn dec2patq(dec: &str) -> Result<String, Error> {
251    UBig::from_str_radix(dec, 10)
252        .map(|n| big2patq(&n))
253        .map_err(|_| Error::InvalidDecimal(dec.to_string()))
254}
255
256/// Convert a decimal encoded string to a @q-encoded string.
257pub fn dec2patp(dec: &str) -> Result<String, Error> {
258    UBig::from_str_radix(dec, 10)
259        .map(|n| big2patp(&n))
260        .map_err(|_| Error::InvalidDecimal(dec.to_string()))
261}
262
263/// Convert a @q-encoded string to a primitive integer
264pub fn patq2int<T>(name: &str) -> Result<T, Error>
265where
266    T: TryFrom<UBig>,
267{
268    T::try_from(patq2big(name)?).map_err(|_| Error::OutOfBounds)
269}
270
271/// Convert a @p-encoded string to a primitive integer
272pub fn patp2int<T>(name: &str) -> Result<T, Error>
273where
274    T: TryFrom<UBig>,
275{
276    T::try_from(patp2big(name)?).map_err(|_| Error::OutOfBounds)
277}
278
279/// Convert a @q-encoded string to a UBig
280pub fn patq2big(name: &str) -> Result<UBig, Error> {
281    let syls = patq2syls(name)?;
282    let buf = syls2buffer(&syls)?;
283
284    Ok(UBig::from_be_bytes(&buf))
285}
286
287/// Convert a @p-encoded string to a UBig
288pub fn patp2big(name: &str) -> Result<UBig, Error> {
289    let syls = patp2syls(name)?;
290    let buf = syls2buffer(&syls)?;
291
292    Ok(fynd(&UBig::from_be_bytes(&buf)))
293}
294
295/// Parse a @p-encoded value into a vector of individual syllables
296pub fn patp2syls(pat: &str) -> Result<Vec<&str>, Error> {
297    pat2syls(pat, "~", true, true)
298}
299
300/// Parse a @q-encoded value into a vector of individual syllables
301pub fn patq2syls(pat: &str) -> Result<Vec<&str>, Error> {
302    pat2syls(pat, ".~", false, false)
303}
304
305/// General parser for @p and @q values.
306pub fn pat2syls(
307    pat: &str,
308    leader: &str,
309    zero_pad: bool,
310    quad_sep: bool,
311) -> Result<Vec<&'static str>, Error> {
312    if let Some(rest) = pat.strip_prefix(leader) {
313        let words: Vec<&str> = rest.split('-').collect();
314        if words.len() == 1 && words[0].len() == 3 {
315            // Galaxy special case
316            static_suf(words[0]).map(|s| vec![s])
317        } else {
318            // An empty word indicates double hyphen
319            let mut empties = 0;
320            for (i, word) in words.iter().rev().enumerate() {
321                if quad_sep && i % 5 == 4 {
322                    if !word.is_empty() {
323                        return Err(Error::InvalidSeparator);
324                    } else {
325                        empties += 1;
326                    }
327                } else if word.is_empty() {
328                    return Err(Error::InvalidSeparator);
329                }
330            }
331
332            // Iterate non-empty words and split into syls
333            let mut syls = Vec::with_capacity((words.len() - empties) * 2);
334            let mut first_word = true;
335            for word in words.iter().filter(|s| !s.is_empty()) {
336                if word.len() == 6 {
337                    let (pre, suf) = word.split_at(3);
338                    syls.push(static_pre(pre)?);
339                    syls.push(static_suf(suf)?);
340                } else if word.len() == 3 && first_word {
341                    if !zero_pad {
342                        syls.push(static_suf(word)?);
343                    } else {
344                        return Err(Error::ZeroPadRequired);
345                    }
346                } else {
347                    return Err(Error::InvalidSection(word.to_string()));
348                }
349                first_word = false;
350            }
351            Ok(syls)
352        }
353    } else {
354        Err(Error::LeaderMissing(leader.to_string()))
355    }
356}
357
358/// Convert syllables into a buffer of their integer values.
359pub fn syls2buffer(syls: &[&str]) -> Result<Vec<u8>, Error> {
360    // Start with suffix when odd number of syllables
361    let mut suffix: bool = syls.len() % 2 == 1;
362    let mut buf = Vec::with_capacity(syls.len());
363    for syl in syls.iter() {
364        if suffix {
365            buf.push(suffix_value(syl)?);
366        } else {
367            buf.push(prefix_value(syl)?);
368        }
369        suffix = !suffix;
370    }
371    Ok(buf)
372}
373
374/// Convert a @q-encoded string to a hex-encoded string.
375pub fn patq2hex(name: &str) -> Result<String, Error> {
376    patq2big(name).map(big2hex)
377}
378
379/// Convert a @p-encoded string to a hex-encoded string.
380pub fn patp2hex(name: &str) -> Result<String, Error> {
381    patp2big(name).map(big2hex)
382}
383
384// Ensures zero-padded even length hex strings
385fn big2hex(n: UBig) -> String {
386    let hex = n.in_radix(16).to_string();
387    if hex.len() % 2 != 0 {
388        format!("0{hex}")
389    } else {
390        hex
391    }
392}
393
394/// Convert a @q-encoded string to a decimal-encoded string.
395pub fn patq2dec(name: &str) -> Result<String, Error> {
396    patq2big(name).map(|n| n.in_radix(10).to_string())
397}
398
399/// Convert a @p-encoded string to a decimal-encoded string.
400pub fn patp2dec(name: &str) -> Result<String, Error> {
401    patp2big(name).map(|n| n.in_radix(10).to_string())
402}
403
404/// Determine the ship class of a @p value.
405pub fn clan(who: &str) -> Result<&'static str, Error> {
406    let n = patp2big(who)?;
407    let wid = met(3, &n);
408
409    Ok(if wid <= 1 {
410        "galaxy"
411    } else if wid == 2 {
412        "star"
413    } else if wid <= 4 {
414        "planet"
415    } else if wid <= 8 {
416        "moon"
417    } else {
418        "comet"
419    })
420}
421
422/// Determine the parent of a @p value.
423pub fn sein(name: &str) -> Result<String, Error> {
424    let who = patp2big(name)?;
425    let mir = clan(name)?;
426
427    let res = match mir {
428        "galaxy" => who,
429        "star" => end(3, &who),
430        "planet" => end(4, &who),
431        "moon" => end(5, &who),
432        _ => UBig::zero(),
433    };
434
435    Ok(big2patp(&res))
436}
437
438/// General validation for pat type values.
439pub fn is_valid_pat(name: &str, leader: &str, force_zero_pad: bool, quad_sep: bool) -> bool {
440    pat2syls(name, leader, force_zero_pad, quad_sep).is_ok()
441}
442
443/// Validate a @p-encoded string.
444pub fn is_valid_patp(name: &str) -> bool {
445    patp2syls(name).is_ok()
446}
447
448/// Validate a @q-encoded string.
449pub fn is_valid_patq(name: &str) -> bool {
450    patq2syls(name).is_ok()
451}
452
453/// Equality test for a @p and @q.
454pub fn eq_patq(p: &str, q: &str) -> Result<bool, Error> {
455    let p_val = patp2big(p)?;
456    let q_val = patq2big(q)?;
457    Ok(p_val == q_val)
458}
459
460#[cfg(test)]
461mod tests {
462    #[allow(unused_imports)]
463    use super::*;
464
465    #[test]
466    fn test_patq() {
467        assert_eq!(patq(0_u32), ".~zod");
468        assert_eq!(patq(0xac_u32), ".~ber");
469        assert_eq!(patq(0x23ec_u32), ".~rovfed");
470        assert_eq!(patq(0x94cf670c_u32), ".~mocwel-magwep");
471        assert_eq!(patq(0xe57f1c2d_u32), ".~sarmed-rinbyn");
472        assert_eq!(
473            patq(0xed816cd86003df88_u64),
474            ".~rivdus-timret-ronwes-hocres"
475        );
476        assert_eq!(patq(0xd86003df88_u64), ".~ret-ronwes-hocres");
477        assert_eq!(
478            patq(0x85613e0edad57f5fddc48d3f5808a628_u128),
479            ".~rolruc-midwyl-hathes-palsym-fotnul-pagpur-nopful-lomtem"
480        );
481    }
482
483    #[test]
484    fn test_patp() {
485        assert_eq!(patp(0_u32), "~zod");
486        assert_eq!(patp(0xac_u32), "~ber");
487        assert_eq!(patp(0x23ec_u32), "~rovfed");
488        assert_eq!(patp(0x94cf670c_u32), "~lapsen-hapmeb");
489        assert_eq!(patp(0xe57f1c2d_u32), "~hobpur-habnev");
490        assert_eq!(patp(0xed816cd86003df88_u64), "~rivdus-timret-tardet-paslux");
491        assert_eq!(patp(0xd86003df88_u64), "~dozret-tardet-paslux");
492        assert_eq!(
493            patp(0x85613e0edad57f5fddc48d3f5808a628_u128),
494            "~rolruc-midwyl-hathes-palsym--fotnul-pagpur-nopful-lomtem"
495        );
496    }
497
498    #[test]
499    fn test_hex2patq() {
500        assert_eq!(hex2patq("0").unwrap(), ".~zod");
501        assert_eq!(hex2patq("ac").unwrap(), ".~ber");
502        assert_eq!(hex2patq("23ec").unwrap(), ".~rovfed");
503        assert_eq!(hex2patq("94cf670c").unwrap(), ".~mocwel-magwep");
504        assert_eq!(hex2patq("e57f1c2d").unwrap(), ".~sarmed-rinbyn");
505        assert_eq!(
506            hex2patq("ed816cd86003df88").unwrap(),
507            ".~rivdus-timret-ronwes-hocres"
508        );
509        assert_eq!(hex2patq("d86003df88").unwrap(), ".~ret-ronwes-hocres");
510        assert_eq!(
511            hex2patq("85613e0edad57f5fddc48d3f5808a628").unwrap(),
512            ".~rolruc-midwyl-hathes-palsym-fotnul-pagpur-nopful-lomtem"
513        );
514    }
515
516    #[test]
517    fn test_hex2patp() {
518        assert_eq!(hex2patp("0").unwrap(), "~zod");
519        assert_eq!(hex2patp("ac").unwrap(), "~ber");
520        assert_eq!(hex2patp("23ec").unwrap(), "~rovfed");
521        assert_eq!(hex2patp("94cf670c").unwrap(), "~lapsen-hapmeb");
522        assert_eq!(hex2patp("e57f1c2d").unwrap(), "~hobpur-habnev");
523        assert_eq!(
524            hex2patp("ed816cd86003df88").unwrap(),
525            "~rivdus-timret-tardet-paslux"
526        );
527        assert_eq!(hex2patp("d86003df88").unwrap(), "~dozret-tardet-paslux");
528        assert_eq!(
529            hex2patp("85613e0edad57f5fddc48d3f5808a628").unwrap(),
530            "~rolruc-midwyl-hathes-palsym--fotnul-pagpur-nopful-lomtem"
531        );
532    }
533
534    #[test]
535    fn test_dec2patq() {
536        assert_eq!(dec2patq("0").unwrap(), ".~zod");
537        assert_eq!(dec2patq("213").unwrap(), ".~hes");
538        assert_eq!(dec2patq("42345").unwrap(), ".~pindet");
539        assert_eq!(dec2patq("1767345876").unwrap(), ".~davnyx-sopnes");
540        assert_eq!(dec2patq("3825024628").unwrap(), ".~walnel-middut");
541        assert_eq!(
542            dec2patq("946825410821165233").unwrap(),
543            ".~sabsep-ragsud-milnet-rigsud"
544        );
545        assert_eq!(
546            dec2patq("946825410821165").unwrap(),
547            ".~wes-mormep-ponrul-borbyn"
548        );
549        assert_eq!(
550            dec2patq("123944546871737295045108034582151365233").unwrap(),
551            ".~mornep-dinfyr-fognut-folheb-rivrem-wanteg-haprev-binter"
552        );
553    }
554
555    #[test]
556    fn test_dec2patp() {
557        assert_eq!(dec2patp("0").unwrap(), "~zod");
558        assert_eq!(dec2patp("213").unwrap(), "~hes");
559        assert_eq!(dec2patp("42345").unwrap(), "~pindet");
560        assert_eq!(dec2patp("1767345876").unwrap(), "~sabhut-mocsed");
561        assert_eq!(dec2patp("3825024628").unwrap(), "~napdut-matful");
562        assert_eq!(
563            dec2patp("946825410821165233").unwrap(),
564            "~sabsep-ragsud-sicsug-solmud"
565        );
566        assert_eq!(
567            dec2patp("946825410821165").unwrap(),
568            "~dozwes-mormep-simred-novtyd"
569        );
570        assert_eq!(
571            dec2patp("123944546871737295045108034582151365233").unwrap(),
572            "~mornep-dinfyr-fognut-folheb--rivrem-wanteg-haprev-binter"
573        );
574    }
575
576    #[test]
577    fn test_patp2syls() {
578        assert!(matches!(patp2syls("~xxx"), Err(Error::InvalidSuffix(_))));
579        assert!(matches!(
580            patp2syls("~sampel-palxxx"),
581            Err(Error::InvalidSuffix(_))
582        ));
583        assert!(matches!(
584            patp2syls("~sampel-xxxnet"),
585            Err(Error::InvalidPrefix(_))
586        ));
587        assert!(matches!(
588            patp2syls("~dev-sampel-palnet"),
589            Err(Error::ZeroPadRequired)
590        ));
591        assert!(matches!(patp2syls("~dozdev-sampel-palnet"), Ok(_)));
592        assert!(matches!(
593            patp2syls("sampel-palnet"),
594            Err(Error::LeaderMissing(_))
595        ));
596        assert!(matches!(
597            patp2syls(".~sampel-palnet"),
598            Err(Error::LeaderMissing(_))
599        ));
600        assert!(matches!(
601            patp2syls("~sampel-word-palnet"),
602            Err(Error::InvalidSection(_))
603        ));
604        assert!(matches!(
605            patp2syls("~rolruc-midwyl-hathes-palsym--fotnul-pagpur-nopful-lomtem"),
606            Ok(_)
607        ));
608        assert!(matches!(
609            patp2syls("~palsym--fotnul-pagpur-nopful-lomtem"),
610            Ok(_)
611        ));
612        assert!(matches!(
613            patp2syls("~rolruc-midwyl-hathes-palsym-fotnul-pagpur-nopful-lomtem"),
614            Err(Error::InvalidSeparator)
615        ));
616        assert!(matches!(
617            patp2syls("~rolruc-midwyl-hathes-palsym-fotnul-pagpur-nopful--lomtem"),
618            Err(Error::InvalidSeparator)
619        ));
620    }
621
622    #[test]
623    fn test_patq2syls() {
624        assert!(matches!(patq2syls(".~xxx"), Err(Error::InvalidSuffix(_))));
625        assert!(matches!(
626            patq2syls(".~sampel-palxxx"),
627            Err(Error::InvalidSuffix(_))
628        ));
629        assert!(matches!(
630            patq2syls(".~sampel-xxxnet"),
631            Err(Error::InvalidPrefix(_))
632        ));
633        assert!(matches!(patq2syls(".~dev-sampel-palnet"), Ok(_)));
634        assert!(matches!(patq2syls(".~dozdev-sampel-palnet"), Ok(_)));
635        assert!(matches!(
636            patq2syls("sampel-palnet"),
637            Err(Error::LeaderMissing(_))
638        ));
639        assert!(matches!(
640            patq2syls("~sampel-palnet"),
641            Err(Error::LeaderMissing(_))
642        ));
643        assert!(matches!(
644            patq2syls(".~sampel-word-palnet"),
645            Err(Error::InvalidSection(_))
646        ));
647        assert!(matches!(
648            patq2syls(".~rolruc-midwyl-hathes-palsym--fotnul-pagpur-nopful-lomtem"),
649            Err(Error::InvalidSeparator)
650        ));
651        assert!(matches!(
652            patq2syls(".~rolruc--midwyl-hathes--palsym-fotnul--pagpur-nopful--lomtem"),
653            Err(Error::InvalidSeparator)
654        ));
655    }
656
657    #[test]
658    fn test_patp2int() {
659        assert_eq!(patp2int::<u16>("~binzod").unwrap(), 512);
660        assert_eq!(patp2int::<u32>("~sampel-palnet").unwrap(), 1624961343);
661        assert_eq!(
662            patp2int::<u64>("~mastyr-midwyt-sampel-palnet").unwrap(),
663            14598768375314968895
664        );
665        assert!(matches!(
666            patp2int::<u32>("~mastyr-midwyt-sampel-palnet"),
667            Err(Error::OutOfBounds)
668        ));
669        assert_eq!(
670            patp2int::<u128>("~rolruc-midwyl-hathes-palsym--fotnul-pagpur-nopful-lomtem").unwrap(),
671            177292234920987225829036013324996224552
672        );
673        assert!(matches!(
674            patp2int::<u128>("~mastyr--rolruc-midwyl-hathes-palsym--fotnul-pagpur-nopful-lomtem"),
675            Err(Error::OutOfBounds)
676        ));
677    }
678
679    #[test]
680    fn test_patq2int() {
681        assert_eq!(patq2int::<u16>(".~binzod").unwrap(), 512);
682        assert_eq!(patq2int::<u32>(".~sampel-palnet").unwrap(), 74415951);
683        assert_eq!(
684            patq2int::<u64>(".~mastyr-midwyt-sampel-palnet").unwrap(),
685            14598768373764423503
686        );
687        assert!(matches!(
688            patq2int::<u32>(".~mastyr-midwyt-sampel-palnet"),
689            Err(Error::OutOfBounds)
690        ));
691        assert_eq!(
692            patq2int::<u128>(".~rolruc-midwyl-hathes-palsym-fotnul-pagpur-nopful-lomtem").unwrap(),
693            177292234920987225829036013324996224552
694        );
695        assert!(matches!(
696            patq2int::<u128>(".~mastyr-rolruc-midwyl-hathes-palsym-fotnul-pagpur-nopful-lomtem"),
697            Err(Error::OutOfBounds)
698        ));
699    }
700
701    #[test]
702    fn test_patp2dec() {
703        assert_eq!(patp2dec("~binzod").unwrap(), "512");
704        assert_eq!(patp2dec("~sampel-palnet").unwrap(), "1624961343");
705        assert_eq!(
706            patp2dec("~mastyr-midwyt-sampel-palnet").unwrap(),
707            "14598768375314968895"
708        );
709        assert_eq!(
710            patp2dec("~rolruc-midwyl-hathes-palsym--fotnul-pagpur-nopful-lomtem").unwrap(),
711            "177292234920987225829036013324996224552"
712        );
713    }
714
715    #[test]
716    fn test_patq2dec() {
717        assert_eq!(patq2dec(".~binzod").unwrap(), "512");
718        assert_eq!(patq2dec(".~sampel-palnet").unwrap(), "74415951");
719        assert_eq!(
720            patq2dec(".~mastyr-midwyt-sampel-palnet").unwrap(),
721            "14598768373764423503"
722        );
723        assert_eq!(
724            patq2dec(".~rolruc-midwyl-hathes-palsym-fotnul-pagpur-nopful-lomtem").unwrap(),
725            "177292234920987225829036013324996224552"
726        );
727    }
728
729    #[test]
730    fn test_patp2hex() {
731        assert_eq!(patp2hex("~binzod").unwrap(), "0200");
732        assert_eq!(patp2hex("~sampel-palnet").unwrap(), "60daf13f");
733        assert_eq!(
734            patp2hex("~rolruc-midwyl-hathes-palsym--fotnul-pagpur-nopful-lomtem").unwrap(),
735            "85613e0edad57f5fddc48d3f5808a628"
736        );
737    }
738
739    #[test]
740    fn test_patq2hex() {
741        assert_eq!(patq2hex(".~binzod").unwrap(), "0200");
742        assert_eq!(patq2hex(".~sampel-palnet").unwrap(), "046f7f4f");
743        assert_eq!(
744            patq2hex(".~rolruc-midwyl-hathes-palsym-fotnul-pagpur-nopful-lomtem").unwrap(),
745            "85613e0edad57f5fddc48d3f5808a628"
746        );
747    }
748
749    #[test]
750    fn test_clan() {
751        assert_eq!(clan("~zod").unwrap(), "galaxy");
752        assert_eq!(clan("~binzod").unwrap(), "star");
753        assert_eq!(clan("~sampel-palnet").unwrap(), "planet");
754        assert_eq!(clan("~rolruc-midwyl-hathes-palsym").unwrap(), "moon");
755        assert_eq!(
756            clan("~rolruc-midwyl-hathes-palsym--fotnul-pagpur-nopful-lomtem").unwrap(),
757            "comet"
758        );
759    }
760
761    #[test]
762    fn test_sein() {
763        assert_eq!(sein("~dev").unwrap(), "~dev");
764        assert_eq!(sein("~binhut").unwrap(), "~hut");
765        assert_eq!(sein("~sampel-palnet").unwrap(), "~talpur");
766        assert_eq!(
767            sein("~rolruc-midwyl-hathes-palsym").unwrap(),
768            "~hathes-palsym"
769        );
770        assert_eq!(
771            sein("~rolruc-midwyl-hathes-palsym--fotnul-pagpur-nopful-lomtem").unwrap(),
772            "~zod"
773        );
774    }
775}