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
use std::io::Write;

use crate::{
    consts::{
        ARR_HEADER, DICT_HEADER, FALSE_VALUE, NULL_VALUE, NUM_HEADER, STR_HEADER, TAG_HEADER,
        TRUE_VALUE,
    },
    util::{small_boxed_string::SmallBoxedStr, trunc_8_bytes::write_trunc_8_bytes},
    DictKey, Litl, TaggedData,
};

impl Litl {
    pub fn write<W: Write>(&self, out: &mut W) -> std::io::Result<()> {
        match self {
            Litl::String(str) => write_str(str, out),
            Litl::Number(num) => write_trunc_8_bytes(NUM_HEADER, &num.to_bits().to_be_bytes(), out),
            Litl::Array(items) => {
                write_trunc_8_bytes(ARR_HEADER, &(items.len() as u64).to_le_bytes(), out)?;
                for item in &**items {
                    item.write(out)?;
                }
                Ok(())
            }
            Litl::Dict(dict) => {
                let (keys, values) = dict.keys_and_values();
                write_trunc_8_bytes(DICT_HEADER, &(keys.len() as u64).to_le_bytes(), out)?;
                for (key, value) in keys.iter().zip(values) {
                    match key {
                        DictKey::String(str) => write_str(str, out),
                        DictKey::TaggedData(tagged_data) => tagged_data.write(out),
                    }?;
                    value.write(out)?;
                }
                Ok(())
            }
            Litl::Bool(false) => out.write_all(&[FALSE_VALUE]),
            Litl::Bool(true) => out.write_all(&[TRUE_VALUE]),
            Litl::Null => out.write_all(&[NULL_VALUE]),
            Litl::TaggedData(tagged_data) => tagged_data.write(out),
        }
    }

    pub fn to_bytes(&self) -> Vec<u8> {
        let mut bytes = Vec::new();
        self.write(&mut bytes).unwrap();
        bytes
    }

    pub fn write_all<'a, I: IntoIterator<Item = &'a Litl>>(values: I) -> Vec<u8> {
        let mut bytes = Vec::new();
        for value in values {
            value.write(&mut bytes).unwrap();
        }
        bytes
    }
}

impl TaggedData {
    fn write<W: Write>(&self, out: &mut W) -> std::io::Result<()> {
        match self {
            TaggedData::Leaf (data) => {
                write_trunc_8_bytes(TAG_HEADER, &("data".len() as u64).to_le_bytes(), out)?;
                out.write_all("data".as_bytes())?;

                write_trunc_8_bytes(0, &(data.len() as u64).to_le_bytes(), out)?;
                out.write_all(data)?;
                Ok(())
            }
            TaggedData::Nested { tag, inner } => {
                write_trunc_8_bytes(TAG_HEADER, &(tag.len() as u64).to_le_bytes(), out)?;
                out.write_all(tag.as_bytes())?;

                match &**inner {
                    Litl::TaggedData(TaggedData::Leaf (data)) => {
                        // skip "data" tag

                        write_trunc_8_bytes(0, &(data.len() as u64).to_le_bytes(), out)?;
                        out.write_all(data)?;
                        Ok(())
                    },
                    Litl::TaggedData(deeper) => deeper.write(out),
                    _ => unreachable!()
                }
            }
        }
    }
}

fn write_str<W: Write>(str: &SmallBoxedStr, out: &mut W) -> std::io::Result<()> {
    let as_bytes = str.as_bytes();

    if as_bytes.len() == 1
        && as_bytes[0] > NULL_VALUE
        && (as_bytes[0] & 0b1000_0000 == 0)
        && (as_bytes[0] & 0b0111_0000 != 0b0001_0000)
    {
        // single ASCII char, not in control range
        out.write_all(&as_bytes[0..1])
    } else if as_bytes.len() == 2 && as_bytes[0] & 0b1110_0000 == 0b_1100_0000 {
        // two-byte unicode point
        out.write_all(&as_bytes[0..2])
    } else if as_bytes.len() == 3 && as_bytes[0] & 0b1111_0000 == 0b_1110_0000 {
        // three-byte unicode point
        out.write_all(&as_bytes[0..3])
    } else if as_bytes.len() == 4 && as_bytes[0] & 0b1111_1000 == 0b_1111_0000 {
        // four-byte unicode point
        out.write_all(&as_bytes[0..4])
    } else {
        // string header + string length + string bytes
        write_trunc_8_bytes(STR_HEADER, &(as_bytes.len() as u64).to_le_bytes(), out)?;
        out.write_all(as_bytes)
    }
}