further simplified the code and added some comments

This commit is contained in:
Yuzu 2024-12-14 22:25:37 -05:00
parent 4fc4ff16c0
commit c35b80739c
3 changed files with 148 additions and 86 deletions

View File

@ -433,18 +433,19 @@ pub fn jsonObject(str: []const u8, allocator: mem.Allocator) ParseResult(JsonRaw
return .{ out, .{} };
};
defer allocator.free(pairs);
errdefer for (pairs) |p| {
allocator.free(p[0]);
p[1].deinit(allocator);
errdefer for (pairs) |kv| {
const k, const v = kv;
allocator.free(k);
v.deinit(allocator);
};
const str4, _ = try closingCurlyBrace(str3, allocator);
var obj: JsonRawHashMap = .{};
errdefer obj.deinit(allocator);
for (pairs) |entry| {
const name, const value = entry;
try obj.put(allocator, name, value);
for (pairs) |kv| {
const k, const v = kv;
try obj.put(allocator, k, v);
}
return .{ str4, obj };
@ -457,23 +458,38 @@ test jsonObject {
const rem, var obj = try jsonObject(data, std.testing.allocator);
defer obj.deinit(std.testing.allocator);
var iterator = obj.iterator();
while (iterator.next()) |kv| {
const k = kv.key_ptr.*;
const v = kv.value_ptr.*;
defer std.testing.allocator.free(k);
defer v.deinit(std.testing.allocator);
std.debug.print("key: {s}, value: {any} and rem: {s}\n", .{ k, v, rem });
}
try std.testing.expect(rem.len == 1);
}
pub const JsonType = union(enum) {
/// the identifier `null`
null,
/// either the identifier `true` or the identifier `false`
bool: bool,
/// the utf-8 string type, surrounded by backquotes
string: []const u8,
/// either a float or an int
/// may be casted
number: JsonNumber,
/// JsonType surrounded by brackets, separated by commas
array: []JsonType,
/// the Object type, an unmanaged array hashmap, as keeping order might be useful for when printing
/// usually a string which represents the name of the property, followed by a colon, then a JsonType, separated by commas
object: JsonRawHashMap,
pub fn is(self: JsonType, tag: std.meta.Tag(JsonType)) bool {
@ -491,11 +507,9 @@ pub const JsonType = union(enum) {
defer @constCast(&obj).deinit(allocator);
var it = obj.iterator();
while (it.next()) |entry| {
//std.debug.print("freeing {*}\n", .{entry.key_ptr});
allocator.free(entry.key_ptr.*);
entry.value_ptr.*.deinit(allocator);
}
//allocator.destroy(&self);
},
else => {},
}
@ -504,7 +518,7 @@ pub const JsonType = union(enum) {
/// entry point of the library
pub const ultimateParser: Parser(JsonType) = alternation(JsonType, .{
jsonNull,
jsonNull, // attempts to parse each type from top to bottom
jsonBool,
jsonString,
jsonNumber,
@ -823,7 +837,7 @@ pub fn optional(comptime T: type, parser: Parser(T)) Parser(?T) {
const rest, const parsed = parser(str, allocator) catch |err| return switch (err) {
error.UnexpectedCharacter, error.Empty, error.NumberCastFail => .{ str, null },
error.OutOfMemory => error.OutOfMemory,
else => return err,
else => err,
};
return .{ rest, parsed };
}
@ -907,7 +921,8 @@ pub const BailingAllocator = struct {
/// general join
/// useful for joining more than 2 parsers
pub fn sequence(comptime Struct: type, comptime parsers: FieldParsers(Struct)) Parser(Struct) {
if (@typeInfo(Struct) != .@"struct") @compileError("expected a `struct` type");
if (@typeInfo(Struct) != .@"struct")
@compileError("expected a `struct` type");
return struct {
fn f(str: []const u8, allocator: std.mem.Allocator) ParseResult(Struct) {
@ -954,6 +969,36 @@ pub fn repetition(comptime T: type, parser: Parser(T)) Parser([]T) {
pub const Error = std.mem.Allocator.Error || ParserError;
/// assumes that you are casting a type against its JsonType counterpart
///
/// u8, u16, u32... -> assumes JsonNumber == .integer
/// i8, i16, i32... -> assumes JsonNumber == .integer
/// f16, f32, f64 -> assumes JsonNumber == .float
/// array or slice of u8 -> JsonType.string
/// array or slice of T -> JsonType.array
/// array of N elements -> assumes JsonType.array.len == N
/// null -> JsonType.null
/// void -> ignores JsonType
/// ?T -> JsonType
/// *T -> JsonType, which always allocates memory
/// struct{..} -> JsonType.object
/// enum(..) -> if JsonNumber, assumes JsonNumber == .integer otherwise JsonType.string
/// union -> JsonType.string | JsonType.bool | JsonType.number
/// (empty) union(enum) -> JsonType.string, the equivalent of `"a" | "b" | "c"` since values are `void`
/// tagged union -> panics, use DiscriminatedUnion(U, key) instead
/// Record(T) -> JsonType.object
/// AssociativeArray(E, V) -> JsonType.object (where keys are interpreted as enum 'E' members)
/// DiscriminatedUnion(U, key) -> JsonType.object (where a `key` that maps to `E` is present). Assumes that `U` is tagged by `E`
///
/// All other types are unsupported.
/// (Packed structs must be parsed manually, as the default parser of a `packed struct` is mirrored to one of an enum's)
///
/// If you must forcibly cast a type, you shall write a `json` method
/// the signature of the `json` function is as follows
/// ```
/// pub fn json(allocator: std.mem.Allocator, value: zjson.JsonType) !T
/// ```
/// where T is the type you must cast into
pub fn parseInto(comptime T: type, allocator: mem.Allocator, value: JsonType) Error!T {
switch (@typeInfo(T)) {
.void => return {},
@ -1041,6 +1086,9 @@ pub fn parseInto(comptime T: type, allocator: mem.Allocator, value: JsonType) Er
if (std.meta.hasFn(T, "json"))
return try T.json(allocator, value);
if (structInfo.layout == .@"packed")
return @bitCast(value.number.cast(structInfo.backing_integer.?)); // forcibly casted
if (!value.is(.object))
@panic("tried to cast a non-object into: " ++ @typeName(T));
@ -1064,16 +1112,19 @@ pub fn parseInto(comptime T: type, allocator: mem.Allocator, value: JsonType) Er
switch (value) {
.string => |string| {
if (arrayInfo.child != u8) return error.TypeMismatch; // attempting to cast an array of T against a string
std.debug.assert(arrayInfo.len == string.len);
var r: T = undefined;
var i: usize = 0;
while (i < arrayInfo.len) : (i += 1)
comptime var i: usize = 0;
inline while (i < arrayInfo.len) : (i += 1)
r[i] = try parseInto(arrayInfo.child, allocator, string[i]);
return r;
},
.array => |array| {
std.debug.assert(arrayInfo.len == array.len);
var r: T = undefined;
var i: usize = 0;
while (i < arrayInfo.len) : (i += 1)
comptime var i: usize = 0;
inline while (i < arrayInfo.len) : (i += 1)
r[i] = try parseInto(arrayInfo.child, allocator, array[i]);
return r;
},
@ -1174,14 +1225,14 @@ pub fn OwnedEither(comptime L: type, comptime R: type) type {
};
}
/// parse any string containing a JSON object root `{...}`
/// casts the value into `T`
/// parse any string containing either a JSON object root `{...}` or a JSON array `[...]`
/// casts the value into `T`, read `parseInto` for detailed instructions
pub fn parse(comptime T: type, child_allocator: mem.Allocator, data: []const u8) ParserError!Owned(T) {
var owned: Owned(T) = .{
.arena = try child_allocator.create(std.heap.ArenaAllocator),
.value = undefined,
};
owned.arena.* = std.heap.ArenaAllocator.init(child_allocator);
owned.arena.* = .init(child_allocator);
const allocator = owned.arena.allocator();
const value = try ultimateParserAssert(data, allocator);
owned.value = try parseInto(T, allocator, value);
@ -1196,7 +1247,7 @@ pub fn parseLeft(comptime L: type, comptime R: type, child_allocator: mem.Alloca
.arena = try child_allocator.create(std.heap.ArenaAllocator),
.value = undefined,
};
owned.arena.* = std.heap.ArenaAllocator.init(child_allocator);
owned.arena.* = .init(child_allocator);
const allocator = owned.arena.allocator();
const value = try ultimateParserAssert(data, allocator);
owned.value = .{ .left = try parseInto(L, allocator, value) };
@ -1211,7 +1262,7 @@ pub fn parseRight(comptime L: type, comptime R: type, child_allocator: mem.Alloc
.arena = try child_allocator.create(std.heap.ArenaAllocator),
.value = undefined,
};
owned.arena.* = std.heap.ArenaAllocator.init(child_allocator);
owned.arena.* = .init(child_allocator);
const allocator = owned.arena.allocator();
const value = try ultimateParserAssert(data, allocator);
owned.value = .{ .right = try parseInto(R, allocator, value) };
@ -1291,3 +1342,78 @@ pub fn AssociativeArray(comptime E: type, comptime V: type) type {
}
};
}
/// assumes object.value[key] is of type `E` and the result thereof maps to the tagged union value
/// assumes `key` is a field present in all members of type `U`
/// eg: `type` is a property of type E and E.User maps to a User struct, whereas E.Admin maps to an Admin struct
pub fn DiscriminatedUnion(comptime U: type, comptime key: []const u8) type {
if (@typeInfo(U) != .@"union")
@compileError("may only cast a union");
if (@typeInfo(U).@"union".tag_type == null)
@compileError("cannot cast untagged union");
const E = comptime @typeInfo(U).@"union".tag_type.?;
if (@typeInfo(U).@"union".tag_type.? != E)
@compileError("enum tag type of union(" ++ @typeName(U.@"union".tag_type) ++ ") doesn't match " ++ @typeName(E));
return struct {
t: U,
pub fn json(allocator: mem.Allocator, value: JsonType) !@This() {
if (!value.is(.object))
@panic("coulnd't match against non-object type");
const discriminator = value.object.get(key) orelse
@panic("couldn't find property " ++ key ++ "in raw object");
var u: U = undefined;
const tag = discriminator.number.cast(@typeInfo(E).@"enum".tag_type);
inline for (@typeInfo(E).@"enum".fields) |field| {
if (field.value == tag) {
const T = comptime std.meta.fields(U)[field.value].type;
comptime std.debug.assert(@hasField(T, key));
u = @unionInit(U, field.name, try parseInto(T, allocator, value));
}
}
return .{ .t = u };
}
};
}
test DiscriminatedUnion {
// pretty simple stuff, in a real app you'd have thousands of these
const AccountTypes = enum { user, admin };
const User = struct {
type: AccountTypes,
id: u64,
name: []const u8,
};
const Admin = struct {
type: AccountTypes,
id: u64,
name: []const u8,
admin: bool,
};
const Account = union(AccountTypes) {
user: User,
admin: Admin,
};
const payload =
\\ {"type": 1, "id": 100000, "name": "Mathew", "admin": true}
;
const data = try parse(DiscriminatedUnion(Account, "type"), std.testing.allocator, payload);
defer data.deinit();
try std.testing.expect(data.value.t == .admin);
try std.testing.expect(data.value.t.admin.type == .admin);
// we don't care otherwise about the data
}

View File

@ -47,7 +47,7 @@ const GatewayPayload = Types.GatewayPayload;
const Opcode = Types.GatewayOpcodes;
const Intents = Types.Intents;
const Snowflake = @import("./structures/snowflake.zig").Snowflake;
const Snowflake = Types.Snowflake;
const FetchReq = @import("http.zig").FetchReq;
const MakeRequestError = @import("http.zig").MakeRequestError;
const Partial = Types.Partial;
@ -190,11 +190,7 @@ inline fn calculateSafeRequests(options: RatelimitOptions) usize {
@as(f64, @floatFromInt(options.max_requests_per_ratelimit_tick orelse 120)) -
@ceil(@as(f64, @floatFromInt(options.ratelimit_reset_interval)) / 30000.0) * 2;
if (safe_requests < 0) {
return 0;
}
return @intFromFloat(safe_requests);
return if (safe_requests < 0) 0 else @intFromFloat(safe_requests);
}
inline fn _connect_ws(allocator: mem.Allocator, url: []const u8) !ws.Client {

View File

@ -16,7 +16,6 @@
const std = @import("std");
const Snowflake = @import("snowflake.zig").Snowflake;
const zjson = @import("../json.zig");
pub const PresenceStatus = enum {
online,
@ -43,10 +42,6 @@ pub const UserFlags = packed struct {
return @bitCast(raw);
}
pub fn json(_: std.mem.Allocator, value: zjson.JsonType) !@This() {
return @bitCast(value.number.cast(u34));
}
DiscordEmployee: bool = false,
PartneredServerOwner: bool = false,
HypeSquadEventsMember: bool = false,
@ -83,10 +78,6 @@ pub const PremiumUsageFlags = packed struct {
return @bitCast(raw);
}
pub fn json(_: std.mem.Allocator, value: zjson.JsonType) !@This() {
return @bitCast(value.number.cast(u8));
}
PremiumDiscriminator: bool = false,
AnimatedAvatar: bool = false,
ProfileBanner: bool = false,
@ -102,10 +93,6 @@ pub const PurchasedFlags = packed struct {
return @bitCast(raw);
}
pub fn json(_: std.mem.Allocator, value: zjson.JsonType) !@This() {
return @bitCast(value.number.cast(u8));
}
NitroClassic: bool = false,
Nitro: bool = false,
GuildBoost: bool = false,
@ -122,10 +109,6 @@ pub const MemberFlags = packed struct {
return @bitCast(raw);
}
pub fn json(_: std.mem.Allocator, value: zjson.JsonType) !@This() {
return @bitCast(value.number.cast(u16));
}
///
/// Member has left and rejoined the guild
///
@ -198,10 +181,6 @@ pub const ChannelFlags = packed struct {
return @bitCast(raw);
}
pub fn json(_: std.mem.Allocator, value: zjson.JsonType) !@This() {
return @bitCast(value.number.cast(u32));
}
None: bool = false,
/// this thread is pinned to the top of its parent `GUILD_FORUM` channel
Pinned: bool = false,
@ -224,10 +203,6 @@ pub const RoleFlags = packed struct {
return @bitCast(raw);
}
pub fn json(_: std.mem.Allocator, value: zjson.JsonType) !@This() {
return @bitCast(value.number.cast(u2));
}
None: bool = false,
/// Role can be selected by members in an onboarding prompt
InPrompt: bool = false,
@ -242,10 +217,6 @@ pub const AttachmentFlags = packed struct {
return @bitCast(raw);
}
pub fn json(_: std.mem.Allocator, value: zjson.JsonType) !@This() {
return @bitCast(value.number.cast(u8));
}
None: bool = false,
_pad: u1 = 0,
/// This attachment has been edited using the remix feature on mobile
@ -263,10 +234,6 @@ pub const SkuFlags = packed struct {
return @bitCast(raw);
}
pub fn json(_: std.mem.Allocator, value: zjson.JsonType) !@This() {
return @bitCast(value.number.cast(u16));
}
_pad: u2 = 0,
/// SKU is available for purchase
Available: bool = false,
@ -288,10 +255,6 @@ pub const MessageFlags = packed struct {
return @bitCast(raw);
}
pub fn json(_: std.mem.Allocator, value: zjson.JsonType) !@This() {
return @bitCast(value.number.cast(u16));
}
/// This message has been published to subscribed channels (via Channel Following)
Crossposted: bool = false,
/// This message originated from a message in another channel (via Channel Following)
@ -328,10 +291,6 @@ pub const ActivityFlags = packed struct {
return @bitCast(raw);
}
pub fn json(_: std.mem.Allocator, value: zjson.JsonType) !@This() {
return @bitCast(value.number.cast(u16));
}
Instance: bool = false,
Join: bool = false,
Spectate: bool = false,
@ -366,10 +325,6 @@ pub const ApplicationFlags = packed struct {
return @bitCast(raw);
}
pub fn json(_: std.mem.Allocator, value: zjson.JsonType) !@This() {
return @bitCast(value.number.cast(u32));
}
_pad: u5 = 0,
/// Indicates if an app uses the Auto Moderation API.
ApplicationAutoModerationRuleCreateBadge: bool = false,
@ -577,10 +532,6 @@ pub const SystemChannelFlags = packed struct {
return @bitCast(raw);
}
pub fn json(_: std.mem.Allocator, value: zjson.JsonType) !@This() {
return @bitCast(value.number.cast(u8));
}
/// Suppress member join notifications
SuppressJoinNotifications: bool = false,
/// Suppress server boost notifications
@ -622,10 +573,6 @@ pub const ChannelTypes = packed struct {
return @bitCast(raw);
}
pub fn json(_: std.mem.Allocator, value: zjson.JsonType) !@This() {
return @bitCast(value.number.cast(u32));
}
/// A text channel within a server
GuildText: bool = false,
/// A direct message between users
@ -949,10 +896,6 @@ pub const BitwisePermissionFlags = packed struct {
return @bitCast(raw);
}
pub fn json(_: std.mem.Allocator, value: zjson.JsonType) !@This() {
return @bitCast(value.number.cast(u64));
}
/// Allows creation of instant invites
CREATE_INSTANT_INVITE: bool = false,
/// Allows kicking members
@ -1296,9 +1239,6 @@ pub const GatewayIntents = packed struct {
return @bitCast(raw);
}
pub fn json(_: std.mem.Allocator, value: zjson.JsonType) !@This() {
return @bitCast(value.number.cast(u32));
}
///
/// - GUILD_CREATE
/// - GUILD_UPDATE