add reflection
This commit is contained in:
parent
f84303e83f
commit
4fcbe20a15
@ -27,8 +27,8 @@
|
|||||||
- Null control characters, eg: U+0000 are forbidden and will be ignored by the parser
|
- 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!
|
- 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 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
|
- All defaults are configurable via the `Flags` bitfield
|
||||||
|
- Reflection is supported
|
||||||
|
|
||||||
## Sic respondeo:
|
## 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.
|
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.
|
||||||
|
@ -177,7 +177,7 @@ fn addNull(self: *Self, allocator: mem.Allocator) !usize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Recursively compute how many index slots a node occupies (including nested)
|
// 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)) {
|
switch (self.index.get(slot)) {
|
||||||
.object => |obj| {
|
.object => |obj| {
|
||||||
var total: usize = 1;
|
var total: usize = 1;
|
||||||
|
181
reflection.zig
181
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});
|
||||||
|
}
|
||||||
|
1
root.zig
1
root.zig
@ -1,3 +1,4 @@
|
|||||||
pub const Language = @import("language.zig");
|
pub const Language = @import("language.zig");
|
||||||
pub const Tokenizer = @import("tokenizer.zig");
|
pub const Tokenizer = @import("tokenizer.zig");
|
||||||
pub const StringPool = @import("strings.zig");
|
pub const StringPool = @import("strings.zig");
|
||||||
|
pub const Reflect = @import("reflection.zig");
|
||||||
|
@ -47,7 +47,7 @@ stack: []usize,
|
|||||||
frame: usize,
|
frame: usize,
|
||||||
|
|
||||||
/// Initialize a new tokenizer
|
/// 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);
|
const stack = try allocator.alloc(usize, 0x100);
|
||||||
errdefer allocator.free(stack);
|
errdefer allocator.free(stack);
|
||||||
@memset(stack, 0);
|
@memset(stack, 0);
|
||||||
@ -279,7 +279,7 @@ pub fn nextIdentifier(self: *Self, allocator: mem.Allocator) Error!Token {
|
|||||||
const ident = buffer[0..i];
|
const ident = buffer[0..i];
|
||||||
|
|
||||||
// true
|
// true
|
||||||
if (std.mem.eql(u8, ident, "true")) {
|
if (.mem.eql(u8, ident, "true")) {
|
||||||
return .{
|
return .{
|
||||||
.type = .true,
|
.type = .true,
|
||||||
.value = null,
|
.value = null,
|
||||||
@ -289,7 +289,7 @@ pub fn nextIdentifier(self: *Self, allocator: mem.Allocator) Error!Token {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// false
|
// false
|
||||||
if (std.mem.eql(u8, ident, "false")) {
|
if (.mem.eql(u8, ident, "false")) {
|
||||||
return .{
|
return .{
|
||||||
.type = .false,
|
.type = .false,
|
||||||
.value = null,
|
.value = null,
|
||||||
@ -299,7 +299,7 @@ pub fn nextIdentifier(self: *Self, allocator: mem.Allocator) Error!Token {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// null
|
// null
|
||||||
if (std.mem.eql(u8, ident, "null")) {
|
if (.mem.eql(u8, ident, "null")) {
|
||||||
return .{
|
return .{
|
||||||
.type = .null,
|
.type = .null,
|
||||||
.value = null,
|
.value = null,
|
||||||
@ -387,7 +387,7 @@ pub fn nextString(self: *Self, allocator: mem.Allocator) Error!Token {
|
|||||||
|
|
||||||
switch (try self.lastChar()) {
|
switch (try self.lastChar()) {
|
||||||
'"' => {
|
'"' => {
|
||||||
while (std.mem.indexOfScalar(u8, buffer.items, 0x00)) |idx|
|
while (.mem.indexOfScalar(u8, buffer.items, 0x00)) |idx|
|
||||||
_ = buffer.swapRemove(idx);
|
_ = buffer.swapRemove(idx);
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user