feat: add support for floats to cbor
This commit is contained in:
parent
6865acc01f
commit
2838ee23e0
2 changed files with 253 additions and 30 deletions
195
src/cbor.zig
195
src/cbor.zig
|
@ -1,5 +1,7 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
const native_endian = builtin.cpu.arch.endian();
|
||||||
const eql = std.mem.eql;
|
const eql = std.mem.eql;
|
||||||
const bufPrint = std.fmt.bufPrint;
|
const bufPrint = std.fmt.bufPrint;
|
||||||
const fixedBufferStream = std.io.fixedBufferStream;
|
const fixedBufferStream = std.io.fixedBufferStream;
|
||||||
|
@ -33,6 +35,9 @@ pub const CborJsonError = error{
|
||||||
const cbor_magic_null: u8 = 0xf6;
|
const cbor_magic_null: u8 = 0xf6;
|
||||||
const cbor_magic_true: u8 = 0xf5;
|
const cbor_magic_true: u8 = 0xf5;
|
||||||
const cbor_magic_false: u8 = 0xf4;
|
const cbor_magic_false: u8 = 0xf4;
|
||||||
|
const cbor_magic_float16: u8 = 0xf9;
|
||||||
|
const cbor_magic_float32: u8 = 0xfa;
|
||||||
|
const cbor_magic_float64: u8 = 0xfb;
|
||||||
|
|
||||||
const cbor_magic_type_array: u8 = 4;
|
const cbor_magic_type_array: u8 = 4;
|
||||||
const cbor_magic_type_map: u8 = 5;
|
const cbor_magic_type_map: u8 = 5;
|
||||||
|
@ -46,6 +51,7 @@ const value_type = enum(u8) {
|
||||||
tag,
|
tag,
|
||||||
boolean,
|
boolean,
|
||||||
null,
|
null,
|
||||||
|
float,
|
||||||
any,
|
any,
|
||||||
more,
|
more,
|
||||||
unknown,
|
unknown,
|
||||||
|
@ -138,6 +144,50 @@ fn writeU64(writer: anytype, value: u64) @TypeOf(writer).Error!void {
|
||||||
return writeTypedVal(writer, 0, value);
|
return writeTypedVal(writer, 0, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn writeF16(writer: anytype, value: f16) @TypeOf(writer).Error!void {
|
||||||
|
try write(writer, cbor_magic_float16);
|
||||||
|
const value_bytes = std.mem.asBytes(&value);
|
||||||
|
switch (native_endian) {
|
||||||
|
.big => try write(writer, value_bytes),
|
||||||
|
.little => {
|
||||||
|
try write(writer, value_bytes[1]);
|
||||||
|
try write(writer, value_bytes[0]);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn writeF32(writer: anytype, value: f32) @TypeOf(writer).Error!void {
|
||||||
|
try write(writer, cbor_magic_float32);
|
||||||
|
const value_bytes = std.mem.asBytes(&value);
|
||||||
|
switch (native_endian) {
|
||||||
|
.big => try write(writer, value_bytes),
|
||||||
|
.little => {
|
||||||
|
try write(writer, value_bytes[3]);
|
||||||
|
try write(writer, value_bytes[2]);
|
||||||
|
try write(writer, value_bytes[1]);
|
||||||
|
try write(writer, value_bytes[0]);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn writeF64(writer: anytype, value: f64) @TypeOf(writer).Error!void {
|
||||||
|
try write(writer, cbor_magic_float64);
|
||||||
|
const value_bytes = std.mem.asBytes(&value);
|
||||||
|
switch (native_endian) {
|
||||||
|
.big => try write(writer, value_bytes),
|
||||||
|
.little => {
|
||||||
|
try write(writer, value_bytes[7]);
|
||||||
|
try write(writer, value_bytes[6]);
|
||||||
|
try write(writer, value_bytes[5]);
|
||||||
|
try write(writer, value_bytes[4]);
|
||||||
|
try write(writer, value_bytes[3]);
|
||||||
|
try write(writer, value_bytes[2]);
|
||||||
|
try write(writer, value_bytes[1]);
|
||||||
|
try write(writer, value_bytes[0]);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn writeString(writer: anytype, s: []const u8) @TypeOf(writer).Error!void {
|
fn writeString(writer: anytype, s: []const u8) @TypeOf(writer).Error!void {
|
||||||
try writeTypedVal(writer, 3, s.len);
|
try writeTypedVal(writer, 3, s.len);
|
||||||
_ = try writer.write(s);
|
_ = try writer.write(s);
|
||||||
|
@ -225,6 +275,12 @@ pub fn writeValue(writer: anytype, value: anytype) @TypeOf(writer).Error!void {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.Null => try writeNull(writer),
|
.Null => try writeNull(writer),
|
||||||
|
.Float => |info| switch (info.bits) {
|
||||||
|
16 => try writeF16(writer, value),
|
||||||
|
32 => try writeF32(writer, value),
|
||||||
|
64 => try writeF64(writer, value),
|
||||||
|
else => @compileError("cannot write type '" ++ @typeName(T) ++ "' to cbor stream"),
|
||||||
|
},
|
||||||
else => @compileError("cannot write type '" ++ @typeName(T) ++ "' to cbor stream"),
|
else => @compileError("cannot write type '" ++ @typeName(T) ++ "' to cbor stream"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -343,6 +399,66 @@ fn decodeJsonObject(iter_: *[]const u8, minor: u5, obj: *json.ObjectMap) CborErr
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decodeFloat(comptime T: type, iter_: *[]const u8, t: CborType) CborError!T {
|
||||||
|
var v: T = undefined;
|
||||||
|
var iter = iter_.*;
|
||||||
|
switch (t.type) {
|
||||||
|
cbor_magic_float16 => {
|
||||||
|
if (iter.len < 2) return error.CborTooShort;
|
||||||
|
var f: f16 = undefined;
|
||||||
|
var f_bytes = std.mem.asBytes(&f);
|
||||||
|
switch (native_endian) {
|
||||||
|
.big => @memcpy(f_bytes, iter[0..2]),
|
||||||
|
.little => {
|
||||||
|
f_bytes[0] = iter[1];
|
||||||
|
f_bytes[1] = iter[0];
|
||||||
|
},
|
||||||
|
}
|
||||||
|
v = @floatCast(f);
|
||||||
|
iter = iter[2..];
|
||||||
|
},
|
||||||
|
cbor_magic_float32 => {
|
||||||
|
if (iter.len < 4) return error.CborTooShort;
|
||||||
|
var f: f32 = undefined;
|
||||||
|
var f_bytes = std.mem.asBytes(&f);
|
||||||
|
switch (native_endian) {
|
||||||
|
.big => @memcpy(f_bytes, iter[0..4]),
|
||||||
|
.little => {
|
||||||
|
f_bytes[0] = iter[3];
|
||||||
|
f_bytes[1] = iter[2];
|
||||||
|
f_bytes[2] = iter[1];
|
||||||
|
f_bytes[3] = iter[0];
|
||||||
|
},
|
||||||
|
}
|
||||||
|
v = @floatCast(f);
|
||||||
|
iter = iter[4..];
|
||||||
|
},
|
||||||
|
cbor_magic_float64 => {
|
||||||
|
if (iter.len < 8) return error.CborTooShort;
|
||||||
|
var f: f64 = undefined;
|
||||||
|
var f_bytes = std.mem.asBytes(&f);
|
||||||
|
switch (native_endian) {
|
||||||
|
.big => @memcpy(f_bytes, iter[0..8]),
|
||||||
|
.little => {
|
||||||
|
f_bytes[0] = iter[7];
|
||||||
|
f_bytes[1] = iter[6];
|
||||||
|
f_bytes[2] = iter[5];
|
||||||
|
f_bytes[3] = iter[4];
|
||||||
|
f_bytes[4] = iter[3];
|
||||||
|
f_bytes[5] = iter[2];
|
||||||
|
f_bytes[6] = iter[1];
|
||||||
|
f_bytes[7] = iter[0];
|
||||||
|
},
|
||||||
|
}
|
||||||
|
v = @floatCast(f);
|
||||||
|
iter = iter[8..];
|
||||||
|
},
|
||||||
|
else => return error.CborInvalidType,
|
||||||
|
}
|
||||||
|
iter_.* = iter;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn matchInt(comptime T: type, iter_: *[]const u8, val: *T) CborError!bool {
|
pub fn matchInt(comptime T: type, iter_: *[]const u8, val: *T) CborError!bool {
|
||||||
var iter = iter_.*;
|
var iter = iter_.*;
|
||||||
const t = try decodeType(&iter);
|
const t = try decodeType(&iter);
|
||||||
|
@ -394,6 +510,22 @@ fn matchBoolValue(iter: *[]const u8, val: bool) CborError!bool {
|
||||||
return if (try matchBool(iter, &v)) v == val else false;
|
return if (try matchBool(iter, &v)) v == val else false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn matchFloat(comptime T: type, iter_: *[]const u8, v: *T) CborError!bool {
|
||||||
|
var iter = iter_.*;
|
||||||
|
const t = try decodeType(&iter);
|
||||||
|
v.* = decodeFloat(T, &iter, t) catch |e| switch (e) {
|
||||||
|
error.CborInvalidType => return false,
|
||||||
|
else => return e,
|
||||||
|
};
|
||||||
|
iter_.* = iter;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matchFloatValue(comptime T: type, iter: *[]const u8, val: T) CborError!bool {
|
||||||
|
var v: T = 0.0;
|
||||||
|
return if (try matchFloat(T, iter, &v)) v == val else false;
|
||||||
|
}
|
||||||
|
|
||||||
fn skipString(iter: *[]const u8, minor: u5) CborError!void {
|
fn skipString(iter: *[]const u8, minor: u5) CborError!void {
|
||||||
const len: usize = @intCast(try decodePInt(iter, minor));
|
const len: usize = @intCast(try decodePInt(iter, minor));
|
||||||
if (iter.len < len)
|
if (iter.len < len)
|
||||||
|
@ -423,35 +555,38 @@ fn skipMap(iter: *[]const u8, minor: u5) CborError!void {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn skipValue(iter: *[]const u8) CborError!void {
|
pub fn skipValue(iter: *[]const u8) CborError!void {
|
||||||
const t = try decodeType(iter);
|
try skipValueType(iter, try decodeType(iter));
|
||||||
try skipValueType(iter, t.major, t.minor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn skipValueType(iter: *[]const u8, major: u3, minor: u5) CborError!void {
|
fn skipValueType(iter: *[]const u8, t: CborType) CborError!void {
|
||||||
switch (major) {
|
switch (t.major) {
|
||||||
0 => { // positive integer
|
0 => { // positive integer
|
||||||
_ = try decodePInt(iter, minor);
|
_ = try decodePInt(iter, t.minor);
|
||||||
},
|
},
|
||||||
1 => { // negative integer
|
1 => { // negative integer
|
||||||
_ = try decodeNInt(iter, minor);
|
_ = try decodeNInt(iter, t.minor);
|
||||||
},
|
},
|
||||||
2 => { // bytes
|
2 => { // bytes
|
||||||
try skipBytes(iter, minor);
|
try skipBytes(iter, t.minor);
|
||||||
},
|
},
|
||||||
3 => { // string
|
3 => { // string
|
||||||
try skipString(iter, minor);
|
try skipString(iter, t.minor);
|
||||||
},
|
},
|
||||||
4 => { // array
|
4 => { // array
|
||||||
try skipArray(iter, minor);
|
try skipArray(iter, t.minor);
|
||||||
},
|
},
|
||||||
5 => { // map
|
5 => { // map
|
||||||
try skipMap(iter, minor);
|
try skipMap(iter, t.minor);
|
||||||
},
|
},
|
||||||
6 => { // tag
|
6 => { // tag
|
||||||
return error.CborInvalidType;
|
return error.CborInvalidType;
|
||||||
},
|
},
|
||||||
7 => { // special
|
7 => switch (t.type) { // special
|
||||||
return;
|
cbor_magic_null, cbor_magic_false, cbor_magic_true => return,
|
||||||
|
cbor_magic_float16 => iter.* = iter.*[2..],
|
||||||
|
cbor_magic_float32 => iter.* = iter.*[4..],
|
||||||
|
cbor_magic_float64 => iter.* = iter.*[8..],
|
||||||
|
else => return error.CborInvalidType,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -459,23 +594,18 @@ fn skipValueType(iter: *[]const u8, major: u3, minor: u5) CborError!void {
|
||||||
fn matchType(iter_: *[]const u8, v: *value_type) CborError!bool {
|
fn matchType(iter_: *[]const u8, v: *value_type) CborError!bool {
|
||||||
var iter = iter_.*;
|
var iter = iter_.*;
|
||||||
const t = try decodeType(&iter);
|
const t = try decodeType(&iter);
|
||||||
try skipValueType(&iter, t.major, t.minor);
|
try skipValueType(&iter, t);
|
||||||
switch (t.major) {
|
switch (t.major) {
|
||||||
0, 1 => v.* = value_type.number, // positive integer or negative integer
|
0, 1 => v.* = value_type.number, // positive integer or negative integer
|
||||||
2 => v.* = value_type.bytes, // bytes
|
2 => v.* = value_type.bytes, // bytes
|
||||||
3 => v.* = value_type.string, // string
|
3 => v.* = value_type.string, // string
|
||||||
4 => v.* = value_type.array, // array
|
4 => v.* = value_type.array, // array
|
||||||
5 => v.* = value_type.map, // map
|
5 => v.* = value_type.map, // map
|
||||||
7 => { // special
|
7 => switch (t.type) { // special
|
||||||
if (t.type == cbor_magic_null) {
|
cbor_magic_null => v.* = value_type.null,
|
||||||
v.* = value_type.null;
|
cbor_magic_false, cbor_magic_true => v.* = value_type.boolean,
|
||||||
} else {
|
cbor_magic_float16, cbor_magic_float32, cbor_magic_float64 => v.* = value_type.float,
|
||||||
if (t.type == cbor_magic_false or t.type == cbor_magic_true) {
|
else => return false,
|
||||||
v.* = value_type.boolean;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
else => return false,
|
else => return false,
|
||||||
}
|
}
|
||||||
|
@ -529,6 +659,8 @@ pub fn matchValue(iter: *[]const u8, value: anytype) CborError!bool {
|
||||||
else
|
else
|
||||||
matchError(T),
|
matchError(T),
|
||||||
.Array => |info| if (info.child == u8) matchStringValue(iter, &value) else matchArray(iter, value, info),
|
.Array => |info| if (info.child == u8) matchStringValue(iter, &value) else matchArray(iter, value, info),
|
||||||
|
.Float => return matchFloatValue(T, iter, value),
|
||||||
|
.ComptimeFloat => matchFloatValue(f64, iter, value),
|
||||||
else => @compileError("cannot match value type '" ++ @typeName(T) ++ "' to cbor stream"),
|
else => @compileError("cannot match value type '" ++ @typeName(T) ++ "' to cbor stream"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -726,6 +858,7 @@ fn Extractor(comptime T: type) type {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
.Float => return matchFloat(T, iter, self.dest),
|
||||||
else => extractError(T),
|
else => extractError(T),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -795,12 +928,15 @@ pub fn JsonStream(comptime T: type) type {
|
||||||
|
|
||||||
pub fn jsonWriteValue(w: *JsonWriter, iter: *[]const u8) (CborJsonError || Writer.Error)!void {
|
pub fn jsonWriteValue(w: *JsonWriter, iter: *[]const u8) (CborJsonError || Writer.Error)!void {
|
||||||
const t = try decodeType(iter);
|
const t = try decodeType(iter);
|
||||||
if (t.type == cbor_magic_false)
|
switch (t.type) {
|
||||||
return w.write(false);
|
cbor_magic_false => return w.write(false),
|
||||||
if (t.type == cbor_magic_true)
|
cbor_magic_true => return w.write(true),
|
||||||
return w.write(true);
|
cbor_magic_null => return w.write(null),
|
||||||
if (t.type == cbor_magic_null)
|
cbor_magic_float16 => return w.write(try decodeFloat(f16, iter, t)),
|
||||||
return w.write(null);
|
cbor_magic_float32 => return w.write(try decodeFloat(f32, iter, t)),
|
||||||
|
cbor_magic_float64 => return w.write(try decodeFloat(f64, iter, t)),
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
return switch (t.major) {
|
return switch (t.major) {
|
||||||
0 => w.write(try decodePInt(iter, t.minor)), // positive integer
|
0 => w.write(try decodePInt(iter, t.minor)), // positive integer
|
||||||
1 => w.write(try decodeNInt(iter, t.minor)), // negative integer
|
1 => w.write(try decodeNInt(iter, t.minor)), // negative integer
|
||||||
|
@ -862,7 +998,6 @@ fn writeJsonValue(writer: anytype, value: json.Value) !void {
|
||||||
.array => |_| unreachable,
|
.array => |_| unreachable,
|
||||||
.object => |_| unreachable,
|
.object => |_| unreachable,
|
||||||
.null => writeNull(writer),
|
.null => writeNull(writer),
|
||||||
.float => |_| error.CborUnsupportedType,
|
|
||||||
inline else => |v| writeValue(writer, v),
|
inline else => |v| writeValue(writer, v),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,9 @@ const fmt = cbor_mod.fmt;
|
||||||
const toJson = cbor_mod.toJson;
|
const toJson = cbor_mod.toJson;
|
||||||
const toJsonPretty = cbor_mod.toJsonPretty;
|
const toJsonPretty = cbor_mod.toJsonPretty;
|
||||||
const fromJson = cbor_mod.fromJson;
|
const fromJson = cbor_mod.fromJson;
|
||||||
|
const fromJsonAlloc = cbor_mod.fromJsonAlloc;
|
||||||
|
const toJsonAlloc = cbor_mod.toJsonAlloc;
|
||||||
|
const toJsonPrettyAlloc = cbor_mod.toJsonPrettyAlloc;
|
||||||
const decodeType = cbor_mod.decodeType;
|
const decodeType = cbor_mod.decodeType;
|
||||||
const matchInt = cbor_mod.matchInt;
|
const matchInt = cbor_mod.matchInt;
|
||||||
const matchIntValue = cbor_mod.matchIntValue;
|
const matchIntValue = cbor_mod.matchIntValue;
|
||||||
|
@ -433,3 +436,88 @@ test "cbor.fromJson_object" {
|
||||||
const cbor = try fromJson(json_buf, &cbor_buf);
|
const cbor = try fromJson(json_buf, &cbor_buf);
|
||||||
try expect(try match(cbor, map));
|
try expect(try match(cbor, map));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "cbor f32" {
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
try expectEqualDeep(
|
||||||
|
fmt(&buf, .{ "float", @as(f32, 0.96891385316848755) }),
|
||||||
|
&[_]u8{
|
||||||
|
0x82, // 82 # array(2)
|
||||||
|
0x65, // 65 # text(5)
|
||||||
|
0x66, // 666C6F6174 # "float"
|
||||||
|
0x6C,
|
||||||
|
0x6F,
|
||||||
|
0x61,
|
||||||
|
0x74,
|
||||||
|
0xfa, // FA 3F780ABD # primitive(1064831677)
|
||||||
|
0x3F,
|
||||||
|
0x78,
|
||||||
|
0x0A,
|
||||||
|
0xBD,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "cbor.fromJson_object f32" {
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
const json_buf: []const u8 =
|
||||||
|
\\["float",0.96891385316848755]
|
||||||
|
;
|
||||||
|
const cbor = try fromJson(json_buf, &buf);
|
||||||
|
try expect(try match(cbor, array));
|
||||||
|
|
||||||
|
try expectEqualDeep(
|
||||||
|
&[_]u8{
|
||||||
|
0x82, // 82 # array(2)
|
||||||
|
0x65, // 65 # text(5)
|
||||||
|
0x66, // 666C6F6174 # "float"
|
||||||
|
0x6C,
|
||||||
|
0x6F,
|
||||||
|
0x61,
|
||||||
|
0x74,
|
||||||
|
0xfb, // FB 3FEF0157A0000000 # primitive(4606902419681443840)
|
||||||
|
0x3f,
|
||||||
|
0xef,
|
||||||
|
0x01,
|
||||||
|
0x57,
|
||||||
|
0xa0,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
},
|
||||||
|
cbor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "cbor.extract_match_f32" {
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
const json_buf: []const u8 =
|
||||||
|
\\["float",0.96891385316848755]
|
||||||
|
;
|
||||||
|
const m = try fromJson(json_buf, &buf);
|
||||||
|
|
||||||
|
try expect(try match(m, .{ "float", @as(f64, 0.96891385316848755) }));
|
||||||
|
|
||||||
|
var f: f64 = undefined;
|
||||||
|
try expect(try match(m, .{ "float", extract(&f) }));
|
||||||
|
try expectEqual(0.96891385316848755, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "cbor.extract_cbor f64" {
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
const json_buf: []const u8 =
|
||||||
|
\\["float",[0.96891385316848755],"check"]
|
||||||
|
;
|
||||||
|
const m = try fromJson(json_buf, &buf);
|
||||||
|
|
||||||
|
var sub: []const u8 = undefined;
|
||||||
|
try expect(try match(m, .{ "float", extract_cbor(&sub), "check" }));
|
||||||
|
|
||||||
|
const json = try toJsonPrettyAlloc(std.testing.allocator, sub);
|
||||||
|
defer std.testing.allocator.free(json);
|
||||||
|
|
||||||
|
const json2 = try toJsonAlloc(std.testing.allocator, sub);
|
||||||
|
defer std.testing.allocator.free(json2);
|
||||||
|
|
||||||
|
try expectEqualDeep("[9.689138531684875e-1]", json2);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue