Skip to content

Commit 9a5df68

Browse files
committed
Unpack: moved to separated file
1 parent 12da4da commit 9a5df68

File tree

2 files changed

+279
-273
lines changed

2 files changed

+279
-273
lines changed

src/Unpack.zig

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
//! Unpacking state.
2+
//!
3+
//! This structure manages user-supplied data and converts messagepack
4+
//! representations to the host representations.
5+
//!
6+
//! To start unpacking, you supply the data with `Unpack.init`.
7+
//!
8+
//! - `Unpack.peek` begins the unpack process for a value.
9+
//! - The function returns a `HeaderType`.
10+
//! - Use `HeaderType.nextComponentSize` to get the required data size
11+
//! for the header.
12+
//! - Ensured the `Unpack.rest` have enough data, use `Unpack.next` move to
13+
//! the next value and extract the header.
14+
//! - The result is a `Header`, it can be inspected to decide what to do with
15+
//! the current value.
16+
//! - You can consume the value with any of `Unpack.nil`,
17+
//! `Unpack.@"bool"`, `Unpack.int`, `Unpack.float`, `Unpack.array`,
18+
//! `Unpack.map`, `Unpack.raw`.
19+
//!
20+
//! You must consume the current value for the next value. To skip the
21+
//! current value, use `Unpack.raw`. `Unpack.raw` can consume any value
22+
//! as a []const u8, excepts arrays and maps.
23+
//! Because they don't have determined byte size in header, see `Header.size`.
24+
//!
25+
//! If the buffer is appended with more data, use `Unpack.setAppend` to set the
26+
//! updated buffer.
27+
//!
28+
//! This structure does not have additional internal state. You can
29+
//! save the state and return to the point as you wish.
30+
//!
31+
//! - Concurrency-safe: No
32+
//! - See `io.UnpackReader`
33+
//!
34+
//! ```
35+
//! const unpack: Unpack = Unpack.init(data);
36+
//!
37+
//! if (unpack.peek()) |peek| {
38+
//! const requiredSize = peek.nextComponentSize();
39+
//! if (requiredSize > unpack.rest.len) {
40+
//! const ndata = readMore(data);
41+
//! unpack.setAppend(data.len, ndata);
42+
//! data = ndata;
43+
//! }
44+
//!
45+
//! const header = unpack.next(peek);
46+
//! if ((header.type.family() != .array
47+
//! or header.type.family() != .map) // streaming map or array elements
48+
//! and unpack.rest.len < header.size) {
49+
//! const ndata = readMore(data);
50+
//! unpack.setAppend(data.len, ndata);
51+
//! data = ndata;
52+
//! }
53+
//! } else {
54+
//! doSomething(); // No enough data to peek
55+
//! }
56+
//! ```
57+
const std = @import("std");
58+
const compatstd = @import("./compatstd.zig");
59+
const readIntBig = compatstd.mem.readIntBig;
60+
const root = @import("./root.zig");
61+
const HeaderType = root.HeaderType;
62+
const Header = root.Header;
63+
64+
rest: []const u8,
65+
66+
const Unpack = @This();
67+
68+
pub fn init(data: []const u8) Unpack {
69+
return .{ .rest = data };
70+
}
71+
72+
pub fn setAppend(self: *Unpack, olen: usize, new: []const u8) void {
73+
const ofs = olen - self.rest.len;
74+
self.rest = new[ofs..];
75+
}
76+
77+
pub const PeekError = error{
78+
BufferEmpty,
79+
UnrecognizedType,
80+
};
81+
82+
pub fn peek(self: *const Unpack) PeekError!HeaderType {
83+
if (self.rest.len == 0) {
84+
return PeekError.BufferEmpty;
85+
}
86+
87+
return HeaderType.from(self.rest[0]) orelse PeekError.UnrecognizedType;
88+
}
89+
90+
/// Consumes the current value header.
91+
///
92+
/// You can use the result `Header.size` to confirm the
93+
/// buffer can have further read.
94+
/// Note: for array or map, the size is the number of items.
95+
/// You can ignore it for streaming or still use it to prepare data,
96+
/// this is also the number of bytes of container types.
97+
///
98+
/// Calling this function, you must confirm the buffer has enough data to
99+
/// read. Use `HeaderType.nextComponentSize` to get the expected size for
100+
/// the value header.
101+
pub fn next(self: *Unpack, headerType: HeaderType) Header {
102+
const header, const consumes = Header.from(headerType, self.rest[1..]);
103+
self.rest = self.rest[1 + consumes ..];
104+
return header;
105+
}
106+
107+
pub const ConvertError = error{InvalidValue};
108+
109+
/// Consumes the current value as the null.
110+
///
111+
/// The `T` must be an optional type.
112+
pub fn nil(_: *Unpack, T: type, header: Header) ConvertError!T {
113+
if (header.type == .nil) {
114+
return null;
115+
}
116+
return ConvertError.InvalidValue;
117+
}
118+
119+
/// Consumes the current value as a bool.
120+
///
121+
/// This function does not need additional data from the buffer.
122+
pub fn @"bool"(_: *Unpack, header: Header) ConvertError!bool {
123+
return switch (header.type) {
124+
.bool => |v| v,
125+
else => ConvertError.InvalidValue,
126+
};
127+
}
128+
129+
inline fn rawUInt(self: *Unpack, header: Header) ConvertError!u64 {
130+
defer self.rest = self.rest[header.size..];
131+
return switch (header.size) {
132+
1 => readIntBig(u8, self.rest[0..1]),
133+
2 => readIntBig(u16, self.rest[0..2]),
134+
4 => readIntBig(u32, self.rest[0..4]),
135+
8 => readIntBig(u64, self.rest[0..8]),
136+
else => unreachable,
137+
};
138+
}
139+
140+
inline fn rawInt(self: *Unpack, header: Header) ConvertError!i64 {
141+
defer self.rest = self.rest[header.size..];
142+
return switch (header.size) {
143+
1 => readIntBig(i8, self.rest[0..1]),
144+
2 => readIntBig(i16, self.rest[0..2]),
145+
4 => readIntBig(i32, self.rest[0..4]),
146+
8 => readIntBig(i64, self.rest[0..8]),
147+
else => unreachable,
148+
};
149+
}
150+
151+
/// Consume the current value and casts to your requested integer type.
152+
///
153+
/// Use `i65` to make sure enough space for any unsigned integer.
154+
pub fn int(self: *Unpack, Int: type, header: Header) ConvertError!Int {
155+
return switch (header.type) {
156+
.fixint => |n| std.math.cast(Int, n) orelse ConvertError.InvalidValue,
157+
.int => std.math.cast(Int, try self.rawInt(header)) orelse ConvertError.InvalidValue,
158+
.uint => std.math.cast(Int, try self.rawUInt(header)) orelse ConvertError.InvalidValue,
159+
.float => @intFromFloat(try self.rawFloat(header)),
160+
else => ConvertError.InvalidValue,
161+
};
162+
}
163+
164+
/// Consume the current value as the raw, as long as they
165+
/// have the size.
166+
pub fn raw(self: *Unpack, header: Header) ConvertError![]const u8 {
167+
switch (header.type) {
168+
.array, .fixarray, .map, .fixmap => return ConvertError.InvalidValue,
169+
else => {
170+
const result = self.rest[0..header.size];
171+
self.rest = self.rest[header.size..];
172+
return result;
173+
},
174+
}
175+
}
176+
177+
inline fn rawFloat(self: *Unpack, header: Header) ConvertError!f64 {
178+
const value: f64 = switch (header.size) {
179+
4 => compatstd.mem.readFloatBig(f32, self.rest[0..4]),
180+
8 => compatstd.mem.readFloatBig(f64, self.rest[0..8]),
181+
else => unreachable,
182+
};
183+
self.rest = self.rest[header.size..];
184+
return value;
185+
}
186+
187+
fn checkedFloatFromInt(Float: type, i: anytype) ConvertError!Float {
188+
const max = (2 << (std.math.floatMantissaBits(Float) + 1)) + 1;
189+
const min = -max;
190+
191+
if (i > max or i < min) {
192+
return ConvertError.InvalidValue;
193+
}
194+
195+
return @floatFromInt(i);
196+
}
197+
198+
/// Consume the current value and casts to requested float type.
199+
pub fn float(self: *Unpack, Float: type, header: Header) ConvertError!Float {
200+
return switch (header.type) {
201+
.float => @floatCast(try self.rawFloat(header)),
202+
.fixint => |n| checkedFloatFromInt(Float, n),
203+
.int => checkedFloatFromInt(Float, try self.rawInt(header)),
204+
.uint => checkedFloatFromInt(Float, try self.rawUInt(header)),
205+
else => ConvertError.InvalidValue,
206+
};
207+
}
208+
209+
pub fn array(self: *Unpack, header: Header) ConvertError!Array {
210+
return switch (header.type) {
211+
.fixarray, .array => Array{
212+
.unpack = self,
213+
.len = header.size,
214+
},
215+
else => return ConvertError.InvalidValue,
216+
};
217+
}
218+
219+
pub fn map(self: *Unpack, header: Header) ConvertError!Map {
220+
return switch (header.type) {
221+
.fixmap, .map => Map{
222+
.unpack = self,
223+
.len = header.size,
224+
},
225+
else => return ConvertError.InvalidValue,
226+
};
227+
}
228+
229+
pub const Array = struct {
230+
unpack: *Unpack,
231+
current: u32 = 0,
232+
len: u32,
233+
234+
/// Peek the next header type.
235+
///
236+
/// Return `null` if the array is ended, `PeekError.BufferEmpty`
237+
/// if the buffered data does not enough for peeking.
238+
pub fn peek(self: Array) PeekError!?HeaderType {
239+
if (self.current >= self.len) {
240+
return null;
241+
}
242+
243+
return try self.unpack.peek();
244+
}
245+
246+
pub fn next(self: *Array, headerType: HeaderType) Header {
247+
const value = self.unpack.next(headerType);
248+
self.current += 1;
249+
return value;
250+
}
251+
};
252+
253+
pub const Map = struct {
254+
unpack: *Unpack,
255+
current: u32 = 0,
256+
len: u32,
257+
is_value: bool = false,
258+
259+
pub fn peek(self: Map) PeekError!?HeaderType {
260+
if (self.current >= self.len) {
261+
return null;
262+
}
263+
264+
return try self.unpack.peek();
265+
}
266+
267+
pub fn next(self: *Map, headerType: HeaderType) Header {
268+
const value = self.unpack.next(headerType);
269+
if (self.is_value) {
270+
self.current += 1;
271+
}
272+
self.is_value = !self.is_value;
273+
return value;
274+
}
275+
};

0 commit comments

Comments
 (0)