aether/reflection.zig
2025-05-30 00:57:50 -05:00

181 lines
6.0 KiB
Zig

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