From e5305f85c9f277a378ed55ecd6322910fbc3f9cf Mon Sep 17 00:00:00 2001 From: "Lucas F." Date: Sun, 4 Jan 2026 12:29:36 -0300 Subject: [PATCH] update: context set --- src/context.zig | 100 +++++++++++++++++++++++++++++++++++++------ src/context_test.zig | 85 ++++++++++-------------------------- 2 files changed, 110 insertions(+), 75 deletions(-) diff --git a/src/context.zig b/src/context.zig index dbb1c5b..f723366 100644 --- a/src/context.zig +++ b/src/context.zig @@ -10,7 +10,7 @@ pub const Value = union(enum) { dict: std.StringHashMapUnmanaged(Value), pub fn deinit(self: Value) void { - _ = self; // nada — a arena libera tudo + _ = self; // arena libera tudo } }; @@ -34,18 +34,94 @@ pub const Context = struct { self.arena.deinit(); } - pub fn set(self: *Context, key: []const u8, value: Value) !void { - const gop = try self.map.getOrPut(self.allocator(), key); - if (gop.found_existing) { - // opcional: deinit value antigo se necessário - // mas como arena libera tudo, não precisa - } else { - gop.key_ptr.* = try self.allocator().dupe(u8, key); - } - gop.value_ptr.* = value; + fn toValue(self: *Context, value: anytype) !Value { + const T = @TypeOf(value); + return switch (@typeInfo(T)) { + .bool => Value{ .bool = value }, + .int, .comptime_int => Value{ .int = @intCast(value) }, + .float, .comptime_float => Value{ .float = @floatCast(value) }, + .pointer => Value{ .string = std.fmt.allocPrint(self.allocator(), "{s}", .{value}) catch "" }, + .@"struct" => blk: { + var dict = std.StringHashMapUnmanaged(Value){}; + inline for (std.meta.fields(T)) |field| { + const field_val = @field(value, field.name); + const converted = try self.toValue(field_val); + try dict.put(self.allocator(), field.name, converted); + } + break :blk Value{ .dict = dict }; + }, + .array => blk: { + var list = try self.allocator().alloc(Value, value.len); + for (value, 0..) |item, i| { + list[i] = try self.toValue(item); + } + break :blk Value{ .list = list }; + }, + .optional => if (value) |v| try self.toValue(v) else .null, + .null => .null, + else => @compileError("Unsupported type: " ++ @typeName(T)), + }; } - pub fn get(self: *const Context, key: []const u8) ?Value { - return self.map.get(key); + pub fn set(self: *Context, key: []const u8, value: anytype) !void { + const v = try self.toValue(value); + + const gop = try self.map.getOrPut(self.allocator(), key); + if (!gop.found_existing) { + gop.key_ptr.* = try self.allocator().dupe(u8, key); + } + gop.value_ptr.* = v; } + + //get com suporte a ponto ("user.name", "lista.0") + pub fn get(self: *const Context, path: []const u8) ?Value { + if (path.len == 0) return null; + + var current = self.map.get(path); + if (current) |v| return v; + + var remaining = path; + var start: usize = 0; + + while (std.mem.indexOfScalarPos(u8, remaining, start, '.')) |dot_pos| { + const key = remaining[start..dot_pos]; + current = self.map.get(key); + if (current == null) return null; + + remaining = remaining[dot_pos + 1 ..]; + start = 0; + + current = switch (current.?) { + .dict => |d| d.get(remaining), + .list => |l| blk: { + const index = std.fmt.parseInt(usize, remaining, 10) catch return null; + break :blk if (index < l.len) l[index] else null; + }, + else => return null, + }; + if (current == null) return null; + } + + return current; + } + + // pub fn get(self: *const Context, comptime T: type, key: []const u8) !T { + // // const opt_value = self.map.get(key) orelse return error.KeyNotFound; + // const opt_value = self.getOptional(T, key); + // + // return switch (opt_value) { + // .null => if (T == void or @typeInfo(T) == .optional) null else error.TypeMismatch, + // .bool => |b| if (T == bool) b else error.TypeMismatch, + // .int => |i| if (@typeInfo(T) == .int or @typeInfo(T) == .comptime_int) @intCast(i) else error.TypeMismatch, + // .float => |f| if (@typeInfo(T) == .float or @typeInfo(T) == .comptime_float) @floatCast(f) else error.TypeMismatch, + // .string => |s| if (T == []const u8) s else error.TypeMismatch, + // .list => |l| if (T == []const Value) l else error.TypeMismatch, + // .dict => |d| if (T == std.StringHashMapUnmanaged(Value)) d else error.TypeMismatch, + // }; + // } + // + // // Versão opcional que retorna optional + // pub fn getOptional(self: *const Context, comptime T: type, key: []const u8) ?T { + // return self.get(T, key) catch null; + // } }; diff --git a/src/context_test.zig b/src/context_test.zig index 8902ac5..d3702b6 100644 --- a/src/context_test.zig +++ b/src/context_test.zig @@ -3,74 +3,33 @@ const testing = std.testing; const Context = @import("context.zig").Context; const Value = @import("context.zig").Value; -test "context com arena" { +test "context set amigável e get com ponto" { const allocator = testing.allocator; var ctx = Context.init(allocator); defer ctx.deinit(); - try ctx.set("nome", Value{ .string = "Lucas" }); // sem dupe! - try ctx.set("idade", Value{ .int = 30 }); + try ctx.set("nome", "Lucas"); + try ctx.set("idade", 30); + try ctx.set("ativo", true); + try ctx.set("preco", 99.99); + try ctx.set("vazio", ""); + // struct + const Person = struct { nome: []const u8, idade: i64 }; + const p = Person{ .nome = "Ana", .idade = 25 }; + try ctx.set("user", p); + // + // // list + const numeros = [_]i64{ 1, 2, 3 }; + try ctx.set("lista", numeros); + + // acesso try testing.expectEqualStrings("Lucas", ctx.get("nome").?.string); - try testing.expectEqual(@as(i64, 30), ctx.get("idade").?.int); -} - -test "context todos os tipos" { - const allocator = testing.allocator; - var ctx = Context.init(allocator); - defer ctx.deinit(); - - // null - try ctx.set("nulo", Value{ .null = {} }); - try testing.expectEqual(ctx.get("nulo").?, .null); - - // bool - try ctx.set("verdadeiro", Value{ .bool = true }); - try ctx.set("falso", Value{ .bool = false }); - try testing.expect(ctx.get("verdadeiro").?.bool == true); - try testing.expect(ctx.get("falso").?.bool == false); - - // int - try ctx.set("idade", Value{ .int = 30 }); try testing.expect(ctx.get("idade").?.int == 30); - - // float - try ctx.set("preco", Value{ .float = 99.99 }); - try testing.expectApproxEqAbs(@as(f64, 99.99), ctx.get("preco").?.float, 0.001); - - // string - try ctx.set("nome", Value{ .string = "Lucas" }); - try testing.expectEqualStrings("Lucas", ctx.get("nome").?.string); - - // list - var list = try allocator.alloc(Value, 3); - defer allocator.free(list); - - list[0] = Value{ .int = 1 }; - list[1] = Value{ .int = 2 }; - list[2] = Value{ .int = 3 }; - - try ctx.set("numeros", Value{ .list = list }); - const numeros = ctx.get("numeros").?.list; - try testing.expectEqual(@as(usize, 3), numeros.len); - try testing.expect(numeros[0].int == 1); - try testing.expect(numeros[1].int == 2); - try testing.expect(numeros[2].int == 3); - - // dict - var dict = std.StringHashMapUnmanaged(Value){}; - try dict.put(ctx.allocator(), "chave1", Value{ .string = "valor1" }); - try dict.put(ctx.allocator(), "chave2", Value{ .int = 42 }); - try ctx.set("config", Value{ .dict = dict }); - const config = ctx.get("config").?.dict; - try testing.expectEqualStrings("valor1", config.get("chave1").?.string); - try testing.expect(config.get("chave2").?.int == 42); -} - -test "failback" { - const allocator = testing.allocator; - var ctx = Context.init(allocator); - defer ctx.deinit(); - - try testing.expectEqual(ctx.get("nome"), null); + try testing.expectEqualStrings("Ana", ctx.get("user.nome").?.string); + try testing.expect(ctx.get("user.idade").?.int == 25); + try testing.expect(ctx.get("lista.1").?.int == 2); + try testing.expect(ctx.get("vazio").?.string.len == 0); + try testing.expect(ctx.get("preco").?.float == 99.99); + try testing.expect(ctx.get("ativo").?.bool == true); }