From 77968f04d2005db384d43c67e136397262fe05ab Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Mon, 20 Nov 2023 00:28:21 -0800 Subject: [PATCH] Implement various `adtf` features needed on ARM (#115) * Fix test step not running tests * Fix `error.Todo` causing a crash while outputting json * Cleanup `@as` usage * Implement count attribute for registers * Add ad-hoc alignment to support an initial reserved region. * Implement register group instances * Add `build.zig.zon` paths --- tools/regz/build.zig | 3 +- tools/regz/build.zig.zon | 9 +++++ tools/regz/src/Database.zig | 15 +++++++++ tools/regz/src/atdf.zig | 65 +++++++++++++++++++++++-------------- tools/regz/src/gen.zig | 32 ++++++++++-------- tools/regz/src/regzon.zig | 3 ++ tools/regz/src/svd.zig | 16 ++++----- tools/regz/src/xml.zig | 40 ++++++++++++++--------- 8 files changed, 121 insertions(+), 62 deletions(-) diff --git a/tools/regz/build.zig b/tools/regz/build.zig index 7cb1c68..3476b18 100644 --- a/tools/regz/build.zig +++ b/tools/regz/build.zig @@ -71,6 +71,7 @@ pub fn build(b: *std.build.Builder) !void { .optimize = optimize, }); tests.linkLibrary(libxml2_dep.artifact("xml2")); + const run_tests = b.addRunArtifact(tests); const test_step = b.step("test", "Run unit tests"); - test_step.dependOn(&tests.step); + test_step.dependOn(&run_tests.step); } diff --git a/tools/regz/build.zig.zon b/tools/regz/build.zig.zon index 5d421e8..1801852 100644 --- a/tools/regz/build.zig.zon +++ b/tools/regz/build.zig.zon @@ -1,6 +1,15 @@ .{ .name = "regz", .version = "0.0.0", + .paths = .{ + "LICENSE", + "README.md", + "build.zig", + "build.zig.zon", + "characterized", + "src", + "tests", + }, .dependencies = .{ .libxml2 = .{ .url = "https://github.com/mitchellh/zig-build-libxml2/archive/4e80a167d6e13e09e0da77457310401084e758cc.tar.gz", diff --git a/tools/regz/src/Database.zig b/tools/regz/src/Database.zig index ef5711e..3631fdc 100644 --- a/tools/regz/src/Database.zig +++ b/tools/regz/src/Database.zig @@ -9,6 +9,7 @@ attrs: struct { description: HashMap(EntityId, []const u8) = .{}, offset: HashMap(EntityId, u64) = .{}, access: HashMap(EntityId, Access) = .{}, + group: HashMap(EntityId, void) = .{}, count: HashMap(EntityId, u64) = .{}, size: HashMap(EntityId, u64) = .{}, reset_value: HashMap(EntityId, u64) = .{}, @@ -75,6 +76,7 @@ const log = std.log.scoped(.database); pub const EntityId = u32; pub const EntitySet = ArrayHashMap(EntityId, void); +pub const RegisterKind = enum { register, register_group }; // concrete arch's that we support in codegen, for stuff like interrupt // table generation @@ -217,6 +219,7 @@ pub fn deinit(db: *Database) void { db.attrs.description.deinit(db.gpa); db.attrs.offset.deinit(db.gpa); db.attrs.access.deinit(db.gpa); + db.attrs.group.deinit(db.gpa); db.attrs.count.deinit(db.gpa); db.attrs.size.deinit(db.gpa); db.attrs.reset_value.deinit(db.gpa); @@ -324,6 +327,7 @@ pub fn destroy_entity(db: *Database, id: EntityId) void { log.debug("{}: destroying peripheral", .{id}); // TODO: remove children _ = db.types.peripherals.swapRemove(id); + _ = db.instances.peripherals.swapRemove(id); }, else => {}, } @@ -476,6 +480,7 @@ pub fn create_register( offset: u64, /// size is in bits size: u64, + kind: RegisterKind = .register, /// count if there is an array count: ?u64 = null, access: ?Access = null, @@ -499,6 +504,11 @@ pub fn create_register( try db.add_offset(id, opts.offset); try db.add_size(id, opts.size); + switch (opts.kind) { + .register => {}, + .register_group => try db.add_group(id), + } + if (opts.count) |c| try db.add_count(id, c); @@ -730,6 +740,11 @@ pub fn add_access(db: *Database, id: EntityId, access: Access) !void { try db.attrs.access.putNoClobber(db.gpa, id, access); } +pub fn add_group(db: *Database, id: EntityId) !void { + log.debug("{}: adding register group", .{id}); + try db.attrs.group.putNoClobber(db.gpa, id, {}); +} + pub fn add_count(db: *Database, id: EntityId, count: u64) !void { log.debug("{}: adding count: {}", .{ id, count }); try db.attrs.count.putNoClobber(db.gpa, id, count); diff --git a/tools/regz/src/atdf.zig b/tools/regz/src/atdf.zig index d21b44f..b9e2166 100644 --- a/tools/regz/src/atdf.zig +++ b/tools/regz/src/atdf.zig @@ -39,11 +39,11 @@ pub fn load_into_db(db: *Database, doc: xml.Doc) !void { defer ctx.deinit(); const root = try doc.get_root_element(); - var module_it = root.iterate(&.{"modules"}, "module"); + var module_it = root.iterate(&.{"modules"}, &.{"module"}); while (module_it.next()) |entry| try load_module_type(&ctx, entry); - var device_it = root.iterate(&.{"devices"}, "device"); + var device_it = root.iterate(&.{"devices"}, &.{"device"}); while (device_it.next()) |entry| try load_device(&ctx, entry); @@ -75,13 +75,13 @@ fn load_device(ctx: *Context, node: xml.Node) !void { if (node.get_attribute("series")) |series| try db.add_device_property(id, "series", series); - var module_it = node.iterate(&.{"peripherals"}, "module"); + var module_it = node.iterate(&.{"peripherals"}, &.{"module"}); while (module_it.next()) |module_node| load_module_instances(ctx, module_node, id) catch |err| { log.warn("failed to instantiate module: {}", .{err}); }; - if (node.find_child("interrupts")) |interrupts_node| + if (node.find_child(&.{"interrupts"})) |interrupts_node| try load_interrupts(ctx, interrupts_node, id); try infer_peripheral_offsets(ctx); @@ -104,11 +104,11 @@ fn load_device(ctx: *Context, node: xml.Node) !void { } fn load_interrupts(ctx: *Context, node: xml.Node, device_id: EntityId) !void { - var interrupt_it = node.iterate(&.{}, "interrupt"); + var interrupt_it = node.iterate(&.{}, &.{"interrupt"}); while (interrupt_it.next()) |interrupt_node| try load_interrupt(ctx, interrupt_node, device_id); - var interrupt_group_it = node.iterate(&.{}, "interrupt-group"); + var interrupt_group_it = node.iterate(&.{}, &.{"interrupt-group"}); while (interrupt_group_it.next()) |interrupt_group_node| try load_interrupt_group(ctx, interrupt_group_node, device_id); } @@ -198,26 +198,33 @@ fn infer_peripheral_offset(ctx: *Context, type_id: EntityId, instance_id: Entity // TODO: assert that there's only one instance using this type var min_offset: ?u64 = null; + var max_size: u64 = 8; // first find the min offset of all the registers for this peripheral const register_set = db.children.registers.get(type_id) orelse return; for (register_set.keys()) |register_id| { const offset = db.attrs.offset.get(register_id) orelse continue; - if (min_offset == null) min_offset = offset else min_offset = @min(min_offset.?, offset); + + if (db.attrs.size.get(register_id)) |size| + max_size = @max(max_size, size); } if (min_offset == null) return error.NoRegisters; - const instance_offset: u64 = db.attrs.offset.get(instance_id) orelse 0; - try db.attrs.offset.put(db.gpa, instance_id, instance_offset + min_offset.?); + const assumed_align = @min(@divExact(std.math.ceilPowerOfTwoAssert(u64, max_size), 8), 8); + const old_instance_offset = db.attrs.offset.get(instance_id) orelse 0; + const new_instance_offset = std.mem.alignBackward(u64, old_instance_offset + min_offset.?, assumed_align); + const offset_delta = @as(i65, new_instance_offset) - @as(i65, old_instance_offset); + + try db.attrs.offset.put(db.gpa, instance_id, new_instance_offset); for (register_set.keys()) |register_id| { if (db.attrs.offset.getEntry(register_id)) |offset_entry| - offset_entry.value_ptr.* -= min_offset.?; + offset_entry.value_ptr.* = @intCast(offset_entry.value_ptr.* - offset_delta); } } @@ -287,7 +294,7 @@ fn infer_enum_size(db: *Database, enum_id: EntityId) !void { // TODO: instances use name in module fn get_inlined_register_group(parent_node: xml.Node, parent_name: []const u8) ?xml.Node { - var register_group_it = parent_node.iterate(&.{}, "register-group"); + var register_group_it = parent_node.iterate(&.{}, &.{"register-group"}); const rg_node = register_group_it.next() orelse return null; const rg_name = rg_node.get_attribute("name") orelse return null; log.debug("rg name is {s}, parent is {s}", .{ rg_name, parent_name }); @@ -325,11 +332,11 @@ fn load_module_type(ctx: *Context, node: xml.Node) !void { if (node.get_attribute("caption")) |caption| try db.add_description(id, caption); - var value_group_it = node.iterate(&.{}, "value-group"); + var value_group_it = node.iterate(&.{}, &.{"value-group"}); while (value_group_it.next()) |value_group_node| try load_enum(ctx, value_group_node, id); - var interrupt_group_it = node.iterate(&.{}, "interrupt-group"); + var interrupt_group_it = node.iterate(&.{}, &.{"interrupt-group"}); while (interrupt_group_it.next()) |interrupt_group_node| try load_module_interrupt_group(ctx, interrupt_group_node); @@ -340,7 +347,7 @@ fn load_module_type(ctx: *Context, node: xml.Node) !void { if (get_inlined_register_group(node, name)) |register_group_node| { try load_register_group_children(ctx, register_group_node, id); } else { - var register_group_it = node.iterate(&.{}, "register-group"); + var register_group_it = node.iterate(&.{}, &.{"register-group"}); while (register_group_it.next()) |register_group_node| try load_register_group(ctx, register_group_node, id); } @@ -350,7 +357,7 @@ fn load_module_interrupt_group(ctx: *Context, node: xml.Node) !void { const name = node.get_attribute("name") orelse return error.MissingInterruptGroupName; try ctx.interrupt_groups.put(ctx.db.gpa, name, .{}); - var interrupt_it = node.iterate(&.{}, "interrupt"); + var interrupt_it = node.iterate(&.{}, &.{"interrupt"}); while (interrupt_it.next()) |interrupt_node| try load_module_interrupt_group_entry(ctx, interrupt_node, name); } @@ -382,13 +389,13 @@ fn load_register_group_children( assert(db.entity_is("type.peripheral", dest_id) or db.entity_is("type.register_group", dest_id)); - var mode_it = node.iterate(&.{}, "mode"); + var mode_it = node.iterate(&.{}, &.{"mode"}); while (mode_it.next()) |mode_node| load_mode(ctx, mode_node, dest_id) catch |err| { log.err("{}: failed to load mode: {}", .{ dest_id, err }); }; - var register_it = node.iterate(&.{}, "register"); + var register_it = node.iterate(&.{}, &.{ "register", "register-group" }); while (register_it.next()) |register_node| try load_register(ctx, register_node, dest_id); } @@ -565,6 +572,16 @@ fn load_register( try std.fmt.parseInt(u64, offset_str, 0) else return error.MissingRegisterOffset, + .kind = if (std.mem.eql(u8, node.get_name(), "register")) + .register + else if (std.mem.eql(u8, node.get_name(), "register-group")) + .register_group + else + unreachable, + .count = if (node.get_attribute("count")) |count_str| + try std.fmt.parseInt(u64, count_str, 0) + else + null, }); errdefer db.destroy_entity(id); @@ -594,13 +611,13 @@ fn load_register( } // assumes that modes are parsed before registers in the register group - var mode_it = node.iterate(&.{}, "mode"); + var mode_it = node.iterate(&.{}, &.{"mode"}); while (mode_it.next()) |mode_node| load_mode(ctx, mode_node, id) catch |err| { log.err("{}: failed to load mode: {}", .{ id, err }); }; - var field_it = node.iterate(&.{}, "bitfield"); + var field_it = node.iterate(&.{}, &.{"bitfield"}); while (field_it.next()) |field_node| load_field(ctx, field_node, id) catch {}; } @@ -758,7 +775,7 @@ fn load_enum( if (node.get_attribute("caption")) |caption| try db.add_description(id, caption); - var value_it = node.iterate(&.{}, "value"); + var value_it = node.iterate(&.{}, &.{"value"}); while (value_it.next()) |value_node| load_enum_field(ctx, value_node, id) catch {}; @@ -826,7 +843,7 @@ fn load_module_instances( } }; - var instance_it = node.iterate(&.{}, "instance"); + var instance_it = node.iterate(&.{}, &.{"instance"}); while (instance_it.next()) |instance_node| try load_module_instance(ctx, instance_node, device_id, type_id); } @@ -885,14 +902,14 @@ fn load_module_instance_from_peripheral( } else { return error.Todo; //unreachable; - //var register_group_it = node.iterate(&.{}, "register-group"); + //var register_group_it = node.iterate(&.{}, &.{"register-group"}); //while (register_group_it.next()) |register_group_node| // loadRegisterGroupInstance(db, register_group_node, id, peripheral_type_id) catch { // log.warn("skipping register group instance in {s}", .{name}); // }; } - var signal_it = node.iterate(&.{"signals"}, "signal"); + var signal_it = node.iterate(&.{"signals"}, &.{"signal"}); while (signal_it.next()) |signal_node| try load_signal(ctx, signal_node, id); @@ -907,7 +924,7 @@ fn load_module_instance_from_register_group( ) !void { const db = ctx.db; const register_group_node = blk: { - var it = node.iterate(&.{}, "register-group"); + var it = node.iterate(&.{}, &.{"register-group"}); const ret = it.next() orelse return error.MissingInstanceRegisterGroup; if (it.next() != null) { return error.TodoInstanceWithMultipleRegisterGroups; diff --git a/tools/regz/src/gen.zig b/tools/regz/src/gen.zig index 75a9c39..b7c8b58 100644 --- a/tools/regz/src/gen.zig +++ b/tools/regz/src/gen.zig @@ -42,7 +42,7 @@ pub fn to_zig(db: Database, out_writer: anytype) !void { try writer.writeByte(0); // format the generated code - var ast = try std.zig.Ast.parse(db.gpa, @as([:0]const u8, @ptrCast(buffer.items[0 .. buffer.items.len - 1])), .zig); + var ast = try std.zig.Ast.parse(db.gpa, buffer.items[0 .. buffer.items.len - 1 :0], .zig); defer ast.deinit(db.gpa); // TODO: ast check? @@ -262,7 +262,7 @@ fn write_peripheral_instance(db: Database, instance_id: EntityId, offset: u64, o else ""; - try writer.print("pub const {s} = @as(*volatile {s}{s}, @ptrFromInt(0x{x}));\n", .{ + try writer.print("pub const {s}: *volatile {s}{s} = @ptrFromInt(0x{x});\n", .{ std.zig.fmtId(name), array_prefix, type_ref, @@ -687,7 +687,13 @@ fn write_register( else ""; - if (db.children.fields.get(register_id)) |field_set| { + if (db.attrs.group.contains(register_id)) { + try writer.print("{s}: {s}{s},\n", .{ + std.zig.fmtId(name), + array_prefix, + std.zig.fmtId(name), + }); + } else if (db.children.fields.get(register_id)) |field_set| { var fields = std.ArrayList(EntityWithOffset).init(db.gpa); defer fields.deinit(); @@ -906,7 +912,7 @@ test "gen.peripheral instantiation" { \\pub const devices = struct { \\ pub const TEST_DEVICE = struct { \\ pub const peripherals = struct { - \\ pub const TEST0 = @as(*volatile types.peripherals.TEST_PERIPHERAL, @ptrFromInt(0x1000)); + \\ pub const TEST0: *volatile types.peripherals.TEST_PERIPHERAL = @ptrFromInt(0x1000); \\ }; \\ }; \\}; @@ -940,8 +946,8 @@ test "gen.peripherals with a shared type" { \\pub const devices = struct { \\ pub const TEST_DEVICE = struct { \\ pub const peripherals = struct { - \\ pub const TEST0 = @as(*volatile types.peripherals.TEST_PERIPHERAL, @ptrFromInt(0x1000)); - \\ pub const TEST1 = @as(*volatile types.peripherals.TEST_PERIPHERAL, @ptrFromInt(0x2000)); + \\ pub const TEST0: *volatile types.peripherals.TEST_PERIPHERAL = @ptrFromInt(0x1000); + \\ pub const TEST1: *volatile types.peripherals.TEST_PERIPHERAL = @ptrFromInt(0x2000); \\ }; \\ }; \\}; @@ -1160,8 +1166,8 @@ test "gen.namespaced register groups" { \\pub const devices = struct { \\ pub const ATmega328P = struct { \\ pub const peripherals = struct { - \\ pub const PORTB = @as(*volatile types.peripherals.PORT.PORTB, @ptrFromInt(0x23)); - \\ pub const PORTC = @as(*volatile types.peripherals.PORT.PORTC, @ptrFromInt(0x26)); + \\ pub const PORTB: *volatile types.peripherals.PORT.PORTB = @ptrFromInt(0x23); + \\ pub const PORTC: *volatile types.peripherals.PORT.PORTC = @ptrFromInt(0x26); \\ }; \\ }; \\}; @@ -1202,7 +1208,7 @@ test "gen.peripheral with reserved register" { \\pub const devices = struct { \\ pub const ATmega328P = struct { \\ pub const peripherals = struct { - \\ pub const PORTB = @as(*volatile types.peripherals.PORTB, @ptrFromInt(0x23)); + \\ pub const PORTB: *volatile types.peripherals.PORTB = @ptrFromInt(0x23); \\ }; \\ }; \\}; @@ -1235,7 +1241,7 @@ test "gen.peripheral with count" { \\pub const devices = struct { \\ pub const ATmega328P = struct { \\ pub const peripherals = struct { - \\ pub const PORTB = @as(*volatile [4]types.peripherals.PORTB, @ptrFromInt(0x23)); + \\ pub const PORTB: *volatile [4]types.peripherals.PORTB = @ptrFromInt(0x23); \\ }; \\ }; \\}; @@ -1268,7 +1274,7 @@ test "gen.peripheral with count, padding required" { \\pub const devices = struct { \\ pub const ATmega328P = struct { \\ pub const peripherals = struct { - \\ pub const PORTB = @as(*volatile [4]types.peripherals.PORTB, @ptrFromInt(0x23)); + \\ pub const PORTB: *volatile [4]types.peripherals.PORTB = @ptrFromInt(0x23); \\ }; \\ }; \\}; @@ -1302,7 +1308,7 @@ test "gen.register with count" { \\pub const devices = struct { \\ pub const ATmega328P = struct { \\ pub const peripherals = struct { - \\ pub const PORTB = @as(*volatile types.peripherals.PORTB, @ptrFromInt(0x23)); + \\ pub const PORTB: *volatile types.peripherals.PORTB = @ptrFromInt(0x23); \\ }; \\ }; \\}; @@ -1335,7 +1341,7 @@ test "gen.register with count and fields" { \\pub const devices = struct { \\ pub const ATmega328P = struct { \\ pub const peripherals = struct { - \\ pub const PORTB = @as(*volatile types.peripherals.PORTB, @ptrFromInt(0x23)); + \\ pub const PORTB: *volatile types.peripherals.PORTB = @ptrFromInt(0x23); \\ }; \\ }; \\}; diff --git a/tools/regz/src/regzon.zig b/tools/regz/src/regzon.zig index 8a97089..2cc9385 100644 --- a/tools/regz/src/regzon.zig +++ b/tools/regz/src/regzon.zig @@ -803,6 +803,9 @@ fn populate_peripheral( if (db.attrs.version.get(id)) |version| try peripheral.put("version", .{ .string = version }); + if (db.attrs.group.contains(id)) + try peripheral.put("group", .{ .bool = true }); + if (db.attrs.count.get(id)) |count| try peripheral.put("count", .{ .integer = @as(i64, @intCast(count)) }); diff --git a/tools/regz/src/svd.zig b/tools/regz/src/svd.zig index 36422a4..41dd1c1 100644 --- a/tools/regz/src/svd.zig +++ b/tools/regz/src/svd.zig @@ -84,7 +84,7 @@ pub fn load_into_db(db: *Database, doc: xml.Doc) !void { // peripherals // vendorExtensions - var cpu_it = root.iterate(&.{}, "cpu"); + var cpu_it = root.iterate(&.{}, &.{"cpu"}); if (cpu_it.next()) |cpu| { const required_properties: []const []const u8 = &.{ "name", @@ -138,7 +138,7 @@ pub fn load_into_db(db: *Database, doc: xml.Doc) !void { const register_props = try RegisterProperties.parse(root); try ctx.register_props.put(db.gpa, device_id, register_props); - var peripheral_it = root.iterate(&.{"peripherals"}, "peripheral"); + var peripheral_it = root.iterate(&.{"peripherals"}, &.{"peripheral"}); while (peripheral_it.next()) |peripheral_node| load_peripheral(&ctx, peripheral_node, device_id) catch |err| log.warn("failed to load peripheral: {}", .{err}); @@ -286,7 +286,7 @@ pub fn load_peripheral(ctx: *Context, node: xml.Node, device_id: EntityId) !void try db.add_size(type_id, elements.dim_increment); } - var interrupt_it = node.iterate(&.{}, "interrupt"); + var interrupt_it = node.iterate(&.{}, &.{"interrupt"}); while (interrupt_it.next()) |interrupt_node| try load_interrupt(db, interrupt_node, device_id); @@ -302,13 +302,13 @@ pub fn load_peripheral(ctx: *Context, node: xml.Node, device_id: EntityId) !void const register_props = try ctx.derive_register_properties_from(node, device_id); try ctx.register_props.put(db.gpa, type_id, register_props); - var register_it = node.iterate(&.{"registers"}, "register"); + var register_it = node.iterate(&.{"registers"}, &.{"register"}); while (register_it.next()) |register_node| load_register(ctx, register_node, type_id) catch |err| log.warn("failed to load register: {}", .{err}); // TODO: handle errors when implemented - var cluster_it = node.iterate(&.{"registers"}, "cluster"); + var cluster_it = node.iterate(&.{"registers"}, &.{"cluster"}); while (cluster_it.next()) |cluster_node| load_cluster(ctx, cluster_node, type_id) catch |err| log.warn("failed to load cluster: {}", .{err}); @@ -425,7 +425,7 @@ fn load_register( }); errdefer db.destroy_entity(id); - var field_it = node.iterate(&.{"fields"}, "field"); + var field_it = node.iterate(&.{"fields"}, &.{"field"}); while (field_it.next()) |field_node| load_field(ctx, field_node, id) catch |err| log.warn("failed to load register: {}", .{err}); @@ -470,7 +470,7 @@ fn load_field(ctx: *Context, node: xml.Node, register_id: EntityId) !void { if (node.get_value("access")) |access_str| try db.add_access(id, try parse_access(access_str)); - if (node.find_child("enumeratedValues")) |enum_values_node| + if (node.find_child(&.{"enumeratedValues"})) |enum_values_node| try load_enumerated_values(ctx, enum_values_node, id); if (node.get_attribute("derivedFrom")) |derived_from| @@ -503,7 +503,7 @@ fn load_enumerated_values(ctx: *Context, node: xml.Node, field_id: EntityId) !vo try db.attrs.@"enum".putNoClobber(db.gpa, field_id, id); - var value_it = node.iterate(&.{}, "enumeratedValue"); + var value_it = node.iterate(&.{}, &.{"enumeratedValue"}); while (value_it.next()) |value_node| try load_enumerated_value(ctx, value_node, id); } diff --git a/tools/regz/src/xml.zig b/tools/regz/src/xml.zig index 71628bd..75bc624 100644 --- a/tools/regz/src/xml.zig +++ b/tools/regz/src/xml.zig @@ -17,21 +17,23 @@ pub const Node = struct { pub const Iterator = struct { node: ?Node, - filter: []const u8, + filter: []const []const u8, pub fn next(it: *Iterator) ?Node { // TODO: what if current node doesn't fit the bill? - return while (it.node != null) : (it.node = if (it.node.?.impl.next) |impl| Node{ .impl = impl } else null) { + while (it.node != null) : (it.node = if (it.node.?.impl.next) |impl| Node{ .impl = impl } else null) { if (it.node.?.impl.type != 1) continue; if (it.node.?.impl.name) |name| - if (std.mem.eql(u8, it.filter, std.mem.span(name))) { - const ret = it.node; - it.node = if (it.node.?.impl.next) |impl| Node{ .impl = impl } else null; - break ret; - }; - } else return null; + for (it.filter) |elem| + if (std.mem.eql(u8, elem, std.mem.span(name))) { + const ret = it.node; + it.node = if (it.node.?.impl.next) |impl| Node{ .impl = impl } else null; + return ret; + }; + } + return null; } }; @@ -57,6 +59,10 @@ pub const Node = struct { } }; + pub fn get_name(node: Node) []const u8 { + return std.mem.span(node.impl.name); + } + pub fn get_attribute(node: Node, key: [:0]const u8) ?[]const u8 { if (c.xmlHasProp(node.impl, key.ptr)) |prop| { if (@as(*c.xmlAttr, @ptrCast(prop)).children) |value_node| { @@ -69,24 +75,26 @@ pub const Node = struct { return null; } - pub fn find_child(node: Node, key: []const u8) ?Node { + pub fn find_child(node: Node, keys: []const []const u8) ?Node { var it = @as(?*c.xmlNode, @ptrCast(node.impl.children)); - return while (it != null) : (it = it.?.next) { + while (it != null) : (it = it.?.next) { if (it.?.type != 1) continue; const name = std.mem.span(it.?.name orelse continue); - if (std.mem.eql(u8, key, name)) - break Node{ .impl = it.? }; - } else null; + for (keys) |key| + if (std.mem.eql(u8, key, name)) + return Node{ .impl = it.? }; + } + return null; } // `skip` will only delve into a specific path of elements // `name` will iterate the child elements with that name - pub fn iterate(node: Node, skip: []const []const u8, filter: []const u8) Iterator { + pub fn iterate(node: Node, skip: []const []const u8, filter: []const []const u8) Iterator { var current: Node = node; for (skip) |elem| - current = current.find_child(elem) orelse return Iterator{ + current = current.find_child(&.{elem}) orelse return Iterator{ .node = null, .filter = filter, }; @@ -105,7 +113,7 @@ pub const Node = struct { /// up to you to copy pub fn get_value(node: Node, key: []const u8) ?[:0]const u8 { - return if (node.find_child(key)) |child| + return if (node.find_child(&.{key})) |child| if (child.impl.children) |value_node| if (@as(*c.xmlNode, @ptrCast(value_node)).content) |content| std.mem.span(content)