add reflection

This commit is contained in:
yuzu 2025-05-30 00:57:50 -05:00
parent f84303e83f
commit 4fcbe20a15
5 changed files with 188 additions and 8 deletions

View File

@ -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.

View File

@ -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;

View File

@ -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});
}

View File

@ -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");

View File

@ -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 .{