nbt_parser/
lib.rs

1#[macro_use]
2extern crate combine;
3
4extern crate libflate;
5
6extern crate failure;
7
8use combine::parser::byte::{
9    byte,
10    num::{be_f32, be_f64, be_i16, be_i32, be_i64, be_u16},
11};
12use combine::stream::{buffered::BufferedStream, state::State, ReadStream};
13use combine::{any, count, many, unexpected};
14use combine::{ParseError, Parser, Stream};
15
16use std::{io::Read, mem};
17
18/// An unnamed tag.
19#[derive(Clone, Debug, PartialEq)]
20pub enum UnnamedTag {
21    /// The `TAG_End` tag. Normally not found anywhere but inside `TAG_Compound`s.
22    End,
23
24    /// The `TAG_Byte` tag.
25    Byte(i8),
26
27    /// The `TAG_Short` tag.
28    Short(i16),
29
30    /// The `TAG_Int` tag.
31    Int(i32),
32
33    /// The `TAG_Long` tag.
34    Long(i64),
35
36    /// The `TAG_Float` tag.
37    Float(f32),
38
39    /// The `TAG_Double` tag.
40    Double(f64),
41
42    /// The `TAG_ByteArray` tag.
43    ByteArray(Vec<i8>),
44
45    /// The `TAG_String` tag.
46    String(String),
47
48    /// The `TAG_List` tag. This contains unnamed tags, which are guaranteed to be all of the same
49    /// type.
50    List(Vec<UnnamedTag>),
51
52    /// The `TAG_Compound` tag. This contains named tags, but the `TAG_End` tag which is always
53    /// present at the end is removed for ease of use.
54    Compound(Vec<NamedTag>),
55}
56
57/// A named tag. Contains only the name on its own, and the actual tag's contents are accessible
58/// via the `content` field.
59#[derive(Clone, Debug, PartialEq)]
60pub struct NamedTag {
61    /// The name of the tag. This is the empty string if the inner tag is a `TAG_End`.
62    pub name: String,
63    /// The actual content of the tag.
64    pub content: UnnamedTag,
65}
66
67fn name<I>() -> impl Parser<Input = I, Output = String>
68where
69    I: Stream<Item = u8>,
70    // Necessary due to rust-lang/rust#24159
71    I::Error: ParseError<I::Item, I::Range, I::Position>,
72{
73    be_u16()
74        .then(|length| count(length as usize, any()))
75        .map(|contents: Vec<u8>| String::from_utf8(contents).unwrap())
76}
77
78fn end_tag<I>() -> impl Parser<Input = I, Output = NamedTag>
79where
80    I: Stream<Item = u8>,
81    // Necessary due to rust-lang/rust#24159
82    I::Error: ParseError<I::Item, I::Range, I::Position>,
83{
84    byte(0).map(|_| NamedTag {
85        name: String::new(),
86        content: UnnamedTag::End,
87    })
88}
89
90fn i8<I>() -> impl Parser<Input = I, Output = i8>
91where
92    I: Stream<Item = u8>,
93    // Necessary due to rust-lang/rust#24159
94    I::Error: ParseError<I::Item, I::Range, I::Position>,
95{
96    any().map(|n| unsafe { mem::transmute::<u8, i8>(n) })
97}
98
99macro_rules! simple_number_tag {
100    ($func_name:ident, $parser_name:ident, $tag_variant:path) => {
101        fn $func_name<I>() -> impl Parser<Input = I, Output = UnnamedTag>
102        where
103            I: Stream<Item = u8>,
104            // Necessary due to rust-lang/rust#24159
105            I::Error: ParseError<I::Item, I::Range, I::Position>,
106        {
107            $parser_name().map($tag_variant)
108        }
109    };
110}
111
112simple_number_tag!(byte_tag, i8, UnnamedTag::Byte);
113simple_number_tag!(short_tag, be_i16, UnnamedTag::Short);
114simple_number_tag!(int_tag, be_i32, UnnamedTag::Int);
115simple_number_tag!(long_tag, be_i64, UnnamedTag::Long);
116simple_number_tag!(float_tag, be_f32, UnnamedTag::Float);
117simple_number_tag!(double_tag, be_f64, UnnamedTag::Double);
118
119fn bytearray_tag<I>() -> impl Parser<Input = I, Output = UnnamedTag>
120where
121    I: Stream<Item = u8>,
122    // Necessary due to rust-lang/rust#24159
123    I::Error: ParseError<I::Item, I::Range, I::Position>,
124{
125    be_i32()
126        .then(|length| count(length as usize, i8()))
127        .map(UnnamedTag::ByteArray)
128}
129
130fn string_tag<I>() -> impl Parser<Input = I, Output = UnnamedTag>
131where
132    I: Stream<Item = u8>,
133    // Necessary due to rust-lang/rust#24159
134    I::Error: ParseError<I::Item, I::Range, I::Position>,
135{
136    name().map(UnnamedTag::String)
137}
138
139fn list_tag<I>() -> impl Parser<Input = I, Output = UnnamedTag>
140where
141    I: Stream<Item = u8>,
142    // Necessary due to rust-lang/rust#24159
143    I::Error: ParseError<I::Item, I::Range, I::Position>,
144{
145    i8().and(be_i32())
146        .then(|(tag_id, length)| {
147            count(
148                length as usize,
149                combine::parser(move |input| match tag_id {
150                    0 => end_tag()
151                        .map(|NamedTag { content, .. }| content)
152                        .parse_stream(input),
153                    1 => byte_tag().parse_stream(input),
154                    2 => short_tag().parse_stream(input),
155                    3 => int_tag().parse_stream(input),
156                    4 => long_tag().parse_stream(input),
157                    5 => float_tag().parse_stream(input),
158                    6 => double_tag().parse_stream(input),
159                    7 => bytearray_tag().parse_stream(input),
160                    8 => string_tag().parse_stream(input),
161                    9 => list_tag().parse_stream(input),
162                    10 => compound_tag().parse_stream(input),
163                    _ => unexpected("Invalid tagId on TAG_List")
164                        .map(|()| UnnamedTag::End)
165                        .parse_stream(input),
166                }),
167            )
168        })
169        .map(UnnamedTag::List)
170}
171
172fn compound_tag<I>() -> impl Parser<Input = I, Output = UnnamedTag>
173where
174    I: Stream<Item = u8>,
175    // Necessary due to rust-lang/rust#24159
176    I::Error: ParseError<I::Item, I::Range, I::Position>,
177{
178    (many(combine::parser(|input| {
179        let (tag, rest) = named_tag().parse_stream(input)?;
180        if tag.content == UnnamedTag::End {
181            combine::unexpected("If you see this, contact github.com/PurpleMyst")
182                .map(|_| tag.clone())
183                .parse_stream(input)
184        } else {
185            Ok((tag, rest))
186        }
187    }))).skip(end_tag())
188        .map(UnnamedTag::Compound)
189}
190
191fn named_tag<I>() -> impl Parser<Input = I, Output = NamedTag>
192where
193    I: Stream<Item = u8>,
194    // Necessary due to rust-lang/rust#24159
195    I::Error: ParseError<I::Item, I::Range, I::Position>,
196{
197    macro_rules! do_it {
198        ($num:expr => $parser:expr) => {
199            byte($num)
200                .with(name())
201                .and($parser)
202                .map(|(name, content)| NamedTag { name, content })
203        };
204    }
205
206    choice!(
207        end_tag(),
208        do_it!(1 => byte_tag()),
209        do_it!(2 => short_tag()),
210        do_it!(3 => int_tag()),
211        do_it!(4 => long_tag()),
212        do_it!(5 => float_tag()),
213        do_it!(6 => double_tag()),
214        do_it!(7 => bytearray_tag()),
215        do_it!(8 => string_tag()),
216        do_it!(9 => list_tag()),
217        do_it!(10 => compound_tag())
218    )
219}
220
221/// Decode a [`Read`] instance. It is assumed that, as the spec defines, the contents of the
222/// instance are gzipped.
223pub fn decode<R: Read>(mut input: R) -> Result<NamedTag, failure::Error> {
224    let decoder = libflate::gzip::Decoder::new(&mut input)?;
225    let mut stream = BufferedStream::new(State::new(ReadStream::new(decoder)), 4096);
226    Ok(named_tag().parse_stream(&mut stream).map_err(|c| c.into_inner().error)?.0)
227}