diff --git a/README.md b/README.md index 7548143..f2beae8 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ - Null control characters, eg: U+0000 are forbidden and will be ignored by the parser - Of course, uses null terminated strings, clearly this is not the best approach, but it's memory efficient and fast as fuck! - It passes most unit tests of the [JSON test suite](https://github.com/nst/JSONTestSuite), totalling 286 tests as of 2025-05-29 when I'm writing this. -- It has no reflection as of right now, you must implement your own `parse` function, with reflection - All defaults are configurable via the `Flags` bitfield +- Reflection is supported ## Sic respondeo: The Zig Discord server is plagued with modern scum, of course, modern scum will dismiss all of my claims or label them as "dumb" or "you're using it wrong!", has any of these individuals not considered that Zig is over complicated? There is a reason why Andrew Kelley himself detached from the communities [and has spoken in multiple instances](https://andrewkelley.me/post/goodbye-twitter-reddit.html) about the "shitification" of software communities, it's like turning a good community of like-minded programmers into a soydev shill. One good thing that he did was shutting down the r/zig subreddit. diff --git a/language.zig b/language.zig index ab77103..d9d8e5b 100644 --- a/language.zig +++ b/language.zig @@ -177,7 +177,7 @@ fn addNull(self: *Self, allocator: mem.Allocator) !usize { } // Recursively compute how many index slots a node occupies (including nested) -fn skipSlots(self: *Self, slot: usize) usize { +pub fn skipSlots(self: *Self, slot: usize) usize { switch (self.index.get(slot)) { .object => |obj| { var total: usize = 1; diff --git a/reflection.zig b/reflection.zig index 954ddd2..24934f1 100644 --- a/reflection.zig +++ b/reflection.zig @@ -1 +1,180 @@ -// TBD +const std = @import("std"); +const mem = std.mem; +const Language = @import("language.zig"); +const Tokenizer = @import("tokenizer.zig"); +const assert = std.debug.assert; + +const Self = @This(); + +pub const Error = error{TypeError}; + +language: *Language, +tokenizer: *Tokenizer, + +pub fn parse(self: *Self, allocator: mem.Allocator) !usize { + return self.language.parse(allocator, self.tokenizer); +} + +pub fn init(allocator: mem.Allocator, text: []const u8) !Self { + const self = Self{ + .language = try allocator.create(Language), + .tokenizer = try allocator.create(Tokenizer), + }; + + self.language.* = .init; + self.tokenizer.* = try .init(allocator, text); + return self; +} + +pub fn deinit(self: *Self, allocator: mem.Allocator) void { + self.language.deinit(allocator); + self.tokenizer.deinit(allocator); + allocator.destroy(self.language); + allocator.destroy(self.tokenizer); +} + +/// always returns 0 (root) +/// needs an index +/// const idx: usize = try self.language.parse(allocator, self.tokenizer); +pub fn reflectT(self: *Self, comptime T: type, allocator: mem.Allocator, idx: usize) !T { + const Schema = @typeInfo(T); + + switch (self.language.index.get(idx)) { + .null => { + if (Schema == .null) return null; + return error.TypeError; + }, + .bool => |b| { + if (Schema == .bool) return b; + return error.TypeError; + }, + .number => |number| switch (Schema) { + .int, .comptime_int => return @intFromFloat(number), + .float, .comptime_float => return @floatCast(number), + .@"struct" => |structInfo| switch (structInfo.layout) { + .@"packed" => return @bitCast(number), + else => return error.TypeError, + }, + else => unreachable, + }, + .string => |string| switch (Schema) { + .array => |arrayInfo| { + assert(arrayInfo.child == u8); + const strslice = string.slice(&self.language.strings); + assert(arrayInfo.len == strslice.len - 1); + + var r: T = undefined; + + for (strslice, 0..) |char, i| + r[i] = char; + return r; + }, + .pointer => |ptrInfo| switch (ptrInfo.size) { + .slice => { + assert(ptrInfo.child == u8); + const strslice = string.slice(&self.language.strings); + var arraylist: std.ArrayList(u8) = .init(allocator); + try arraylist.ensureUnusedCapacity(strslice.len); + + for (strslice) |char| if (char != 0x00) + arraylist.appendAssumeCapacity(char); + + if (ptrInfo.sentinel_ptr) |some| { + const sentinel = @as(*align(1) const ptrInfo.child, @ptrCast(some)).*; + return try arraylist.toOwnedSliceSentinel(sentinel); + } + + if (ptrInfo.is_const) { + arraylist.deinit(); + return strslice; + } else { + arraylist.deinit(); + const slice = try allocator.dupe(u8, strslice); + return @as(T, slice); + } + return try arraylist.toOwnedSlice(); + }, + else => return error.TypeError, + }, + else => return error.TypeError, + }, + .array => |slice| switch (Schema) { + .array => |arrayInfo| { + assert(slice.len == arrayInfo.len); + var r: T = undefined; + + for (0..slice.len) |i| + r[i] = try self.reflectT(arrayInfo.child, allocator, slice.tip + i); + return r; + }, + .pointer => |ptrInfo| switch (ptrInfo.size) { + .slice => {}, + else => return error.TypeError, + }, + else => return error.TypeError, + }, + .object => |object| switch (Schema) { + .@"struct" => |structInfo| { + if (structInfo.is_tuple) return error.TypeError; + + var tip = object.tip; + + var map: std.StringArrayHashMapUnmanaged(usize) = .empty; + try map.ensureTotalCapacity(allocator, object.len); + defer map.deinit(allocator); + + for (0..object.len) |_| if (self.language.property_map.get(tip)) |pen| { + const key = pen.tip.slice(&self.language.properties); + map.putAssumeCapacity(key, tip); + tip += self.language.skipSlots(tip); + }; + + var r: T = undefined; + inline for (structInfo.fields) |field| { + if (field.is_comptime) + @panic(@typeName(T) ++ "." ++ field.name ++ " may not be a comptime field"); + + if (map.get(field.name)) |next_i| { + @field(r, field.name) = try self.reflectT(field.type, allocator, next_i); + } else switch (@typeInfo(field.type)) { + .optional => @field(r, field.name) = null, + else => @panic("Unknown property: " ++ field.name), + } + } + + return r; + }, + else => return error.TypeError, + }, + } + + unreachable; +} + +test reflectT { + const allocator = std.testing.allocator; + + const text = + \\{ + \\ "age": 15, + \\ "name": "Yuzu", + \\ "admin": true + \\} + ; + var self = try allocator.create(Self); + self.* = try init(allocator, text); + + defer allocator.destroy(self); + defer self.deinit(allocator); + + const idx: usize = try self.parse(allocator); + const UserSchema = struct { + age: f64, + name: []const u8, + admin: bool, + }; + const root = try self.reflectT(UserSchema, allocator, idx); + errdefer allocator.free(root.name); + + std.debug.print("my name is {s}\n", .{root.name}); +} diff --git a/root.zig b/root.zig index 3645513..0a8aaaf 100644 --- a/root.zig +++ b/root.zig @@ -1,3 +1,4 @@ pub const Language = @import("language.zig"); pub const Tokenizer = @import("tokenizer.zig"); pub const StringPool = @import("strings.zig"); +pub const Reflect = @import("reflection.zig"); diff --git a/tokenizer.zig b/tokenizer.zig index 156ae2f..1455423 100644 --- a/tokenizer.zig +++ b/tokenizer.zig @@ -47,7 +47,7 @@ stack: []usize, frame: usize, /// Initialize a new tokenizer -pub fn init(allocator: std.mem.Allocator, text: []const u8) mem.Allocator.Error!Self { +pub fn init(allocator: .mem.Allocator, text: []const u8) mem.Allocator.Error!Self { const stack = try allocator.alloc(usize, 0x100); errdefer allocator.free(stack); @memset(stack, 0); @@ -279,7 +279,7 @@ pub fn nextIdentifier(self: *Self, allocator: mem.Allocator) Error!Token { const ident = buffer[0..i]; // true - if (std.mem.eql(u8, ident, "true")) { + if (.mem.eql(u8, ident, "true")) { return .{ .type = .true, .value = null, @@ -289,7 +289,7 @@ pub fn nextIdentifier(self: *Self, allocator: mem.Allocator) Error!Token { } // false - if (std.mem.eql(u8, ident, "false")) { + if (.mem.eql(u8, ident, "false")) { return .{ .type = .false, .value = null, @@ -299,7 +299,7 @@ pub fn nextIdentifier(self: *Self, allocator: mem.Allocator) Error!Token { } // null - if (std.mem.eql(u8, ident, "null")) { + if (.mem.eql(u8, ident, "null")) { return .{ .type = .null, .value = null, @@ -387,7 +387,7 @@ pub fn nextString(self: *Self, allocator: mem.Allocator) Error!Token { switch (try self.lastChar()) { '"' => { - while (std.mem.indexOfScalar(u8, buffer.items, 0x00)) |idx| + while (.mem.indexOfScalar(u8, buffer.items, 0x00)) |idx| _ = buffer.swapRemove(idx); return .{